Repository navigation
feat(migration): plan entry v2 issuer (backfills, base-read capability, commit identity) - #117
Draft
AgentRelayBot wants to merge 2 commits into
Draft
AgentRelayBot wants to merge 2 commits into
AgentRelayBot wants to merge 2 commits into
Conversation
…, commit identity) PROTOTYPE ONLY. Built against 96BullSessions #86 @ e74be4c0, which is on contract-review HOLD pending amendments. Not final; do not merge. - Plan: optional `backfills` of v2 inputs {v:2, kind:"backfill", key, org_id, workspace_id, source_user_id, session, format:"ndjson", chunks:[{start_offset, bytes, sha256, records}]}. The issuer adds grant_id and derives backfill_sha256 (v1 hash chain, epoch 0) and raw_bytes; client-supplied derived fields are refused. records is mandatory, offsets contiguous from 0, keys unique across both lists, pairs and limits span both lists. - Digest: absent and empty `backfills` give the byte-identical v1 plan and digest. With a backfill: sha256(canonical({v:2, grant_id, exported_at, manifests, backfills})). verifyStoredMigrationGrantPlan recomputes both shapes; a v2 row with no backfills or a v1 row carrying backfills is refused. Old binaries refuse v2 rows (fail closed). - Mint: upload is exactly {key} (v1, unchanged), {key, kind: "backfill_base_read"} or {key, base}; each form looks up only its own list; tenancy and digests come only from the stored entry. - Base-read capability follows the review direction: its own subject, audience (<grant origin>/v1/pipeline/manifests/base), scope (rth:migration-base:read), tokenClass, TTL (<=30 s) and jti namespace (mbr_), audited as resource migration-base-read. Single-use consume is the ingest's job. - Base: exact 7 keys and safe-integer bounds (revision 1..2^53-2, chunk_count <= 2^53-1-4096, epoch <= 2^32-2), strings 1..512, owner not migration:*, content_sha256 lowercase hex. - Audit: upload_kind, entry_sha256 and base_revision only. - SDK verifier and @relayauth/types accept exactly the three shapes. Tests: vendored #79 @ c91f4ac0 vectors reproduced (entry, backfill and plan digests, commit identity, work key); v1 digest a6682d4d unchanged for absent and []; negatives from #86 section 8; stored-plan tamper; base bounds; mint form/list/pair/selector matrix with audited refusals; verifier audience matrix both ways. Validation (local): turbo typecheck 11/11, build 9/9, npm test 16/16 (server 805/805, sdk 171/171, ai 70, core 19, migrate 18, relayauth 12, types 1, root 8); server e2e 40 pass, 93 skipped. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Important Draft PR not reviewedDraft PRs are not automatically reviewed by default.
To automatically review draft PRs, update your CodeRabbit configuration: reviews:
auto_review:
drafts: true
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. |
Aligns the #117 prototype with 96BullSessions #86, the spec of record (main @ f67f741; reviewed and PASSED at d4e0e040, same blobs): docs/contracts/relayhistory-export-plan-entry-v2-issuer.md and its vectors (blob 59a8bde). - Base-read capability: sub agent_relayhistory_migration_base_read, tokenClass migration_base_read, scope rth:migration_base_read, aud <origin>/v1/pipeline/manifests/base, TTL <= 30 s, jti mbr_, identity in meta.read with meta.upload absent. Issuance is default-off behind RELAYAUTH_ENABLE_MIGRATION_BASE_READ (403 migration_base_read_disabled; chart migrationAssertions.baseReadEnabled). - Legacy conditional (section 1): absent or [] backfills is the v1 path byte for byte, including v1's 1..1000 manifests rule; a non-empty list is v2 and may have manifests: [] (ruling 2: >= 1 combined entry); null or non-array backfills is invalid_plan. - Exact bounds (section 2): start_offset + bytes <= MAX checked without a float sum; records mandatory 0..MAX. - Rulings: v1 mint and refusal audit rows unchanged (no upload_kind or entry_sha256 on v1); v2 rows omit the raw nonce and correlate by tokenId; v2 wrong-org is a generic invalid_plan externally with detail backfill_org_mismatch only in the internal audit; v1 keeps plan_org_mismatch. - SDK verifier: meta.upload is v1 or v2 commit only; meta.read is the base-read only; both together are refused. - Tests use #86's issuer vectors (legacy.absent, legacy.empty, v2_plan, backfill_entry_*, base_read_meta_read, commit_meta_upload) in place of the #79 c91f4ac0 copy. Review: adversarial (token-class confusion both ways at the verifier, selector/list/pair/tenant override refusals, bounds at MAX and MAX + 1, flag exactness, no raw key/nonce/base content in audit) and contextual (v1 vectors and #111 tests unchanged, docs, chart, env wiring). Fixed a timestamp-ordering flake in a new test found by repeated runs. Validation (local; hosted CI only triggers for base main): turbo typecheck --force 11/11, build --force 9/9, test --force 16/16 (0 cached): server 816/816, sdk 171/171, ai 70, core 19, migrate 18, relayauth 12, types 1; root tests 8/8; server e2e 40 pass, 93 skipped. prettier 3.6.2 --no-config --check: new files pass; the warned files are exactly those already non-conforming at 2adf414. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
AgentRelayBot
pushed a commit
that referenced
this pull request
Oct 31, 2026
…gerprint Ruling 5 on 96BullSessions #86 (spec of record main @ f67f741): the idempotency request fingerprint must not depend on any generated grant_id, including those in plan entry v2 `backfills` (relayauth #117). migrationGrantFingerprintPlan now strips the plan's grant_id and each entry's in both `manifests` and `backfills`. A v1 plan has no `backfills` key, so v1 fingerprints are byte-identical to before (pinned by a test against the previous construction). Validation (local): turbo typecheck --force 11/11; turbo test --force 16/16 (0 cached): server 754/754, sdk 170/170, ai 70, core 19, migrate 18, relayauth 12, types 1; root tests 8/8. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.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.
Plan entry v2 issuer (backfill epochs) for the RelayHistory to 96Bulls migration. Stacked on #111 (
feat/migration-grant-pair-binding@2adf414a). Draft; do not merge.Spec of record: 96BullSessions main @
f67f741, which is #86 merged. That commit holdsdocs/contracts/relayhistory-export-plan-entry-v2-issuer.md(blobb4bde2e6) and.vectors.json(blob59a8bde9). Contract review PASSED atd4e0e040, and its blobs are identical. The vectors are vendored byte for byte atpackages/server/src/__tests__/fixtures/relayhistory-export-plan-entry-v2-issuer.vectors.json.Behaviour
Legacy conditional (§1). An absent
backfillsorbackfills: []takes the v1 path byte for byte: the same stored plan and digest, v1's rule of 1 to 1000 manifests, and unchanged audit rows. A non-empty list stores av: 2plan whose digest covers{v: 2, grant_id, exported_at, manifests, backfills}. In that casemanifestsmay be[], but the plan needs at least one entry in total (ruling 2). Abackfillsthat isnullor not an array isinvalid_plan.v2 entries (§2). The input has exactly the spec's keys. RelayAuth derives
grant_id,backfill_sha256andraw_bytes, and refuses a client value for any of them.recordsis mandatory, offsets run on from 0, andstart_offset + bytes≤ MAX is checked exactly. A key may appear only once across both lists. A v2 entry in the wrong org gets a genericinvalid_plan, and the precisebackfill_org_mismatchappears only in the internal auditdetail. v1 keepsplan_org_mismatch(ruling 3).Two token classes (§3).
{key, base}{key, kind: "backfill_base_read"}agent_relayhistory_migrationagent_relayhistory_migration_base_readmigration_assertionmigration_base_read["rth:sync"]["rth:migration_base_read"][<origin>][<origin>/v1/pipeline/manifests/base]meta.upload(§5)meta.read(§4);meta.uploadabsenttok_mbr_Both forms look the key up only in
backfills. The pair, live-identity and revocation checks are v1's, and tenancy comes only from the stored entry. Sending bothkindandbase, or any otheruploadkey, gets 400invalid_upload_selector. Base-read issuance is default-off:RELAYAUTH_ENABLE_MIGRATION_BASE_READ(chartmigrationAssertions.baseReadEnabled) must be exactlytrue, or the mint gets 403migration_base_read_disabled(§9).basebounds (§5). Exactly 7 keys.revisionis 1 to MAX−1,chunk_countis 0 to MAX−4096, andepochis 0 to 4294967294.version,ownerandwriterare 1 to 512 characters, andownermay not start withmigration:.content_sha256is lowercase hex.Audit (rulings 1 and 4). v1 mint and refusal rows are exactly fix(migration): bind grants to canonical upload pairs #111's. v2 rows carry
upload_kind,uploadKeyHash,entry_sha256and, for a commit,base_revision. They omit the raw nonce and are correlated bytokenId, which is the jti. A v2 refusal row carriesupload_kindand the key hash. No row holds a raw key, token or base content.SDK verifier and types.
meta.uploadmust be the v1 or v2 commit shape, andmeta.readmust be the base-read shape. A token carrying both is refused.Ruling 5 (#112)
#112's request fingerprint must strip
grant_idfrom backfill entries. That change is made on #112's own branch as a stacked follow-up; see #112.Tests (new or changed; all pass)
migration-plan-v2.test.ts: 67, including subtests.legacy.absentandlegacy.empty(stored JSON and digesta6682d4d…).v2_plan(f29e6cc4…),backfill_entry_stored,backfill_entry_sha256,base_read_meta_read(including canonical JSON) andcommit_meta_upload.backfills, no entries at all, the wrong-org audit reason, and MAX and MAX+1 bounds.basebound cases, and a backfill-only plan.migration-assertion.test.ts, v2 section: 6 tests, 22 including subtests.true, never overriding the main switch).verify.test.ts: 1 test that accepts the commit shape inmeta.uploadand the base-read shape inmeta.read, and refuses 19 confused or malformed shapes.Validation
Local, all uncached:
turbo typecheck --forceturbo build --forceturbo test --force(16/16, 0 cached)test:e2ehelm templateRELAYAUTH_ENABLE_MIGRATION_BASE_READonly whenbaseReadEnabled=truePrettier (
npx --yes prettier@3.6.2 --no-config --check <the 16 added or modified files>) exits 1. The files it warns about are exactly the ones that already fail the same check at2adf414a.deployment.yamlis a Go template, so it can't be parsed as YAML. The new and newly touched files, the fixture,values.yamlandlib/migration-grants.ts, pass. I did not reformat existing files.Hosted CI:
ci.ymltriggers only for PRs targetingmainand has noworkflow_dispatch. CI therefore runs on the temporary CI-only draft #118, from this exact head. #118 will be closed once CI reports.Nothing was merged, deployed or touched live, and no keys or credentials were created or used.
Independent SOC2 re-review pending.
🤖 Generated with Claude Code
Agent Relay sessions
claudesession719b5435· opened viagh pr create· last active 2026-10-31