Repository navigation
PRODUCE-PY P2: the five notebook helpers (pin, show, badge_cell, comparison_cell, sidecar) - #2
Merged
Merged
Conversation
An autouse fixture replaces socket connect, connect_ex, bind and create_connection with functions that raise NetworkBlocked. Five tests drive a real attempt of each kind, including httpx's real transport. Red first: without the fixture, each attempt reached the OS (ConnectionRefusedError, or a bind that succeeded). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
The link and Markdown follow host-core's links.ts (116882a): the bundle URL percent-encoded as encodeURIComponent does, the destination in angle brackets. A golden captured from host-core's own functions and the vendored Node's encodeURIComponent both check it. The Jupyter form splices a markdown cell with a fixed id into the notebook's cells array, so every other byte stays as written; the Marimo form returns a mo.md cell's source. The cell names no hash and no time: a URL or fact that would put one in it is refused. marimo and nbformat join the dev dependency group, so the tests drive the real mo.md and nbformat's schema and writer. Red first (driven): a safe set of "-_.~" failed 3 tests; a hash row in the cell with the refusal disabled failed 3; a whole-file re-serialise failed 6; cell inserted last failed 8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
The caller's named values are written as Python literals (None, bool, int, finite float, str, and lists and str-keyed dicts of those), then the caller's one-line recompute expression and the delta loop, in the spec's shape. Anything else is refused. The cell is spliced in as the last cell with a fixed id, and a test signs the result inline through the real CLI with a throwaway seed. Red first (driven): the literal check removed failed 9 tests; the cell inserted first failed 2; a str written with str() failed 5; the recompute line ignored failed 9; a notebook left as Latin-1 made the CLI exit 2 in the signing test. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
pin(url) returns Pinned(content, entry). The entry follows the core-satellite example's queries[] retrieval shape: url, sha256 (of the body), bytes, httpStatus and fetchedAt, plus rowsUpdatedAt and datasetId for a URL with an open-data portal resource's shape, read from <origin>/api/views/<id> in a second request. httpx is imported inside the call; the client or transport and the clock are injectable, and the tests use httpx.MockTransport and a fixed time. save= writes the bytes and the entry beside them. pin.py stays the one module that imports hashlib: a new guard test asserts the unallowlisted scan finds exactly pin.py. Red first (driven): a digest over the URL failed 9 tests; a cached first-body digest failed 5; the metadata request dropped failed 7; hashlib imported in a second module failed 2 guard tests. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
sidecar(view, artifact) writes the view's fields as YAML beside the artifact, leaving out the inline package and a served bundle's trustRegistry, neither of which is a spec 8.8.1 field. Key order is the view's; yaml.safe_dump keeps Unicode and quotes any string YAML would re-type. The name keeps the artifact's extension (analysis.ipynb.record.yaml): the spec's <artifact-basename> does not say, and this form is the POSIX basename and cannot collide when two artifacts share a stem. The record fixtures for this and for show are captured through the wrapper under a throwaway seed (tests/fixtures/capture_records.py), an active record and a withdrawn one, each with a vcsRef and a role under extensions. Each is pinned by SHA-256, and the CLI's verify of each bundle must still equal the captured document. Red first (driven): lifecycleAttestations dropped failed 2 tests; the package kept failed 3; scalars written by hand failed 1, after the first run passed and showed the tricky strings were only nested, so the test now puts each at the top level too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
show(record, result=None) renders the type and the role (read from extensions at role_path, labelled as the signer's assertion), the abbreviated signer, "Signed with a self-certifying key" for a self_certified key, the display name and binding tier as the signer's own description, the first 12 hex of the envelope hash, createdAt, the vcsRef marked "asserted; not fetched", the status with its reason or successor, one line per check, and one sentence saying it does not say the analysis is correct. The labels follow hub ADR-0030 section 10; no line carries a check-mark. Every record string is HTML-escaped. Without a result it runs verify, and renders a failed verdict too. Jupyter gets a Shown with _repr_html_; marimo=True returns mo.Html of the same HTML, importing marimo only then. A stand-in module and the real marimo both test it. Committed renderings of the two captured records pin the bytes. Red first (driven): escaping removed failed 18 tests; a check-marked "verified signer" failed 5; an object id in the output failed 6; the withdrawal reason dropped failed 3; the spec 9.3 sentence dropped failed 4; the vcsRef mark dropped failed 4; #14 shown as ok failed 5; the Marimo form returning the Jupyter object failed 2. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
…m the wheel The README gains a Notebook helpers section: each helper's call and output, the sidecar's name and why it keeps the extension, the role keys show reads, and which libraries are imported only inside a call. The wheel smoke check now runs the five helpers offline in the fresh environment, so a missing runtime dependency (httpx, PyYAML) fails the wheel job. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u Signed-off-by: Nathan Storey <npstorey@users.noreply.github.com>
Owner
Author
|
GO — CAT PLAN (the program seat), bound to head Read from GitHub and disk, 2026-10-04:
The owner merges with |
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.
Phase P2 of PRODUCE-PY (npstorey/typedstandards#135): the notebook helpers of
typedstandards, all tested offline.Branch:
ts135/p2-helpers· Head:84cc751adc4d86f7c88deb391e51a079145532f1· Base:mainat104448bSize:
git diff --numstat main...HEAD: 36 files, +4254 −6.Blast zone: only this branch, in this repository. The diff touches
src/typedstandards/(four new private modules,pin.py, and the exports in__init__.py),tests/,scripts/smoke_wheel.py,README.md,CHANGELOG.md,CLAUDE.md,pyproject.tomlanduv.lock(the dev group gainsmarimoandnbformat). Nothing in typedstandards, the host template, the hub or the core-satellite example changed._cli.py,_commands.py,_node.pyanderrors.pyare unchanged. The CI workflow is unchanged, and so are its job names.What it adds
pin(url)fetches once and returnsPinned(content, entry). The entry is aqueries[]retrieval entry in the core-satellite example's shape:url,sha256of the body,bytes,httpStatusandfetchedAt(UTC). A URL shaped like an open-data portal resource also getsrowsUpdatedAtanddatasetId, read from<origin>/api/views/<id>.httpxis imported inside the call. The client or transport and the clock can be injected.save=writes the bytes and the entry.pin.pyis still the only module that importshashlib, and a new guard test checks exactly that.badge_cell(bundle_url, *, capture_method, notebook=None, marimo=False)writes the verifier badge in host-core's Markdown form, with?url=encoded asencodeURIComponentdoes, above a table of the host and the capture method. Withnotebookit becomes cell 0 (idtypedstandards-badge). Withmarimo=Trueit returns the source of amo.md(...)cell. The cell holds no hash and no time; input that would put one there is refused.comparison_cell(notebook, values, *, recompute, captured_at)appends the spec §8.7.4 cell as the last cell (idtypedstandards-comparison). Values must be literals; anything else is refused.sidecar(view, artifact)writes<artifact file name>.record.yamlfromview's output. It leaves outpackageandtrustRegistry, keeps the view's key order, and every value loads back as its JSON value.show(record, result=None, *, role_path=("role",), marimo=False)renders a record and itsverify --jsonresult as HTML:Shown._repr_html_for Jupyter,mo.Htmlfor Marimo. Its labels follow hub ADR-0030 §10. It uses no check-marks and carries one sentence saying it does not say the analysis is correct. Every string taken from the record is HTML-escaped.The notebook helpers splice the new cell into the
cellsarray, so every other byte of the file stays as written: key order, indentation, line endings, number spelling, escapes. Importingtypedstandardsloads none ofhttpx,yaml,marimo,IPythonornbformat.Acceptance
Each red below was driven locally by mutating the implementation and running the criterion's tests.
pinhashlibimported in a second module: 2 guard tests failedtest_pin.py,test_guards.py)badge_cell-_.~instead ofencodeURIComponent's: 3 failed; a hash row with the refusal disabled: 3 failed; the refusal alone disabled: 2 failed; whole-file re-serialisation: 6 failed; cell inserted last: 8 failedcomparison_cellstr()for strings: 5 failed; recompute line ignored: 9 failed; notebook left as Latin-1: the real CLI exited 2 in the signing testsidecarlifecycleAttestationsdropped: 2 failed;packagekept: 3 failed; scalars written by hand: the first run passed (the test's tricky strings were only nested), the test was extended to top-level strings, then 1 failedtest_fixtures.py)showok: 5 failed; the Marimo form returning the Jupyter object: 2 failedConnectionRefusedError; a bind that succeeded): 5 failedChecks run locally on the head
From a clean clone at
84cc751,UV_PYTHON=<py> uv sync --locked, thenuv run pytest:Also run:
uv run ruff check .: all checks passed.uv run ruff format --check .: 37 files already formatted.uv build: 232 vendored files in the wheel. The wheel was installed into a fresh environment, andscripts/smoke_wheel.pyran there with a throwaway seed and printedsmoke check passed. The smoke check now also runs the five helpers from the installed wheel.gitleaks git --log-opts="main..HEAD" --no-banner: 8 commits, no leaks found.These runs were on macOS (arm64). CI has not run on this head yet: it runs once the branch is pushed.
Commits
Eight commits. Each is signed (
%G?=G) and carries oneSigned-off-by: Nathan Storey <npstorey@users.noreply.github.com>, equal to the author email.Fixtures
tests/fixtures/README.mdrecords each new fixture's source, command, date and SHA-256, and a test pins each hash.badge-golden.json: output of host-core's ownbuildVerifyHrefandbuildEmbedMarkdown(typedstandards116882a), run by Node 24.21.0.record-*.json: two records signed through the wrapper with CLI 0.2.0 under a throwaway seed, bytests/fixtures/capture_records.py.test_fixtures.pychecks that the CLI'sverify --jsonof each bundle still equals the captured document.show-*.html:show's committed rendering of those two records. The test asserts byte equality.Choices to note
analysis.ipynb.record.yaml, with the extension kept. Spec §8.8.3's<artifact-basename>is ambiguous on this point. The extension is kept because this is the POSIX basename, because it cannot collide when two artifacts share a stem, and because the artifact's name is recoverable from it. This is carried to the close record as a spec item.showreads the role fromextensions["role"]by default (role_pathchanges this). It renders one line per check that verify-core reports, numbered as in §9.2. #13 has no field of its own inchecks.Model: Claude Opus 5.5 (
claude-opus-5-5).🤖 Generated with Claude Code
https://claude.ai/code/session_01Ru4PYga7Zf8HANgotZKs4u