Uh oh!
There was an error while loading. Please reload this page.
test(mcp): gate CONNECT_AGENT_PAGE on canonical expression envelopes - #12345
Conversation
packages/mcp is the third package shipping a raw-literal Page that reaches the kernel through its own manifest bundle, and the only one without the canonical-expression-envelope gate the other two carry. CONNECT_AGENT_PAGE authors zero expression keys today, which is the argument for the gate rather than against it: the hazard is the NEXT predicate added to it, which would ship bare with every authoring-time signal green. Thin gate copying the cloud-connection shape: the shared detector from @objectstack/lint, the @objectstack/lint devDependency, and an anchored vitest source alias appended to this package's existing alias array so the gate judges lint's SOURCE rather than a possibly-stale dist. Population is discovered by export shape over src/ through the shared maskComments, never by a hard-coded name, with a floor so a scan that reads nothing cannot pass as a clean page, and a phantom-comment pin so comment-shaped text cannot delete a page from it. Door 3 has one recorded exemption: mcp:connect-agent is a console-registered widget with no ComponentPropsMap row, the same standing-exemption shape cloud-connection's two widgets were in before #11575 gave them rows. The exemption is asserted exactly, its bag is pinned empty, and a non-vacuity pin reds if the type ever stops appearing on the page. The cross-package maskComments import is declared in scripts/cross-package-test-inputs.mjs and hashed by a matching @objectstack/mcp#test entry in turbo.json. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
…ssue-12269-mcp-canonical-envelope-gate
📓 Docs Drift Check
What this run could not see
Coarse fallback — 12 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
yinlianghui
commented
Aug 25, 2026
ACCEPT — PM review of #12269. The claim I asked you to falsify, re-measured hereZone 2b said the card names one page and told you a gate auditing one page in a package shipping two is worse than no gate. Checked independently on Exactly one page, one bundle, one register call — your sweep holds. Positive control, because a single hit could just as easily be a broken pattern: The same pattern finds pages where they exist, so the single hit in ⭐ And the control says something your prose only implies: Leg B is the best thing in this PR
That is precisely the trap Zone 2d named — a gate whose population scan returns zero passes exactly like a gate over a clean page, and both greens are green. You did not just add the floor assert, you measured what it buys: seven tests that would have vanished without a single red anywhere. The test-count delta is the evidence; the assertion message alone would not have been. ⭐ The Ⓘ note — a false RED, and the second self-caught instrument failure today
This is the mirror of the failure this lane keeps hitting from the other side. An ablation is supposed to produce a red, so a red from the harness being wrong is indistinguishable from success — and unlike a false green, nothing about it feels wrong to the author. Catching it required reading why it was red rather than that it was red. Reporting only the re-run is right. (For the record: #11363's dev caught the same class in the opposite direction the same hour — a grep bracket expression that could not match multibyte marks, reporting "no cases went red" for two legs that did. Two devs, two self-caught instrument failures, opposite signs.) The rest
Landing conditions
Generated by Claude Code |
Uh oh!
There was an error while loading. Please reload this page.
Fixes#12269
packages/mcpwas the third package shipping a raw-literalPagethat reaches the kernel through its own manifest bundle, and the only one of the three without the canonical-expression-envelope gate. This adds the thin gate, taking closure A from the card.CONNECT_AGENT_PAGEauthors zero expression keys today. That is the argument for the gate rather than against it, and #11480's own header states it:The page body is one
mcp:connect-agentwidget plus apage:header, both live SDUI surfaces that can grow avisibleWhenat any time.Third hand-copy, recorded as such
This is the third per-package copy of the same shape (
platform-objects#11255,cloud-connection#11480, this one). That count is live evidence for hoisting the population discovery into one shared helper instead of repeating it — #11576's option C, and the subject of #12307, which stays that card's question rather than this one's. Recorded here so the count is inherited rather than the habit.The copy was held behind #12317 deliberately: until that merged, both sibling gates carried the naive two-regex comment stripper that #9367 retired from six gates, and a third hand-copy would have carried the defect forward a third time. Both siblings were read on this branch's base before anything was copied — both now import
maskCommentsfrom the shared mask, and the private stripper is gone from both.Which sibling was copied, and why
cloud-connection's.platform-objectsdiscovers its audited pages through a page barrel (import * as pageExports from './index.js'), whichpackages/mcpdoes not have — its page lives in a*-ui.tsfile.cloud-connection's shape (explicit audited list, checked against the source scan in both directions) is also strictly the better one: it reds both when a declared page is unaudited and when an audited page falls out of the: Pagescan. Itsimport.meta.urlseed is kept rather thanplatform-objects'__dirname, which that file uses only to dodge a TS1470 its own package config forces.The population is discovered by export shape over
src/, never by a hard-coded name.The page population was swept, not assumed
The card names one page. Swept
packages/mcp/srcfor everyexport const … : Page =, everyregions:andpages:literal, every*_UI_BUNDLE, and every.register(call site. Result: exactly one page (CONNECT_AGENT_PAGE), one bundle (CONNECT_AGENT_UI_BUNDLE), one register call (plugin.ts:505). A gate that audits one page in a package shipping two would be worse than no gate, so this was checked rather than taken from the card.Door 3 carries one recorded exemption
page:headerhas aComponentPropsMaprow;mcp:connect-agentdoes not — it is a console-registered widget, so door 3 has no schema to read itspropertieswith. This is the same standing-exemption statecloud-connection's two widgets were in between #11480 and #11575, and giving this type its row is that same piece of work, outside this card's declared surface. Filed as #12344.Contained rather than covered, three pins together:
The gate is proven to fail — both ways
A gate whose population scan silently returns zero pages passes exactly like a gate over a clean page, and those two greens mean opposite things. Both were ablated, each mutation confirmed on disk by counting the injected text and the anchor text (an editor's exit code is not evidence), each restored under a
trap … EXIT INT TERMand proved byte-identical withgit hash-object.Leg A — a bare predicate planted in the shipped page literal. Anchor count 1 to 0, injected count 0 to 1. Result: 3 failed | 10 passed (13), the verdict test among them —
(the two downgrade controls red on their pristine re-audits, correctly: the export is no longer pristine.) Restored:
42ada6a679623486368d7e86341c5e311a02ea91, identical to before.Leg B — the population emptied. Scan regex made to match nothing and the audited list emptied, i.e. the gate finds no pages at all. Result: 4 failed | 2 passed (6), the floor assert firing by name:
Worth reading the counts: 6 tests instead of 13, because an empty population registers no
it.eachcases. Without the floor assert an empty scan would silently drop 7 tests and report green — which is exactly the reading the floor exists to separate.Rebuild leg — does one apply here? Not to the mutations. Both ablated files are read from source: vitest resolves
./connect-ui.jsinside the package tosrc/, and@objectstack/lintthrough the new anchored alias tolint/src/index.ts, so neither mutation passes through adist/. One thing in the measured graph does: the detector importsComponentPropsMap/PageComponentSchema/PageSchemafrom@objectstack/spec/ui, which resolves throughexportstopackages/spec/dist. That closure was built in this worktree before any measurement (pnpm --workspace-concurrency=2 --filter '@objectstack/mcp^...' build, and again on the merged head), and this diff touches nopackages/specsource, so that dist is current for every number above.Cross-package inputs declared, not worked around
The
maskCommentsimport escapes the package, soscripts/cross-package-test-inputs.mjsgains an@objectstack/mcpentry andturbo.jsona matching@objectstack/mcp#testtask hashing the same two paths — the.d.mtsalongside the.mjs, because it is what givesmaskCommentsits type and so is an input to this package's typecheck verdict too. Same declaration both siblings carry.The vitest alias is appended to this package's existing alias array rather than replacing it —
packages/mcpalready aliased@objectstack/metadata-core, and both entries stay anchored so neither swallows a subpath.No changeset — reasoning, not assumption
The diff touches no published
packages/mcpsource.packages/mcppublishesdistonly (files: ["dist", "README.md", "CHANGELOG.md"]), and the change is a test file, adevDependenciesline, a vitest config, a lint-roster entry and a turbo task. Nothing a consumer can observe changes; nodistoutput moves. #12317 — the same kind of diff over these same gates — shipped no changeset either.skip-changesetlabel applied.Verification
Gate union derived at the final commit
0efd5f8bcewithnode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack. The first derivation printedSTALE TREE(6 commits behind, 6 files it derives from changed,lint.ymland the script itself among them), soorigin/mainwas merged atc48d46d70aand the union re-derived clean — which addedcheck:bash32-floorto the matched list.All run on
0efd5f8bce, exit codes captured before any pipe:@objectstack/mcpsuiteTest Files 23 passed (23)·Tests 255 passed (255)@objectstack/mcptypecheckcheck:cross-package-test-inputsOK: 18 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.check:type-check-coveragecheck-type-check-coverage: OK — 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger …check:test-source-aliascheck-test-source-alias OK — 72 packages with tests scanned; 61 registered as still resolving a workspace dep through dist/; 45 published subpath(s) resolved through every alias table.check:page-declaration-shapeOK — 34 page entries across 2188 sources … all reach the kernel through a discoverable declarationcheck:published-files69 publishable package(s) of 78 workspace member(s) declare a files whitelist …check:engine-double-contractcheck:where-matcherOK self-test: separates conjoining, early-returning, combinator-blind and refusingcheck:query-options-erasureratchet holds: 67 unswept non-test site(s) in 17 file(s), none newcheck:type-source-resolutionOK — 93 tsc program(s) across 77 packages scannedcheck:slot-lookupcheck:parse-guard167 scripts/ file(s) — every TypeScript parse goes through ts-parse.mjs.check:entry-guard168 scripts/ file(s) — every entry guard goes through invoked-as.mjscheck:agent-test-spelling0 violations — 384 file(s) · 3934 bare separator token(s) · 1167 launcher-rooted run(s)check:pnpm-filter-targets136/173 --filter occurrence(s) across 28 file(s) resolve against 78 workspace package(s)check:cli-command-ids276 command-id literal(s) across 99 file(s) … all resolve to a real command pathcheck:bash32-floor20 tracked shell file(s) … name no bash 4+ constructcheck:nul-bytesOK (scanned 6834 text file(s) … no raw ASCII control bytes)pnpm lint(whole repo, not narrowed)Not run locally, left to CI:
check:type-check-debt --re-measure(needs the full workspace closure built), and thedocs-audit,osv-exemptionsandchangeset-fixedfamilies that need CI context. Every heavy run went throughscripts/pm/os-verify-lock.sh.Ⓘ One derivation note worth carrying: two ablation legs were first run with a repo-relative path handed to a vitest invoked with the package as cwd. Both exited 1 on
No test files found— a false red that reads exactly like a successful ablation. They were re-run with the package-relative filter, and only the re-run is reported above.Generated by Claude Code