Repository navigation
Conversation
Signed-off-by: Tim Nunamaker <tnunamak@gmail.com> Assisted-by: AI
Contributor
Schema Health Check — All ClearAll 73 scopes have consistent local schema files. |
tnunamak
marked this pull request as draft
September 10, 2026 21:16
This was referenced Sep 10, 2026
Three mutations of the retention code survived the existing suite, so the tests did not pin the behavior they claimed. Every corrupt-bytes test ran the complete_raw path, where the whole-source source_digest_mismatch backstop catches corruption on its own. That backstop is disabled once omitted_bytes > 0, so the chunk-level blob comparison was never the thing that failed a test. Two new cases build a complete_with_exclusions capture and corrupt a retained blob: one on the durable-readback path with no sourceBytes, one against frozen bytes. Both assert accepted === false and a blob_integrity_mismatch issue. Nothing asserted that a required key is required or that an unknown key is rejected, so both .optional() and a strictObject to loose relaxation passed unnoticed. The schema tests now drop each required root, chunk and blob_ref key and assert rejection, and reject a smuggled unknown key in the root, chunk, blob_ref, encoded path, manifest page reference, policy and observation. Matching verifier cases prove the same at the behavior level: a record with a smuggled key, or a root missing session identity, does not verify. Verified by re-applying each mutation and confirming the suite fails: blob comparison to if (false) fails both exclusion-path cases; blob_ref optional plus loose fails the strictness cases; optional session identity fails the required-key cases. Production code is unchanged. Assisted-by: AI Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
The drop test listed 13 of the root schema's 23 required keys and 5 of the chunk schema's 15. The omitted 10 on each side are exactly the shared provenance block, including classifier_version, connector_instance_id and source_root_id. Making any of those optional left the suite green. Derive the key set from each fixture instead of listing it. The round-trip test already proves both fixtures parse back to themselves with no defaults applied, so a fixture's own key set is exactly its schema's required set, and a key added to either schema arrives here with the fixture that has to carry it. Assert the expected counts so a fixture that stops covering the schema fails loudly rather than silently testing less. Confirmed load-bearing: marking classifier_version, connector_instance_id or source_root_id optional in retention-schemas.ts now fails with the diagnostic naming that key. All three passed before this change. Retention suites 32/32 and package verify pass. Signed-off-by: Tim Nunamaker <tnunamak@gmail.com> Assisted-by: AI
tnunamak
force-pushed
the
feat/retention-manifest-schemas
branch
from
September 13, 2026 02:04
e13adf9 to
8bf0782
Compare
…my-0913 Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
`retention-schemas.ts` hardcoded an enum of fourteen artifact classes, eight of them connector-specific (`claude.tool_result`, `codex.reasoning`, and six more). Adding a connector meant editing a shared schema file, which is the arrangement `b076a2d4` removed for default stream sets by making each `collector-definition.ts` the single place its own streams are written. The taxonomy now splits the same way. `RETENTION_BASE_ARTIFACT_CLASSES` keeps only what means the same thing for every connector (`images`, `documents`, `unknown`, `unclassified`). Each connector declares its own classes in its own `collector-definition.ts`, and the collector registry assembles them into `CONNECTOR_RETENTION_ARTIFACT_CLASSES`, next to the existing `definitionStreams` helper and for the same reason. Two classes were connector-specific despite unprefixed names and moved to their owners: `shell_snapshots` is a Codex stream (`connectors/codex/index.ts:234`), and the `tool-results/*.txt` sidecars are a Claude Code on-disk structure (`connectors/claude_code/index.ts:475`). They are now `codex.shell_snapshots` and `claude.tool_result_sidecars`, so the connector prefix is uniform and a new test pins it: an unprefixed or unregistered declaration fails. The enum stays closed, which is load-bearing. A free-form class string would let an omission entry cite a rule no owner policy could have contained, defeating the verifier's `omission_policy_mismatch` check. Closing the enum over declared classes keeps that check meaningful while letting each connector own its entries. The inferred TypeScript type is still the exact literal union, now assembled rather than written out. `RETENTION_STREAMS` in `retention-runtime.ts` was flagged as the same kind of hardcoded table but is not one, and is left as data with a comment saying why: its six names are the retention wire format's own streams, each bound to the schema that validates it, identical for every connector. Adding a connector adds artifact classes, never a retention stream, so there is no per-connector copy there to drift. Adding a connector now touches that connector's own directory and the registry list that registers it, and no shared schema. Verified by adding a throwaway connector declaring two classes: both became citable in policy rules and omission entries with `retention-schemas.ts` unmodified, undeclared classes stayed rejected, and the prefix test caught the connector while it was declared but not yet registered. 36 pass, 0 fail, up from 32; the four added tests cover the base staying connector-agnostic, the prefix invariant, admissibility with closure, and each connector contributing its own entries. Both existing guarantees still fail under their sabotages: replacing the blob byte-length and SHA-256 comparison in `retention-verifier.ts` with `if (false)` fails with `same-length corrupt blob bytes must not be accepted`, and marking `classifier_version`, `connector_instance_id` or `source_root_id` optional each fails with `chunk must require <key>`. Noted, not introduced: disabling only the first of that comparison's four clauses is not caught, because the other three still reject the corrupt blob. That gap predates this change. Assisted-by: AI Signed-off-by: Tim Nunamaker <tnunamak@gmail.com>
Contributor
Author
|
Superseded. Byte retention landed as a content-addressed spool with a four-field record reference (data-connect #106, data-connectors #100), which needs none of this contract. The chunk/coverage/omission model here is only required for owner-directed omission, which is not scheduled; the design reasoning is retained for when sparse archives are specified. Assisted-by: AI |
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.
The Claude Code and Codex connectors store session files lossily. Large fields keep only a bounded prefix plus the true byte length: 500 characters for attachments, a bounded prefix for tool results, and
GUARD_PREFIX_BYTESof 64 KB for file reads (connectors/codex/index.ts:135). The bound exists because one tool result can reach hundreds of megabytes, and reading it whole would make collector memory scale with session size. The bytes themselves are never stored, so the durable record cannot reconstruct the session.This is the first change toward a byte archive that keeps full content without loading it into memory. It adds the contract only: schemas, dormant stream declarations, a compatibility check, and a verifier over supplied synthetic records and bytes. The PR that wires
checkRetentionCompatibilityin will be the one that enables capture.No collector code path reaches the new modules.
checkRetentionCompatibilityand the verifier are called only from their own tests, andRETENTION_STREAMSis declared but never read by a collector, so nothing captures a file, uploads a blob or writes a cursor.retention-runtime.test.tsreadsmanifests/claude_code.jsonandmanifests/codex.jsonand asserts neither declares a retention stream, which are the two connectors this contract is written for.Verify, from
packages/polyfill-connectors:36 pass, 0 fail. Note that
scripts/run-tests.mjsignores argv and globs a fixed set, sonpm test -- <filter>runs the whole suite instead.Artifact classes are declared per connector
retention-schemas.tspreviously hardcoded an enum of fourteen artifact classes, eight of them connector-specific, so adding a connector meant editing a shared schema.b076a2d4had already settled this question the other way for default stream sets, making eachcollector-definition.tsthe single place its own streams are written. This branch predated that commit and is now reconciled with it.RETENTION_BASE_ARTIFACT_CLASSESkeeps only what means the same thing for every connector:images,documents,unknown,unclassified. Each connector declares its own classes in its owncollector-definition.ts, andcollector-registry.tsassembles them intoCONNECTOR_RETENTION_ARTIFACT_CLASSESbeside the existingdefinitionStreamshelper.Two classes were connector-specific despite unprefixed names.
shell_snapshots(connectors/codex/index.ts:234) and thetool-results/*.txtsidecars (connectors/claude_code/index.ts:475) becomecodex.shell_snapshotsandclaude.tool_result_sidecars. A test pins the prefix in both directions: an unprefixed class fails, and so does one whose prefix names no registered connector.The enum stays closed, which is load-bearing. A free-form class string would let an omission entry cite a rule no owner policy could have contained, defeating
omission_policy_mismatch. Closing over declared classes keeps that check meaningful while letting each connector own its entries, and the inferred type is still the exact literal union, now assembled rather than written out.Checked by adding a throwaway connector declaring two classes: both became citable in policy rules and omission entries with
retention-schemas.tsunmodified, undeclared classes stayed rejected, and the prefix test caught the connector while it was declared but not yet registered.RETENTION_STREAMSwas flagged in this PR as the same kind of hardcoded table. It is not — its six names are the format's own streams, identical for every connector, so there is no per-connector copy to drift. Left as data with a comment.Tests that would otherwise pass on broken code
The corrupt-bytes tests all ran the
complete_rawpath, where the whole-sourcesource_digest_mismatchbackstop catches corruption on its own — so disabling the chunk-level blob comparison entirely left the suite green. The added test verifies acomplete_with_exclusionscapture withomitted_bytes > 0and nosourceBytes, which is the durable-readback case where that backstop is off, and corrupts a retained blob both by flipping a byte and by changing its length. Replacing the byte-length and SHA-256 comparison inretention-verifier.ts:94withif (false)now fails withsame-length corrupt blob bytes must not be accepted.required root and chunk keys cannot be dropped or padded with unknown keysnow drops every required key rather than a chosen subset. It previously listed 13 of the root schema's 23 required keys and 5 of the chunk schema's 15, omitting the shared provenance block on both sides, so markingclassifier_version,connector_instance_idorsource_root_idoptional left the suite green.It now derives each key set with
Object.keyson the fixture instead of listing names.v1 root, page, chunk and receipt fixtures round-trip without defaults(retention-schemas.test.ts:89) parses each fixture and asserts it equals itself, so no field is defaulted or dropped and a fixture's key set is its schema's required set. The asserted counts, 23 root and 15 chunk, fail if a fixture stops covering its schema. Marking any of those three provenance keys optional inretention-schemas.tsnow fails withchunk must require <key>.Both sabotages were re-run after the taxonomy change and still fail.
Not verified: making
blob_ref.sha256andsize_bytesoptional without also looseningstrictObjectis behaviorally inert and no test detects it. The chunk-level.refinealready requires those fields to equal their sibling chunk values, and those siblings are required, so validation still fails through the refine. Detecting that relaxation on its own would mean changing the refine, not the field modifier.Also not caught: disabling only the first of the blob comparison's four clauses, because the other three still reject the corrupt blob. That gap predates this change and is not introduced by it.
Assisted-by: AI