You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[finding] packages/mcp ships a kernel-reaching Page with no canonical-envelope gate — now discoverable, still unaudited #12269
Filed unassigned by the os-dev seat implementing #11576 (PR #12266). Observation-class; recording the residual, not claiming it.
What
Measuring #11576's residual turned up CONNECT_AGENT_PAGE (packages/mcp/src/connect-ui.ts) — a raw object literal reaching the kernel through manifest.register(CONNECT_AGENT_UI_BUNDLE) (packages/mcp/src/plugin.ts:505), authored with no : Page annotation.
PR #12266 annotates it, so export-shape discovery can now see it. That closes discoverability. It does not make the page audited: packages/mcp carries no canonical-expression-envelope gate, so nothing checks that its page serves the canonical { dialect, source } envelope at every ExpressionInputSchema position.
The two packages that ship raw-literal pages and DO carry that gate:
packages/mcp is now the third package shipping a page and the only one without the gate.
Why it is worth recording rather than fixing on the #11576 PR
CONNECT_AGENT_PAGE authors zero expression keys today, so a gate over it would pass trivially. That is precisely the argument #11480's own header makes for having one anyway:
They author ZERO expression keys today, which is exactly why the gate is worth having: the hazard is the NEXT predicate added to one of them, which would ship bare with every authoring-time signal green.
The page body is a single mcp:connect-agent widget plus a page:header, and both are live SDUI surfaces that can grow a visibleWhen at any time.
It was deliberately left out of PR #12266's scope: that card's declared file surface is the repo-wide gate, explicitly not the per-package canonical-envelope gates.
Possible closures
A. Give packages/mcp the thin gate, copying the cloud-connection shape (shared detector from @objectstack/lint, the @objectstack/lint devDependency, and the anchored vitest source alias). Mechanical; the pattern is two packages deep.
No recommendation recorded. Note that the "copy the gate into the next package" step is the same per-package residual #11576's option C names, so a third hand-copy is also evidence for hoisting the population discovery rather than repeating it.
Clause ②: falsify, don't build on, this seat's assumptions.
Released from a hold, and the hold's condition is now met. This card was held behind PR #12317, not behind anything on the card itself. The reason: its closure A is "copy the cloud-connection shape", and until an hour ago both sibling gates carried the naive two-regex stripComments that #9367 retired from six gates — so a third hand-copy would have copied the defect forward a third time. #12317 merged at c71978ff96 (1 parent, squash) and converted both onto the shared mask. Verified on that ref, not assumed:
So the annotation #12266 added is in place (discoverable), and packages/mcp is still the third page-shipping package and the only one with no gate. Both halves hold.
Declared file surface: packages/mcp/ — the new gate test, its package.json devDependency on @objectstack/lint, and its vitest source alias; plus the scripts/cross-package-test-inputs.mjs and turbo.json declarations the cross-package import forces, exactly as #12317 had to make them.
⛔ Not scripts/check-parse-guard.mjs. ⛔ Not a hoist of the population discovery — that is the card's own closing note and is #12307's subject, ungraded, not this card's.
{
"issue": 12269,
"status": "done",
"branch": "claude/issue-12269-mcp-canonical-envelope-gate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12345",
"premise_still_valid": true,
"summary": "Closure A taken: packages/mcp gets the thin canonical-expression-envelope gate, copied from the cloud-connection shape on a base at c71978ff96 (both siblings re-read there first -- both already import maskComments from the shared mask, the private stripper gone from both, so this third hand-copy carries the repaired shape). cloud-connection was the one copied, and it is the better one rather than merely the nearer: platform-objects discovers its audited pages through a src/pages barrel that packages/mcp does not have, and cloud-connection's list is checked against the source scan in BOTH directions. Population is discovered by export shape over src/, never by a hard-coded name, with a floor that reds on an empty scan. Assumption 2b was swept, not assumed (every `export const ... : Page =`, every regions:/pages: literal, every *_UI_BUNDLE, every .register( call site): CONNECT_AGENT_PAGE really is the only page, one bundle, one register at plugin.ts:505. Assumption 2e holds -- @objectstack/lint exports both detector symbols and depends on formula/sdui-parser/spec, so the devDependency creates no cycle. ONE PM assumption was FALSIFIED (1e/2c held): mcp:connect-agent has no ComponentPropsMap row, so door 3 is blind to its props bag and the gate had to carry a standing exemption -- the same state cloud-connection's two widgets were in between #11480 and #11575. Contained the way the pre-#11575 sibling did (exemption asserted EXACTLY, exempted bag pinned EMPTY, plus a non-vacuity pin so a stale exemption cannot sit there covering nothing) and filed as #12344 rather than fixed in scope, since giving that type its row is #11575's piece of work and outside this card's declared surface. The cross-package maskComments import is declared in scripts/cross-package-test-inputs.mjs with a matching @objectstack/mcp#test entry in turbo.json; the vitest alias was APPENDED to this package's existing alias array (it already aliased @objectstack/metadata-core), both entries anchored. No changeset: the diff touches no published packages/mcp source (dist-only publish; the change is a test, a devDependencies line, a vitest config, a roster entry and a turbo task), which is #12317's precedent exactly -- skip-changeset applied and read back as [size/m, skip-changeset].",
"tests": "All on final commit 0efd5f8bce (gate union derived there with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`; the FIRST derivation printed STALE TREE -- 6 commits behind, 6 files it derives from changed incl. lint.yml and the script itself -- so origin/main was merged at c48d46d70a and the union re-derived clean, which ADDED check:bash32-floor). Every heavy run through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe. Verdict lines quoted from each gate's own output: @objectstack/mcp test -> 'Test Files 23 passed (23)' / 'Tests 255 passed (255)' (baseline was 22/242; this file adds 13). @objectstack/mcp typecheck -> exit 0, no diagnostics. check:cross-package-test-inputs -> 'OK: 18 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' check:type-check-coverage -> 'check-type-check-coverage: OK -- 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger'. check: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-shape -> 'OK -- 34 page entries across 2188 sources ... all reach the kernel through a discoverable declaration'. check:published-files, check:engine-double-contract, check:where-matcher, check:query-options-erasure ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'), check:type-source-resolution ('93 tsc program(s) across 77 packages'), check:slot-lookup, check:parse-guard ('167 scripts/ file(s)'), check:entry-guard ('168 scripts/ file(s)'), check:agent-test-spelling ('0 violations -- 384 file(s)'), check:pnpm-filter-targets, check:cli-command-ids, check:bash32-floor, check:nul-bytes ('scanned 6834 text file(s) ... no raw ASCII control bytes') -- all exit 0. pnpm lint run WHOLE-REPO (not narrowed, 98s under the lock) -> exit 0, no findings. NOT MEASURED, left to CI: check:type-check-debt --re-measure (needs the full workspace closure built) and the docs-audit / osv-exemptions / changeset-fixed families. ABLATION, two legs, each mutation confirmed ON DISK by counting the injected text AND the anchor text (not an editor exit code), each restored under `trap ... EXIT INT TERM` and proved byte-identical with git hash-object. Leg A (bare predicate planted in the shipped page literal; anchor 1->0, injected 0->1): 3 failed | 10 passed (13), the verdict test 'CONNECT_AGENT_PAGE authors NO bare expression string' among them plus both downgrade controls reding on their pristine re-audits (correct -- the export was no longer pristine); restored to 42ada6a679623486368d7e86341c5e311a02ea91, identical. Leg B (scan regex made to match nothing AND audited list emptied = the gate finds no pages at all): 4 failed | 2 passed (6), floor assert firing by name -- 'AssertionError: expected 0 to be greater than or equal to 1'. Note the 6-vs-13 count: an empty population registers no it.each cases, so without the floor assert an empty scan would silently drop 7 tests and report green, which is exactly the reading the floor separates. REBUILD LEG -- does one apply? Not to the mutations: vitest resolves ./connect-ui.js to src/ and @objectstack/lint through the new anchored alias to lint/src/index.ts, so neither mutated file passes through a dist/. One thing in the measured graph does -- the detector imports ComponentPropsMap/PageComponentSchema/PageSchema from @objectstack/spec/ui, which resolves through exports to packages/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 no packages/spec source, so it is current for every number above. HONEST CORRECTION: both ablation legs were FIRST run with a repo-relative path handed to a vitest whose cwd is the package. Both exited 1 on 'No test files found' -- a false red that reads exactly like a successful ablation. That run is NOT MEASURED; only the re-run with the package-relative filter is reported here.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #12344: `mcp:connect-agent` has no `ComponentPropsMap` row, so door 3 of the canonical-envelope gate cannot read its props bag; this PR contains it with an exact-set exemption + an empty-bag pin + a non-vacuity pin rather than covering it (the #11575 treatment is the fix and is out of this card's declared surface). Filed unassigned and unlabelled for PM triage."
]
}
Channel note for the PM. The skip-changeset label could NOT go through the additive REST endpoint from this seat: direct curl to api.github.com returns 403 — "GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization." — for both reads and writes. The declared fallback was used (read → union → group write via MCP issue_write), and the readback confirms ["size/m", "skip-changeset"], so the size-labeler's label survived. The same 403 forced the duplicate pre-check for #12344 onto one targeted MCP search_issues call (0 hits) instead of the REST list endpoints — a deviation from the ⛔, taken only because the prescribed channel is measurably unavailable in this session, and recorded here rather than passed over.
⚠️ This comment replaces the one directly above it for scanning purposes only — same report, nothing changed. The HTML-comment marker on that one was eaten by the body sanitizer after it landed (verified by reading it back: the stored body now begins with the code fence, the marker gone), which makes it invisible to a marker scan. There is no comment-update path from this seat — the MCP surface has no edit-comment tool and direct REST PATCH returns the same 403 described below — so the report is re-posted here leading with the marker as literal text, which survives.
{
"issue": 12269,
"status": "done",
"branch": "claude/issue-12269-mcp-canonical-envelope-gate",
"pr": "https://github.com/objectstack-ai/objectstack/pull/12345",
"premise_still_valid": true,
"summary": "Closure A taken: packages/mcp gets the thin canonical-expression-envelope gate, copied from the cloud-connection shape on a base at c71978ff96 (both siblings re-read there first -- both already import maskComments from the shared mask, the private stripper gone from both, so this third hand-copy carries the repaired shape). cloud-connection was the one copied, and it is the better one rather than merely the nearer: platform-objects discovers its audited pages through a src/pages barrel that packages/mcp does not have, and cloud-connection's list is checked against the source scan in BOTH directions. Population is discovered by export shape over src/, never by a hard-coded name, with a floor that reds on an empty scan. Assumption 2b was swept, not assumed (every `export const ... : Page =`, every regions:/pages: literal, every *_UI_BUNDLE, every .register( call site): CONNECT_AGENT_PAGE really is the only page, one bundle, one register at plugin.ts:505. Assumption 2e holds -- @objectstack/lint exports both detector symbols and depends on formula/sdui-parser/spec, so the devDependency creates no cycle. ONE PM assumption was FALSIFIED (1e/2c held): mcp:connect-agent has no ComponentPropsMap row, so door 3 is blind to its props bag and the gate had to carry a standing exemption -- the same state cloud-connection's two widgets were in between #11480 and #11575. Contained the way the pre-#11575 sibling did (exemption asserted EXACTLY, exempted bag pinned EMPTY, plus a non-vacuity pin so a stale exemption cannot sit there covering nothing) and filed as #12344 rather than fixed in scope, since giving that type its row is #11575's piece of work and outside this card's declared surface. The cross-package maskComments import is declared in scripts/cross-package-test-inputs.mjs with a matching @objectstack/mcp#test entry in turbo.json; the vitest alias was APPENDED to this package's existing alias array (it already aliased @objectstack/metadata-core), both entries anchored. No changeset: the diff touches no published packages/mcp source (dist-only publish; the change is a test, a devDependencies line, a vitest config, a roster entry and a turbo task), which is #12317's precedent exactly -- skip-changeset applied and read back as [size/m, skip-changeset].",
"tests": "All on final commit 0efd5f8bce (gate union derived there with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`; the FIRST derivation printed STALE TREE -- 6 commits behind, 6 files it derives from changed incl. lint.yml and the script itself -- so origin/main was merged at c48d46d70a and the union re-derived clean, which ADDED check:bash32-floor). Every heavy run through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe. Verdict lines quoted from each gate's own output: @objectstack/mcp test -> 'Test Files 23 passed (23)' / 'Tests 255 passed (255)' (baseline was 22/242; this file adds 13). @objectstack/mcp typecheck -> exit 0, no diagnostics. check:cross-package-test-inputs -> 'OK: 18 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' check:type-check-coverage -> 'check-type-check-coverage: OK -- 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger'. check: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-shape -> 'OK -- 34 page entries across 2188 sources ... all reach the kernel through a discoverable declaration'. check:published-files, check:engine-double-contract, check:where-matcher, check:query-options-erasure ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'), check:type-source-resolution ('93 tsc program(s) across 77 packages'), check:slot-lookup, check:parse-guard ('167 scripts/ file(s)'), check:entry-guard ('168 scripts/ file(s)'), check:agent-test-spelling ('0 violations -- 384 file(s)'), check:pnpm-filter-targets, check:cli-command-ids, check:bash32-floor, check:nul-bytes ('scanned 6834 text file(s) ... no raw ASCII control bytes') -- all exit 0. pnpm lint run WHOLE-REPO (not narrowed, 98s under the lock) -> exit 0, no findings. NOT MEASURED, left to CI: check:type-check-debt --re-measure (needs the full workspace closure built) and the docs-audit / osv-exemptions / changeset-fixed families. ABLATION, two legs, each mutation confirmed ON DISK by counting the injected text AND the anchor text (not an editor exit code), each restored under `trap ... EXIT INT TERM` and proved byte-identical with git hash-object. Leg A (bare predicate planted in the shipped page literal; anchor 1->0, injected 0->1): 3 failed | 10 passed (13), the verdict test 'CONNECT_AGENT_PAGE authors NO bare expression string' among them plus both downgrade controls reding on their pristine re-audits (correct -- the export was no longer pristine); restored to 42ada6a679623486368d7e86341c5e311a02ea91, identical. Leg B (scan regex made to match nothing AND audited list emptied = the gate finds no pages at all): 4 failed | 2 passed (6), floor assert firing by name -- 'AssertionError: expected 0 to be greater than or equal to 1'. Note the 6-vs-13 count: an empty population registers no it.each cases, so without the floor assert an empty scan would silently drop 7 tests and report green, which is exactly the reading the floor separates. REBUILD LEG -- does one apply? Not to the mutations: vitest resolves ./connect-ui.js to src/ and @objectstack/lint through the new anchored alias to lint/src/index.ts, so neither mutated file passes through a dist/. One thing in the measured graph does -- the detector imports ComponentPropsMap/PageComponentSchema/PageSchema from @objectstack/spec/ui, which resolves through exports to packages/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 no packages/spec source, so it is current for every number above. HONEST CORRECTION: both ablation legs were FIRST run with a repo-relative path handed to a vitest whose cwd is the package. Both exited 1 on 'No test files found' -- a false red that reads exactly like a successful ablation. That run is NOT MEASURED; only the re-run with the package-relative filter is reported here.",
"open_questions": [],
"out_of_scope_findings": [
"filed as #12344: `mcp:connect-agent` has no `ComponentPropsMap` row, so door 3 of the canonical-envelope gate cannot read its props bag; this PR contains it with an exact-set exemption + an empty-bag pin + a non-vacuity pin rather than covering it (the #11575 treatment is the fix and is out of this card's declared surface). Filed unassigned and unlabelled for PM triage."
]
}
Channel note for the PM (also on the comment above). Direct curl to api.github.com returns 403 from this seat — "GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization." — for both reads and writes. Three consequences, each a declared deviation rather than a silent one: the skip-changeset label went through the read → union → group-write fallback instead of the additive endpoint (readback: ["size/m", "skip-changeset"], so the size-labeler's label survived); the duplicate pre-check before filing #12344 used one targeted MCP search_issues call (0 hits) instead of the REST list endpoints; and this marker repair had to be a new comment rather than a PATCH.
Activity
yinlianghui commentedon Aug 25, 2026
Claim —
domain:devx @ objectstackseat (#6023), sessionsession_01UjM2ia8Av1v5NqfqQEQmC6.claude/issue-12269-mcp-canonical-envelope-gate../objectstack-12269(dedicated)Released from a hold, and the hold's condition is now met. This card was held behind PR #12317, not behind anything on the card itself. The reason: its closure A is "copy the
cloud-connectionshape", and until an hour ago both sibling gates carried the naive two-regexstripCommentsthat #9367 retired from six gates — so a third hand-copy would have copied the defect forward a third time. #12317 merged atc71978ff96(1 parent, squash) and converted both onto the shared mask. Verified on that ref, not assumed:The private
function stripCommentsis gone from both. A copy made today copies the repaired shape.The card's own premise, re-verified on the same merged ref:
So the annotation #12266 added is in place (discoverable), and
packages/mcpis still the third page-shipping package and the only one with no gate. Both halves hold.Declared file surface:
packages/mcp/— the new gate test, itspackage.jsondevDependency on@objectstack/lint, and its vitest source alias; plus thescripts/cross-package-test-inputs.mjsandturbo.jsondeclarations the cross-package import forces, exactly as #12317 had to make them.⛔ Not
scripts/check-parse-guard.mjs. ⛔ Not a hoist of the population discovery — that is the card's own closing note and is #12307's subject, ungraded, not this card's.Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
{ "issue": 12269, "status": "done", "branch": "claude/issue-12269-mcp-canonical-envelope-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/12345", "premise_still_valid": true, "summary": "Closure A taken: packages/mcp gets the thin canonical-expression-envelope gate, copied from the cloud-connection shape on a base at c71978ff96 (both siblings re-read there first -- both already import maskComments from the shared mask, the private stripper gone from both, so this third hand-copy carries the repaired shape). cloud-connection was the one copied, and it is the better one rather than merely the nearer: platform-objects discovers its audited pages through a src/pages barrel that packages/mcp does not have, and cloud-connection's list is checked against the source scan in BOTH directions. Population is discovered by export shape over src/, never by a hard-coded name, with a floor that reds on an empty scan. Assumption 2b was swept, not assumed (every `export const ... : Page =`, every regions:/pages: literal, every *_UI_BUNDLE, every .register( call site): CONNECT_AGENT_PAGE really is the only page, one bundle, one register at plugin.ts:505. Assumption 2e holds -- @objectstack/lint exports both detector symbols and depends on formula/sdui-parser/spec, so the devDependency creates no cycle. ONE PM assumption was FALSIFIED (1e/2c held): mcp:connect-agent has no ComponentPropsMap row, so door 3 is blind to its props bag and the gate had to carry a standing exemption -- the same state cloud-connection's two widgets were in between #11480 and #11575. Contained the way the pre-#11575 sibling did (exemption asserted EXACTLY, exempted bag pinned EMPTY, plus a non-vacuity pin so a stale exemption cannot sit there covering nothing) and filed as #12344 rather than fixed in scope, since giving that type its row is #11575's piece of work and outside this card's declared surface. The cross-package maskComments import is declared in scripts/cross-package-test-inputs.mjs with a matching @objectstack/mcp#test entry in turbo.json; the vitest alias was APPENDED to this package's existing alias array (it already aliased @objectstack/metadata-core), both entries anchored. No changeset: the diff touches no published packages/mcp source (dist-only publish; the change is a test, a devDependencies line, a vitest config, a roster entry and a turbo task), which is #12317's precedent exactly -- skip-changeset applied and read back as [size/m, skip-changeset].", "tests": "All on final commit 0efd5f8bce (gate union derived there with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`; the FIRST derivation printed STALE TREE -- 6 commits behind, 6 files it derives from changed incl. lint.yml and the script itself -- so origin/main was merged at c48d46d70a and the union re-derived clean, which ADDED check:bash32-floor). Every heavy run through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe. Verdict lines quoted from each gate's own output: @objectstack/mcp test -> 'Test Files 23 passed (23)' / 'Tests 255 passed (255)' (baseline was 22/242; this file adds 13). @objectstack/mcp typecheck -> exit 0, no diagnostics. check:cross-package-test-inputs -> 'OK: 18 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' check:type-check-coverage -> 'check-type-check-coverage: OK -- 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger'. check: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-shape -> 'OK -- 34 page entries across 2188 sources ... all reach the kernel through a discoverable declaration'. check:published-files, check:engine-double-contract, check:where-matcher, check:query-options-erasure ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'), check:type-source-resolution ('93 tsc program(s) across 77 packages'), check:slot-lookup, check:parse-guard ('167 scripts/ file(s)'), check:entry-guard ('168 scripts/ file(s)'), check:agent-test-spelling ('0 violations -- 384 file(s)'), check:pnpm-filter-targets, check:cli-command-ids, check:bash32-floor, check:nul-bytes ('scanned 6834 text file(s) ... no raw ASCII control bytes') -- all exit 0. pnpm lint run WHOLE-REPO (not narrowed, 98s under the lock) -> exit 0, no findings. NOT MEASURED, left to CI: check:type-check-debt --re-measure (needs the full workspace closure built) and the docs-audit / osv-exemptions / changeset-fixed families. ABLATION, two legs, each mutation confirmed ON DISK by counting the injected text AND the anchor text (not an editor exit code), each restored under `trap ... EXIT INT TERM` and proved byte-identical with git hash-object. Leg A (bare predicate planted in the shipped page literal; anchor 1->0, injected 0->1): 3 failed | 10 passed (13), the verdict test 'CONNECT_AGENT_PAGE authors NO bare expression string' among them plus both downgrade controls reding on their pristine re-audits (correct -- the export was no longer pristine); restored to 42ada6a679623486368d7e86341c5e311a02ea91, identical. Leg B (scan regex made to match nothing AND audited list emptied = the gate finds no pages at all): 4 failed | 2 passed (6), floor assert firing by name -- 'AssertionError: expected 0 to be greater than or equal to 1'. Note the 6-vs-13 count: an empty population registers no it.each cases, so without the floor assert an empty scan would silently drop 7 tests and report green, which is exactly the reading the floor separates. REBUILD LEG -- does one apply? Not to the mutations: vitest resolves ./connect-ui.js to src/ and @objectstack/lint through the new anchored alias to lint/src/index.ts, so neither mutated file passes through a dist/. One thing in the measured graph does -- the detector imports ComponentPropsMap/PageComponentSchema/PageSchema from @objectstack/spec/ui, which resolves through exports to packages/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 no packages/spec source, so it is current for every number above. HONEST CORRECTION: both ablation legs were FIRST run with a repo-relative path handed to a vitest whose cwd is the package. Both exited 1 on 'No test files found' -- a false red that reads exactly like a successful ablation. That run is NOT MEASURED; only the re-run with the package-relative filter is reported here.", "open_questions": [], "out_of_scope_findings": [ "filed as #12344: `mcp:connect-agent` has no `ComponentPropsMap` row, so door 3 of the canonical-envelope gate cannot read its props bag; this PR contains it with an exact-set exemption + an empty-bag pin + a non-vacuity pin rather than covering it (the #11575 treatment is the fix and is out of this card's declared surface). Filed unassigned and unlabelled for PM triage." ] }Channel note for the PM. The
skip-changesetlabel could NOT go through the additive REST endpoint from this seat: directcurltoapi.github.comreturns 403 —"GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization."— for both reads and writes. The declared fallback was used (read → union → group write via MCPissue_write), and the readback confirms["size/m", "skip-changeset"], so the size-labeler's label survived. The same 403 forced the duplicate pre-check for #12344 onto one targeted MCPsearch_issuescall (0 hits) instead of the REST list endpoints — a deviation from the ⛔, taken only because the prescribed channel is measurably unavailable in this session, and recorded here rather than passed over.Generated by Claude Code
Generated by Claude Code
yinlianghui commentedon Aug 25, 2026
os-dev-report
PATCHreturns the same 403 described below — so the report is re-posted here leading with the marker as literal text, which survives.{ "issue": 12269, "status": "done", "branch": "claude/issue-12269-mcp-canonical-envelope-gate", "pr": "https://github.com/objectstack-ai/objectstack/pull/12345", "premise_still_valid": true, "summary": "Closure A taken: packages/mcp gets the thin canonical-expression-envelope gate, copied from the cloud-connection shape on a base at c71978ff96 (both siblings re-read there first -- both already import maskComments from the shared mask, the private stripper gone from both, so this third hand-copy carries the repaired shape). cloud-connection was the one copied, and it is the better one rather than merely the nearer: platform-objects discovers its audited pages through a src/pages barrel that packages/mcp does not have, and cloud-connection's list is checked against the source scan in BOTH directions. Population is discovered by export shape over src/, never by a hard-coded name, with a floor that reds on an empty scan. Assumption 2b was swept, not assumed (every `export const ... : Page =`, every regions:/pages: literal, every *_UI_BUNDLE, every .register( call site): CONNECT_AGENT_PAGE really is the only page, one bundle, one register at plugin.ts:505. Assumption 2e holds -- @objectstack/lint exports both detector symbols and depends on formula/sdui-parser/spec, so the devDependency creates no cycle. ONE PM assumption was FALSIFIED (1e/2c held): mcp:connect-agent has no ComponentPropsMap row, so door 3 is blind to its props bag and the gate had to carry a standing exemption -- the same state cloud-connection's two widgets were in between #11480 and #11575. Contained the way the pre-#11575 sibling did (exemption asserted EXACTLY, exempted bag pinned EMPTY, plus a non-vacuity pin so a stale exemption cannot sit there covering nothing) and filed as #12344 rather than fixed in scope, since giving that type its row is #11575's piece of work and outside this card's declared surface. The cross-package maskComments import is declared in scripts/cross-package-test-inputs.mjs with a matching @objectstack/mcp#test entry in turbo.json; the vitest alias was APPENDED to this package's existing alias array (it already aliased @objectstack/metadata-core), both entries anchored. No changeset: the diff touches no published packages/mcp source (dist-only publish; the change is a test, a devDependencies line, a vitest config, a roster entry and a turbo task), which is #12317's precedent exactly -- skip-changeset applied and read back as [size/m, skip-changeset].", "tests": "All on final commit 0efd5f8bce (gate union derived there with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack`; the FIRST derivation printed STALE TREE -- 6 commits behind, 6 files it derives from changed incl. lint.yml and the script itself -- so origin/main was merged at c48d46d70a and the union re-derived clean, which ADDED check:bash32-floor). Every heavy run through scripts/pm/os-verify-lock.sh; exit codes captured before any pipe. Verdict lines quoted from each gate's own output: @objectstack/mcp test -> 'Test Files 23 passed (23)' / 'Tests 255 passed (255)' (baseline was 22/242; this file adds 13). @objectstack/mcp typecheck -> exit 0, no diagnostics. check:cross-package-test-inputs -> 'OK: 18 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' check:type-check-coverage -> 'check-type-check-coverage: OK -- 65/78 workspace packages type-checked (plus the root), 13 in the DEBT ledger'. check: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-shape -> 'OK -- 34 page entries across 2188 sources ... all reach the kernel through a discoverable declaration'. check:published-files, check:engine-double-contract, check:where-matcher, check:query-options-erasure ('ratchet holds: 67 unswept non-test site(s) in 17 file(s), none new'), check:type-source-resolution ('93 tsc program(s) across 77 packages'), check:slot-lookup, check:parse-guard ('167 scripts/ file(s)'), check:entry-guard ('168 scripts/ file(s)'), check:agent-test-spelling ('0 violations -- 384 file(s)'), check:pnpm-filter-targets, check:cli-command-ids, check:bash32-floor, check:nul-bytes ('scanned 6834 text file(s) ... no raw ASCII control bytes') -- all exit 0. pnpm lint run WHOLE-REPO (not narrowed, 98s under the lock) -> exit 0, no findings. NOT MEASURED, left to CI: check:type-check-debt --re-measure (needs the full workspace closure built) and the docs-audit / osv-exemptions / changeset-fixed families. ABLATION, two legs, each mutation confirmed ON DISK by counting the injected text AND the anchor text (not an editor exit code), each restored under `trap ... EXIT INT TERM` and proved byte-identical with git hash-object. Leg A (bare predicate planted in the shipped page literal; anchor 1->0, injected 0->1): 3 failed | 10 passed (13), the verdict test 'CONNECT_AGENT_PAGE authors NO bare expression string' among them plus both downgrade controls reding on their pristine re-audits (correct -- the export was no longer pristine); restored to 42ada6a679623486368d7e86341c5e311a02ea91, identical. Leg B (scan regex made to match nothing AND audited list emptied = the gate finds no pages at all): 4 failed | 2 passed (6), floor assert firing by name -- 'AssertionError: expected 0 to be greater than or equal to 1'. Note the 6-vs-13 count: an empty population registers no it.each cases, so without the floor assert an empty scan would silently drop 7 tests and report green, which is exactly the reading the floor separates. REBUILD LEG -- does one apply? Not to the mutations: vitest resolves ./connect-ui.js to src/ and @objectstack/lint through the new anchored alias to lint/src/index.ts, so neither mutated file passes through a dist/. One thing in the measured graph does -- the detector imports ComponentPropsMap/PageComponentSchema/PageSchema from @objectstack/spec/ui, which resolves through exports to packages/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 no packages/spec source, so it is current for every number above. HONEST CORRECTION: both ablation legs were FIRST run with a repo-relative path handed to a vitest whose cwd is the package. Both exited 1 on 'No test files found' -- a false red that reads exactly like a successful ablation. That run is NOT MEASURED; only the re-run with the package-relative filter is reported here.", "open_questions": [], "out_of_scope_findings": [ "filed as #12344: `mcp:connect-agent` has no `ComponentPropsMap` row, so door 3 of the canonical-envelope gate cannot read its props bag; this PR contains it with an exact-set exemption + an empty-bag pin + a non-vacuity pin rather than covering it (the #11575 treatment is the fix and is out of this card's declared surface). Filed unassigned and unlabelled for PM triage." ] }Channel note for the PM (also on the comment above). Direct
curltoapi.github.comreturns 403 from this seat —"GitHub access is not enabled for this session. An org admin must connect the Claude GitHub App for this organization."— for both reads and writes. Three consequences, each a declared deviation rather than a silent one: theskip-changesetlabel went through the read → union → group-write fallback instead of the additive endpoint (readback:["size/m", "skip-changeset"], so the size-labeler's label survived); the duplicate pre-check before filing #12344 used one targeted MCPsearch_issuescall (0 hits) instead of the REST list endpoints; and this marker repair had to be a new comment rather than aPATCH.Generated by Claude Code
Generated by Claude Code
fix(spec): os migrate meta guidance for the ui-* and plugin-* migrati…