fix(routes): reject same-server MCP App resourceUri collisions with AB4829 and sweep orphaned Playground staging files - #430
Merged
ScriptedAlchemy merged 2 commits intoSep 3, 2026
Conversation
…B4829 and sweep orphaned Playground staging files Two App routes of one generated server declaring the same static config.resourceUri resolved first-wins with no diagnostic; the route-graph compiler now reports AB4829 naming both route files and the server, while the same URI across different servers still passes. The native Playground catalog publisher sweeps singly linked .<epoch>.stage-<pid>-<nonce> files that an exited publisher of another epoch left behind, bounded per publish and confined to its own directory, keeping a live winner's hard-linked staging entry, live publishers' files, and foreign entries.
🦋 Changeset detectedLatest commit: 4891697 The changes in this PR will be included in the next version bump. This PR includes changesets to release 1 package
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Owner
Author
|
@codex review |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
commit: |
ScriptedAlchemy
deleted the
fix/sweep-resource-uri-collision-stage-sweep
branch
September 3, 2026 17:12
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…g; renumber the retention diagnostic to AB4833 - a replayed admission (attempt receipt already present) resolves the next-event ceiling from the ledger's current advertisement instead of the notice's class, so a store reopened under a lowered ceiling never replays the authored secret - AB4829 was taken by #430 on main; the notices.retention diagnostic is AB4833 - inspect-state: the retention CLI assertions get their own bounded test
This was referenced Sep 3, 2026
Merged
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…g; renumber the retention diagnostic to AB4833 - a replayed admission (attempt receipt already present) resolves the next-event ceiling from the ledger's current advertisement instead of the notice's class, so a store reopened under a lowered ceiling never replays the authored secret - AB4829 was taken by #430 on main; the notices.retention diagnostic is AB4833 - inspect-state: the retention CLI assertions get their own bounded test
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…g; renumber the retention diagnostic to AB4833 - a replayed admission (attempt receipt already present) resolves the next-event ceiling from the ledger's current advertisement instead of the notice's class, so a store reopened under a lowered ceiling never replays the authored secret - AB4829 was taken by #430 on main; the notices.retention diagnostic is AB4833 - inspect-state: the retention CLI assertions get their own bounded test
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 3, 2026
…g; renumber the retention diagnostic to AB4833 - a replayed admission (attempt receipt already present) resolves the next-event ceiling from the ledger's current advertisement instead of the notice's class, so a store reopened under a lowered ceiling never replays the authored secret - AB4829 was taken by #430 on main; the notices.retention diagnostic is AB4833 - inspect-state: the retention CLI assertions get their own bounded test
ScriptedAlchemy
added a commit
that referenced
this pull request
Sep 4, 2026
…acceptance item 7 (#437) * feat(notices): redaction contract and retention policy close out #99 acceptance item 7 - sensitivity: 'public' | 'internal' | 'secret' on publish; per-route host ceilings (noticeDelivery.*.sensitivity with dated evidence) honoured by the ledger, inbox resource, event admission, and resources/updated signaller; withholdings recorded on the notice; internal content secret-pattern redacted on egress - notices.retention config (AB4829) -> ledger retain()/inspect(); terminal notices pruned on admitted events; journal compaction via AgentStateStore.compact()/inspect() (compact baseline record, kernel format 2, pruned-key bookkeeping) on both drivers - adapter capability rows + adapterRevision bumps; inspect --state and Workbench State panel show the resolved retention policy - mcp-server-runtime.d.ts spells GeneratedNoticeDeliveryBinding locally; packed assertion that no aliased/public declaration resolves through the notices subpath * test(workbench): bound the State-panel retention assertions by the browser timeout * chore(changeset): reference #437 * fix(workbench): admit noticeRetention in the strict route-manifest wire schema The dev server's manifest now carries the resolved notice retention policy; the browser client's strictObject rejected the unknown key and rendered the whole Routes catalog as unavailable (caught by examples-real.e2e). * fix(notices): disclose acknowledged content per route and record signaller withholdings - acknowledge() returns the notice as the acknowledging route may disclose it (next-event for events, the inbox ceiling otherwise); a withheld class comes back as a [REDACTED] placeholder, so an id from a redacted inbox unlocks nothing - the resources/updated signaller records its refusal durably through the new AgentNoticeLedger.recordWithholding() / 'withheld' event, once per subscription * fix(notices): disclose replayed deliveries against the current ceiling; renumber the retention diagnostic to AB4833 - a replayed admission (attempt receipt already present) resolves the next-event ceiling from the ledger's current advertisement instead of the notice's class, so a store reopened under a lowered ceiling never replays the authored secret - AB4829 was taken by #430 on main; the notices.retention diagnostic is AB4833 - inspect-state: the retention CLI assertions get their own bounded test * fix(notices): mask credential-keyed JSON whole; replay only the live baseline; match d.ts specifiers not prose - redactJson masks every string under a credential-shaped key (isSecretKey, pinned equal to the compiler's isCredentialKey) so { password: 'hunter2' } never leaks - replayJournal parses only the latest baseline at or below the target, so a compact/reset baseline written before a later migration is never fed to the current schema (conformance case on both drivers) - the packed d.ts assertion matches import specifiers, not doc comments * fix(notices): fail closed on unknown ceilings at construction; replay a retried admission instead of conflicting - routeSensitivityCeiling admits nothing for a ceiling outside the vocabulary and the ledger and signaller validate the advertisement when constructed (invalid-input) - a retry of the same event invocation whose recomputed admission differs replays the committed admission on idempotency-conflict instead of failing the request * fix(notices): mask JSON keys and keep deduped publish from leaking content A guessed dedupe key must not return another author's notice text, and credential-shaped JSON member names are redacted like any other prose. * feat(notices): redact notice content with flare-redact instead of hand-rolled patterns Per maintainer direction the secret pass is an npm library, not custom code: flare-redact@1.6.1 (MIT, zero deps, browser-safe root entry) becomes an exact-pinned runtime dependency; the ledger runs its default detectors and credential-shaped member names with every finding replaced whole by [REDACTED]. The hand-rolled pattern/key sources, isSecretKey, and the cross-package parity test are removed; credentials.ts and redactProbeText return to main. README records the evaluated libraries. * fix(notices): keep the byte bound on egress; fail closed on library limits; bounded-history wording Self-review findings: a redacted document that grew past the Agent Document byte bound is handed out as the placeholder; RedactionLimitError falls back to the mark instead of failing the inbox; the pinned detectors' assignment and OpenAI length limits are documented as contract; the generated notice page no longer calls the ledger append-only. * docs(notices): name noticeRedactionPlaceholder in the changeset and README Self-review second pass: the helper is exported from @agent-bundle/runtime/notices on purpose (embedders recognise the placeholder a withholding route hands out), so the release text and the README name it alongside the other new exports.
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.
Summary
Final-sweep lane B2, items 7 and 8.
7. Route graph —
AB4829for same-server MCP AppresourceUricollisions. Two App routes of one generated server declaring the same staticconfig.resourceUriresolved first-wins with no diagnostic (only theAB4812presence check existed).compileRouteGraphnow tracks App routes per generated server byresourceUriand reportsAB4829(error) for every additional claimant, naming both route files and the server; the route stays visible in the IR andvalidate/inspectfail the wayAB4812does. The same URI on App routes of different servers is not a collision (each generated server registers only its own Apps, per #42 / #418), and servers pinnedcustom/command/remoteare not checked (they ship no Apps).docs/diagnostics.md: newAB4829row (next free code in theAB48xxblock; the website diagnostics reference is generated from it).docs/entry-conventions.md,website/docs/{en,zh}/guide/authoring/mcp.mdx: the App-route paragraph now namesAB4812/AB4829.8. Native Playground publisher — bounded orphan staging sweep. A publisher that dies before its staging cleanup leaves
.<epoch>.stage-<pid>-<nonce>files behind. Entries still hard-linked to their epoch's sidecar are already recovered by the reader (#377/#408); entries that alias nothing (died beforelink(), lost theEEXISTrace, sidecar withdrawn later) were never swept. Each successful#persistSnapshotnow runs#sweepAbandonedStagingonce over its own catalog directory:nativePlaygroundStagingSweepLimits,@internal);stagingPublisherExited), andlstatreports a regular file withnlink === 1that does not alias<epoch>.json(#sidecarStillLinkedinode check);Changeset:
.changeset/sweep-resource-uri-collision-stage-sweep.md(agent-bundlepatch, mentionsAB4829).Evidence
packages/agent-bundle/tests/route-graph.test.ts— new test rejects two App routes of one server that declare the same resourceUri with AB4829, but not the same URI across servers: same server →['AB4829']with both files and"curator"in the message,sourcePath= second route; three claimants → twoAB4829; two servers sharing one URI → no diagnostics;customserver → no diagnostics;validatereportsAB4829andinspectisinvalid.packages/agent-bundle/tests/native-playground-service.test.ts— three new tests: (a) older-epoch orphan + orphaned guard removed on the next publish, same-epoch orphan left until a later epoch publishes; (b) live winner's hard-linked entry (inode equal to the sidecar,nlink2), live-pid file, five foreign look-alikes, a directory, a symlink to a file outside the directory, and a link aliasing an outside file are all kept, and the kept older epoch still reads back through the recovery path; (c) 20 orphans → 16 removed on the first publish, the rest on the second; 64 live-pid candidates ahead of an orphan exhaust the examination cap until they are gone.pnpm build:pnpm typecheck✅,pnpm lint✅ (0/0),pnpm test:projection✅,pnpm docs:site:build✅ (language parity ok).pnpm test:unitandpnpm test:route-unit: all tests pass except two 5 s timeouts unrelated to this change under load average ≈ 25 on the shared box (native-claude-contract.test.ts"fails closed when the candidate plugin…" — snapshots the real~/.claude;route-unit/lifecycle-replay.test.ts— passed on isolated re-run). Both touch neitherroutes/graph.tsnor the Playground service.Test plan
pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/route-graph.test.ts(50/50)pnpm exec rstest --config rstest.unit.config.ts packages/agent-bundle/tests/native-playground-service.test.ts(42/42)pnpm typecheck,pnpm lint,pnpm test:unit,pnpm test:route-unit,pnpm test:projection,pnpm docs:site:buildReview status
chatgpt-codex-connectoranswered "You have reached your Codex usage limits for code reviews" to both the automatic review and the explicit@codex reviewrequest (2026-09-03 16:50Z / 16:51Z). No review threads were opened.ad29670196e9f154c799eff39406ad87aba1f436,4891697b41d9b097a7eb0c4c32ca0964b3c2a876(head).