Repository navigation
Conversation
…ped stub The tests for typedstandards#141 P2's acceptance 1 to 5, against a stub module whose names raise NotImplementedError, so the suite collects and each new test fails at its call: - tests/test_publish.py: a new record, a listed hash, a listed name with and without revises=, the name rules, a BlobRef output, the policy's signer, role and type, an empty title, the non-fast-forward retry, publish_attestation, and the Git Data API as the only writer; - tests/test_publish_token.py: the token's source and shape, a scanner of every captured output driven over each publish path and over offenders, no file opened, and the host's repr; - tests/test_readme.py: the README's publishing section; - scripts/github_stub.py: an in-memory GitHub API for httpx.MockTransport; - tests/fixtures/template-host*.json: the host template's host.json and host-policy.json at 70bfd18, verbatim and pinned by SHA-256. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
ts.publish(signed, host=, title=, name= or notebook=, role="notebook", revises=None) reads
host.json and host-policy.json at the branch head and writes the signed file and the edited
host.json in one commit: a blob each, a tree on the head's tree, a commit whose parent is the head,
and a fast-forward of the branch. A ref update that does not answer 200 re-reads the head first;
if the commit did not land and the branch moved, the call plans again from the new head once.
ts.publish_attestation(node, host=, name=) adds what withdraw or attest printed to a listed
record's entry the same way.
Refused before any write: a token that is not a fine-grained one or holds whitespace, a quote or
op://, a name failing host-core's rule or with a records or evidence segment, an empty title, a
BlobRef output, a signer or type the policy does not name, a role no active rule admits
(typedstandards#141 G0-3), and a listed name with another record unless revises= matches by type,
successorNodeId and targetNodeId (G0-4). A listed hash is a no-op. The receipt is
{name, commit, bundle_url, verify_url, registry_url, written, run: None}.
The module computes no hash (blob ids come back from the API), runs no CLI and reads no seed. The
token comes from token=, else TYPEDSTANDARDS_GITHUB_TOKEN when a publish runs, and is held only in
the client's Authorization header. scripts/smoke_publish.py is the live check for gate G3;
scripts/smoke_wheel.py now also publishes over MockTransport.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji
Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
httpx's default trust_env=True iterates os.environ for proxy variables when a client is built, and the environment of a signing notebook holds the seed. The client publish builds now passes trust_env=False, so it reads no variable but the token's (and httpx's own SSLKEYLOGFILE) and no .netrc. A caller who needs a proxy or a certificate bundle passes client=. The new test drives the live-client path under the guard tests' RecordingEnviron; with trust_env=True it fails on read_all. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
The README's new section documents GitHubPagesHost, publish and publish_attestation, the default name first and then the derived name a listed name takes with revises=, the refusals, the receipt's keys (run is None: publish does not wait for the deploy), the token (fine-grained, one repository, Contents read and write, never a literal in a cell), and the seed in a hosted notebook: the one line that sets it from the hosting service's secret store, the unsafe forms, the custody sentence, and GitHub Codespaces. publish and publish_attestation join the calls that run without Node; a test pins it. CHANGELOG: under Unreleased; the version stays 0.1.1. The README test's ordering assertion compares the default name with the derived-name rule rather than with the first "revises=", which the call's signature line carries. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
A notebook's verbose traceback (IPython's %xmode Verbose) prints the locals of every frame it shows. The scanner now also renders each exception with capture_locals=True, and an offender that raises from a frame holding the header proves it fails there. Against the current module, every token-shape refusal fails: the value is a local of the frame that raises. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
The token's shape is checked by a helper that returns the refusal's words, and the value is deleted before the refusal is raised. The request headers are built inside the calls that use them, so neither the API's constructor nor its request method holds them as a local. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
…did:key read A publish-mode copy starts with "records": [] and no records/ directory (the template's setup, step 5). The new test publishes into that state: the first entry is appended to the empty list and the tree on base_tree creates records/<name>.signed.json. It passes as written: publish already handles both. The README test fails: the README does not yet show the author how to read their did:key, which the template's setup puts in host-policy.json, before the first publish. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
…s setup The template's setup puts the author's did:key in host-policy.json's signer, and publish refuses any other signer. The README shows reading it from the first signed record (signed["package"]["signer"]["identifier"]), since the CLI prints a did:key only in what it signs, and links the template README's "Publishing from a notebook" section rather than restating it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01LJBCtCpvKX2vdg53xqw8ji Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
P2 of the notebook-publish sprint (npstorey/typedstandards#141; G0 record: its comment 6027279902).
A notebook author publishes a signed record to a GitHub Pages host made from the host template's publish mode, with one call.
ts.GitHubPagesHost("owner/repo"),ts.publish(signed, host=…, title=…, notebook=… or name=…, role=…, revises=…)andts.publish_attestation(node, host=…, name=…)each write one Git Data API commit: blobs, a tree onbase_tree, a commit, and a fast-forward of the branch. Every refusal happens before any write. The token reaches no output. The README documents all of it, including the seed's custody in a hosted notebook. There is no release in this PR: the version stays 0.1.1, and the changes are under## Unreleased.Draft until G3: the owner runs
scripts/smoke_publish.pyfrom the installed wheel, underop run, against the scratch publish-mode copynpstorey/typedstandards-publish-check. Its output will be added here before this is reported merge-ready.Branch and blast zone
ts141/p2-publish, head576c637ef40961d4f9b8c7e22bb2ce38f6150ab2, on6c0c20c. There are eight commits, each SSH-signed with a sign-off.These are unchanged, as
git diff origin/main...HEADover them is empty:tests/guards.py,tests/test_guards.py,.gitleaks.toml,package.json,package-lock.json,pyproject.toml,.github/, the CLI pin, the Node floor and the version. Nothing undersrc/importshashlib, names the seed variable, passesenv=or changesos.environ.Acceptance (#141 P2, with G0-3 and G0-4)
Red at
130585a, over a typed stub:89 failed, 249 passed. Each new test fails at its call (NotImplementedError) or at the missing README section. Green at head:346 passed.httpx.MockTransportpaths (tests/test_publish.py,tests/test_publish_token.py):written: False.revises=;<name>-<first 8 hex of its envelopeHash>, with the revises node on the target's entry, in one commit.recordsorevidencesegment is refused (4 cases); a name failing host-core's rule is refused (10 cases).github_pat_, or holds whitespace, quotes orop://, is refused, and the message does not contain the value (10 cases)."records": []and norecords/directory (the template's setup, step 5); the first entry is appended.repr, exceptionstrorrepr, or traceback, including a traceback with frame locals.test_the_scanner_fails_on_an_offenderdrives it over a path that leaks: red at130585a, green with 6 cases./contents/, and one commit per ref update._Api.writerefuses anything but POST or PATCH under/git/./contents/lines insrc/are GETs.token=, elseTYPEDSTANDARDS_GITHUB_TOKEN, read when the call runs.repr(GitHubPagesHost(...))shows the repository and branch only, and the host neither pickles nor exposesvars().tests/test_readme.pychecks each of these:TYPEDSTANDARDS_SIGNING_SEED_B64from a hosted platform's secret store;<stem>/<date>-<8 hex>, plan §7), which comes before the derived-name rule, per the seat's note on G0-4;did:keyfrom the first signed record, before the first publish.The Codespaces line rests on GitHub's documentation, read 2026-10-06 (the account-specific Codespaces secrets page; the Actions secrets and variables concept pages). That a Jupyter kernel started in a codespace inherits the variable was not measured.
576c637:346 passedon Python 3.11, 3.12 and 3.14 with Node v24.21.0;ruff check:All checks passed!;ruff format --check:46 files already formatted.346 passed), and the wheel job from a fresh clone: sdist then wheel, installed into a fresh environment,scripts/smoke_wheel.pyexit 0. Its new line reads:publish: … in one commit, then a no-op, then a withdrawal, over MockTransport.scripts/smoke_publish.py(G3). It signs one record with the installed wheel and publishes it to the scratch copy. It prints only the receipt's fields. It exits 2 on a refusal, 3 on an API error, 4 on a CLI error and 5 on an origin mismatch. Three tests run it over the stub API. It has not been run live.gitleaks over
origin/main..HEAD: no leaks found. An offline emulation of the push guard's keyword scan (digests masked,pushguard.allowapplied) gave 0 hits over the range.Fixture provenance
tests/fixtures/template-host.jsonhost.json70bfd188a80c9ea344196ac2b27b3a91eb3439f9217e72c5491b18540d253925b28a6fetest_template_fixture_is_the_verbatim_copy)tests/fixtures/template-host-policy.jsonhost-policy.json70bfd189150db40fee1f2e17bf09d128c080f0bee9a3ac0c8bda9dbd0467e11a144c0f8Signed documents and nodes are made in each test session by the vendored CLI 0.2.0, under fresh seeds, and are not committed.
Deviations and premises that did not hold as written
notebook=. The signed document carries no file name. Only the stem is used, and the file is not read. The date iscreatedAt's, in UTC.trust_env=Falseonpublish's own client. httpx's default reads the whole environment, which holds the seed, when a client is built (measured). A caller who needs a proxy or a certificate bundle passesclient=.pinstill uses the default; that is out of scope here.%xmode Verbose) prints frame locals. The refusal path no longer holds the token as a local; tested red, then green.envelopeHashcompared. The same signed document published under two different explicit names becomes two entries. The default name carries the hash, so a rerun under the default name is a no-op. The README says so.typethe policy's top-leveltypedoes not name is refused, for the same reason as the role check: it would fail every later build.Flags, not fixed
SSLKEYLOGFILEeven withtrust_env=False.pinhas the same exposure.Model
Implemented by IMPL P2 on Opus 5.5 at high effort, as the impl agent file pins. Re-verified by ORCH NOTEBOOK-PUBLISH on Opus 5.5.
🤖 Generated with Claude Code