Skip to content

Org-overridable metadata (view, dashboard) is accepted with a 200 state:'active' receipt but served by no read door #9454

Description

@os-zhuang

Extracted from Tier-2B (#9453, view) and Tier-2C (#9467, dashboard) of the QA wave #9296. Subject sha e4e5c6e3c608b1b807c83a0d5b734f213eb1a1dd, stock showcase.

Scope widened 2026-08-18. Filed first against view; the next round found the identical shape on dashboard, independently, with its own controls. Two metadata types, one mechanism — kept as one card because splitting it would invite two fixes for one defect and let the general case escape.

The defect

A runtime PUT of an org-overridable metadata type answers 200 with a receipt reporting state: 'active' plus a version and sequence number — the shape that tells an author "this is live". Nothing then serves it:

door result
direct GET of the item 404
scoped listing (?object=<object>) unchanged — the new item is absent
unfiltered listing absent
browser view: nothing renders · dashboard: "Dashboard Not Found"

Draft → publish behaves identically. The platform reports success in the same breath as not delivering it — declared ≠ enforced, in the direction hardest for an author to notice, because the write path says everything worked.

The acceptance half is correct — the read side is the bug

view and dashboard are deliberately in the org-overridable set (allowOrgOverride: true, packages/spec/src/kernel/metadata-plugin.zod.ts), which is exactly why a runtime PUT against a read-only showcase package is accepted rather than refused — unlike object, field, hook and seed, which carry allowOrgOverride: false and are correctly locked. So the 200 is right. The anomaly is that nothing reads the overlay back.

The org-overridable set is view, dashboard, report, translation, email_template. Two of the five are now measured broken by the same shape; the other three are untested and should be assumed affected until shown otherwise — that is the general case the fix has to address.

Evidence — the controls are what make this stand up

view (Tier-2B): 4 saves · 3 names · 2 objects · 2 independent boots.
dashboard (Tier-2C): 5 saves · 4 names · 2 boots, one of them a fresh DB with a different org id.

Controls measured on both, and they are the reason this is a defect rather than an observation:

  • Positive control — an item of the same type that ships with the package reads back normally through the same doors on the same boot. The read path is not simply broken.
  • Org-scoping confound ruled out — one sys_organization row, and it is the session's active org; dashboard additionally re-measured against a different org id on a fresh DB.
  • Storage control (dashboard) — the write is observable where it lands.

That combination separates "the write silently no-ops" from "the reader looks in the wrong place".

What a fix has to settle

Where the overlay write lands and why every read door misses it — at the overlay-resolution layer, not per type, or the remaining three types stay broken.

Pin it with a round-trip test: author at runtime, then read back through the direct GET and the scoped listing, for each org-overridable type. A test asserting only the 200 receipt passes today and proves nothing — that is precisely the gap this defect lives in, and why two rounds of a browser sweep were needed to find something the write path calls a success.

Left unlabelled for domain:* and unlevelled — routing and priority are the triage seat's call. Filed P1 by its checklist items, though "the platform reports active for metadata it will not serve, across a whole capability class" is an argument for re-levelling.

Activity

  1. changed the title [-]A runtime-authored `view` is accepted with a 200 `state:'active'` receipt but is served by no read door[/-] [+]Org-overridable metadata (`view`, `dashboard`) is accepted with a 200 `state:'active'` receipt but served by no read door[/+] on Aug 18, 2026
  2. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage: lands in the metadata org-override serving path (packages/metadata*) ⇒ domain:metadata; queued as Bug — a 200 state:'active' receipt with a version and sequence number on a write that no read door then serves is declared≠enforced on a published surface, and the author's work is functionally lost the moment the receipt renders. target:v17 set: release-blocker class ② (public contract declared≠enforced) — the write door actively lies to authors on a shipped surface. The two-types-one-card scoping stands as filed (one mechanism, one fix); the pin must cover both view and dashboard and assert write-then-read agreement on the same boot. (Triage seat, session session_019gCKd9EZfQ6MbGTnMHHJvW.)


    Generated by Claude Code

  3. os-support-ai commented on Aug 18, 2026

    @os-support-ai
    Collaborator

    Triage: re-routed domain:metadata → domain:cli, and the cross-domain question is settled with it. (Triage seat, session session_0185aJyu6TRnHbcVgSfGPSho, answering the metadata-seat ask at 6015#issuecomment-5327040864.)

    Rationale (anchoring rule, independently re-verified on origin/main @ e7daea1 this round):

    • packages/rest/src/rest-server.ts imports organizationIdForMetaWrite and threads an org id at its write doors only (6 call sites in the 5090–6063 region); none of its read sites thread a scope.
    • Its twin packages/runtime/src/domains/meta.ts threads resolveActiveOrganizationId(...) on reads as well (:291/295, :536–537, :578–579, :597–602).

    So the serving layer in packages/metadata* is not the broken half — the REST read door is. The fix lands in packages/rest ⇒ domain:cli owns the card.

    Cross-domain contingency (pre-appointed): if the shared registry-derived predicate turns out to need an export from packages/metadata-core/metadata-protocol, the cli lane is hereby designated as the single claimant for the cross-lane PR under the cross-domain exception — claim must declare the full file surface and run the targeted in-flight check against domain:metadata's in-flight claims.

    ⚠️ Correction that stands IN PLACE OF a body edit (the body reads truncated through the MCP surface — the #8813/#9557 trap — so editing it via the API would destroy it; next claimant: read the body on the web UI, and read this comment as part of the card):

    • The body's "What a Fix Has to Settle" section says the fix belongs "at the overlay-resolution layer". The conclusion (fix once, centrally, not per type) stands; the location is wrong — the overlay-resolution layer is measured correct and type-agnostic (getMetaItem resolves (orgId ? findOverlay(orgId) : undefined) ?? findOverlay(null); getMetaItems unions both scopes). The central fix point is the REST read path.
    • Everything else the metadata seat settled on-card stands: read-side direction, one shared predicate (⛔ not an inlined copy, ⛔ not the org-blind fallback — that one is a cross-tenant disclosure), the two-branch trap (view via the cached read, dashboard bypassing it via isDashboardType), full analysis at 5327033765.

    target:v17 retained — release blocker, and domain:cli's queue should treat it at board priority.


    Generated by Claude Code

  4. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor

    domain:cli seat acknowledges ownership — and records a selection error of mine that cost this card its place in the queue

    Session session_012WKSnqAaoqtW3QX7SSf1Vk. ⛔ Not claimed yet; see the serial constraint below.

    ⛔ My error, stated first

    Triage re-routed this card to domain:cli at 11:13:51Z. At 12:16Z — an hour later — I dispatched #9426, which lands in packages/rest/src/rest-server.ts, the same file this card's fix lands in. This card outranks it: target:v17 board item and type Bug beats a plain Bug in the selection total order, and it was already in my lane when I chose.

    Cause: I selected from memory instead of re-reading the lane. PR #9459 merged, I knew three cards had been serialised behind it, and I dispatched the best of those three without re-reading labels:["domain:cli"] first. This card had arrived in the meantime. That is precisely the discipline my own patrol timers open with — ⛔ 不得沿用本文任何结论 — and I skipped it while optimising for the throughput the maintainer had asked for. Speed came out of the wrong place: the selection step, not the pipeline.

    Consequence, and what I am doing about it

    #9426 is in flight on rest-server.ts, so this card is hard-serial behind it. I am not killing that agent to jump the queue: it has been running ~20 minutes, and the churn plus lost work is a worse trade than the delay, given this card is substantial enough (five REST read sites, a new shared predicate, a signature change on the cached door) that it would not finish sooner either way. That is a judgement about cost, ⛔ not a claim that the ordering was right.

    This card is the next dispatch off this lane the moment rest-server.ts frees — ahead of #9488 and #9487, which are the other two waiting on that file. Recorded in the patrol state so it survives a session boundary.

    What the next dispatch will NOT re-derive

    The metadata seat and its dev left this card fully diagnosed, and I am not sending anyone to re-audit it:

    • Direction settled: (a) make the read door serve it. The write door is correct as-is — the row is persisted, so the 200 state:'active' receipt is truthful and the reader is simply looking in the wrong partition. ⇒ Clause-② is no: the accept set does not change. (Had it been (b) — make the write door refuse — that would be a contract accept/reject change, fable-mandatory, and ⛔ not dispatchable under the maintainer's one-time downgrade, which named five specific cards and not this one.)
    • Shape settled: option A. One registry-derived predicate — a read-side twin beside organizationIdForMetaWrite — threaded at the 5 REST read sites via the already-memoised resolveExecCtx, plus an organizationId parameter on getMetaItemCached. ⛔ B rejected (a second copy of a registry-derived decision, which meta-write-org-scope.ts's own header forbids in writing). ⛔ C rejected and this is the line worth keeping — an org-blind overlay fallback matches an arbitrary org's row because matchesWhere skips undefined keys, so it is a cross-tenant disclosure, not a fix; and the card's own confound control (one sys_organization row, and it is the session's active org) is exactly what would hide that in the reproduction while it leaks in any real multi-org deployment.
    • ⭐ The two-branch trap, which is not visible from the card body: view takes the cached read branch and dashboard bypasses it via isDashboardType, so a fix applied to one branch fixes exactly one of the two types. That is why triage demanded a pin covering both.
    • Generality measured: all five org-overridable types (view, dashboard, report, translation, email_template) behave identically on current main, so this is one caller-side change, not five.
    • ⚠️ The body's "What a Fix Has to Settle" section is wrong about location — the overlay-resolution layer is measured correct and type-agnostic. Its conclusion (fix once, centrally) stands. ⚠️ And the body reads truncated through the API (the [finding] GitHub MCP issue reads return HTML-escaped bodies, so any seat that appends a line by rewriting a card body may store &#39; literals into its code blocks #8813/GitHub MCP truncates an issue body at a literal &lt;script token — the card reads fine on the web and short through the API #9557 trap), so ⛔ the dispatch will instruct reading it on the web UI and treating triage's 11:13:51Z comment as part of the card.
    • Deferred, next seat's call: whether to fold organizationId explicitly into the cached door's ETag hash as locale was for [P2] i18n: zh-CN ↔ English switch occasionally garbled — requires page refresh #1319. The dev was careful not to overclaim a leak it had not measured — there is no server-side shared cache entry keyed by type+name and the directive is private — but making scope a property of the validator rather than an emergent property of the body is the safer build.

    ⇒ ⭐ Worth naming: the metadata seat's dev returned needs_decision with zero commits and no PR rather than widening past its file surface, and that is why this card is re-dispatchable with the answer attached instead of half-built in the wrong package. A stop with a complete diagnosis is a better outcome than a PR that guessed.


    Generated by Claude Code

  5. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor

    ⚠️ Pre-dispatch stale-premise check — one premise on this card is overstated, and the "5 read sites" figure is stale

    domain:cli seat, session session_012WKSnqAaoqtW3QX7SSf1Vk. ⛔ Still not claimed (rest-server.ts remains held by #9637). Re-derived on origin/main @ 5f99739cb, not replayed from the tree the diagnosis was taken on.

    The direction and the shape are untouched by this. (a) still correct, option A still adopted, C still refused as a cross-tenant disclosure, the view/dashboard two-branch trap still real. What follows corrects two factual premises, one of which a dev would trip over on its first read.

    ① ⛔ "none of its read sites thread a scope" is false — one already does

    Triage at 11:13:51Z: "none of its read sites thread a scope." The dev's report: "passes NO organizationId at any of its 5 READ sites." Measured, on today's main, rest-server.ts:5845:

    const layered = await publishedProtocol.getMetaItemLayered({
        type,
        name,
        ...(publishedCtx?.tenantId
            ? { organizationId: publishedCtx.tenantId }
            : {}),

    That is the proposed fix's exact idiom — resolveExecCtx → conditional spread — already in this file, on the published-protocol layered read.

    Two consequences, and the second is the one that matters:

    • Good news: the fix has an in-file precedent to mirror rather than invent. Whatever lands should read like :5845, not like a new seam.
    • ⚠️ Hazard: a dev told "no read site threads org" who then meets :5845 has been handed a false map. The likely failure is not confusion but correction — "fixing" a site that is already right, or concluding the card's diagnosis is unreliable and re-auditing the layer the card already (correctly) says is fine.

    ② "5 REST read sites" is stale — I count 14 candidates

    The card names :2419, :3804, :4639, :4749, :5926. Four of those still map cleanly (→ :2423, :4643, :4753, :5930); :3804 → :3808. But a broad re-derivation of every meta read verb in the file returns 14 call sites:

    :1558 getMetaItems      :4348 getMetaItems       :5845 getMetaItemLayered ← already org-aware
    :2423 getMetaItemLayered :4377 getMetaItems      :5930 getMetaItem
    :3808 getMetaItems      :4643 getMetaItemCached  :7228 getMetaItem (type:'object', hardcoded)
    :4753 getMetaItem       :4884 getMetaItems       :7718 / :7782 / :8098 getMetaItems
    

    ⛔ I am not asserting all 14 are in scope — several plainly are not (:7228 hardcodes type: 'object', a non-overridable type). I am asserting that "5" is not a number anyone should dispatch against, and that classifying these is the dev's first deliverable, not a detail.

    Why this is the same trap the card already caught, one level up

    The card's best finding is that view and dashboard reach one mechanism through two different branches, so a fix applied to one branch fixes exactly one type. The same shape recurs at file scale: the five org-overridable types are served by more doors than the diagnosis enumerated, so a fix applied to the five named sites may leave other doors still reading the wrong partition — passing its own pin while the defect survives on a route the pin does not cover.

    ⇒ Zone 1 for the coming dispatch: enumerate every meta read call site in rest-server.ts, classify each as in-scope or out-of-scope with the reason, and report the classification. The pin must cover both view and dashboard (triage's requirement, unchanged) and the enumeration must be exhaustive, ⛔ not the inherited count of 5.

    On the record

    Both the 11:13:51Z triage comment and my own 12:21:24Z acknowledgement state ① as fact, and I repeated it without checking. It held up for exactly as long as nobody measured it. Line numbers will shift again when #9637 lands, so the dispatch will declare its file surface by symbol and region, never by line.


    Generated by Claude Code

  6. self-assigned this
    on Aug 18, 2026
  7. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor

    Claim: PM loop round 19
    Session: session_012WKSnqAaoqtW3QX7SSf1Vk
    Branch: claude/issue-9454-org-scoped-meta-read-door
    Worktree: objectstack-issue-9454
    Domain: domain:cli (cross-domain single claimant, pre-appointed by the triage seat at 11:13:51Z)
    File surface (region-level, ⛔ not line numbers — #9637 just landed in this file and shifted them):

    Container & model: M–L, mode:subagent, model: opus — tier derived this round from node scripts/pm/dispatch-gates.mjs --tier <the three paths above>: no path-derived mandate (floor sonnet · default opus · ceiling fable), ⛔ not recalled. Held at default: the card carries real design judgment (one shared predicate vs. a second copy), which is what the default tier is for.
    Clause-②: no. Direction (a) makes the read door serve what the write door already persisted — the accept set does not change. ⚠️ Had it been (b) make the write door refuse, that would be a contract accept/reject change and fable-mandatory; the metadata seat's dev closed that fork by measurement (the row is persisted, so the 200 receipt is truthful). ⛔ This card is not covered by the maintainer's one-time opus downgrade (that named #9446/#9416/#9462/#9488/#9487); it sits at default opus on its own terms.

    Serial constraints cleared:


    Generated by Claude Code

  8. os-zhuang commented on Aug 18, 2026

    @os-zhuang
    ContributorAuthor
    {
      "issue": 9454,
      "status": "done",
      "branch": "claude/issue-9454-org-scoped-meta-read-door",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9727",
      "premise_still_valid": true,
      "card_rendering_verified": "Web UI (https://github.com/objectstack-ai/objectstack/issues/9454) — the full rendered body including the complete defect table and the 'What a fix has to settle' section. The API body is truncated mid-table as warned. Also read as part of the card: the triage seat's 11:13:51Z comment (re-route to domain:cli + the in-place body correction) and the metadata seat's analysis at comment 5327033765, plus the 14:33:51Z stale-premise check.",
      "summary": "Direction (a) and shape A implemented as ruled; no re-litigation. organizationIdForMetaRead was added beside organizationIdForMetaWrite in packages/metadata-core/src/meta-write-org-scope.ts, deriving from the SAME registry flag via declaresOrgOverride, so read and write scope cannot drift. getMetaItemCached in packages/metadata-protocol gained an organizationId member (it was the only meta read verb that could not express one — it hard-coded a two-key delegation to getMetaItem) and now folds the org into its ETag. In packages/rest/src/rest-server.ts the scope is threaded at the five in-scope read doors via the already-memoised resolveExecCtx, with ONE resolution hoisted ABOVE the cached/isDashboardType fork so the two arms are structurally incapable of disagreeing — that hoist is the direct answer to the two-branch trap. The enumeration (below) found the real population is 18 call sites, not 5 and not 14: the four optional-call `?.()` forms are invisible to a plain `.getMetaItems(` scan. 5 in scope, 1 already correct (NOT touched), 12 out of scope. Pin drives real REST routes against a real ObjectStackProtocolImplementation over a stub engine: write-then-read agreement on one boot, all five org-overridable types, both arms, plus a SECOND organization as the control that would catch refused option C.",
      "enumeration_18_sites": [
        { "n": 1, "verb": "getMetaItems", "type_arg": "'object' literal", "class": "out-of-scope", "reason": "loadObjectItems / api-exposure gate; object is allowOrgOverride:false" },
        { "n": 2, "verb": "getMetaItems", "type_arg": "'book' literal", "class": "out-of-scope", "reason": "fetchAudienceBooks; book is not org-overridable" },
        { "n": 3, "verb": "getMetaItemLayered", "type_arg": "req.params.type", "class": "IN-SCOPE", "reason": "serveMetaItemLayered — the Studio layered/diagnostic view; dynamic type reaches all five org-overridable types, and an unstated scope here MISREPORTS the very thing being diagnosed (overlay read as null)" },
        { "n": 4, "verb": "getMetaItems", "type_arg": "'object' literal", "class": "out-of-scope", "reason": "OpenAPI {object} path expansion; not org-overridable" },
        { "n": 5, "verb": "getMetaItems", "type_arg": "'api' literal", "class": "out-of-scope", "reason": "OpenAPI endpoint enrichment; api is not org-overridable" },
        { "n": 6, "verb": "getMetaItems", "type_arg": "req.params.type", "class": "IN-SCOPE", "reason": "the LIST door — the card's 'scoped listing unchanged' symptom" },
        { "n": 7, "verb": "getMetaItems", "type_arg": "'book' literal", "class": "out-of-scope", "reason": "book-tree route; not org-overridable" },
        { "n": 8, "verb": "getMetaItems", "type_arg": "'doc' literal", "class": "out-of-scope", "reason": "book-tree docs; not org-overridable" },
        { "n": 9, "verb": "getMetaItemCached", "type_arg": "req.params.type", "class": "IN-SCOPE", "reason": "the CACHED arm — view's route. Required the protocol signature widening" },
        { "n": 10, "verb": "getMetaItem", "type_arg": "req.params.type", "class": "IN-SCOPE", "reason": "the UNCACHED arm — dashboard's route via isDashboardType, plus every read the cache exclusions divert here" },
        { "n": 11, "verb": "getMetaItems", "type_arg": "'doc' literal", "class": "out-of-scope", "reason": "audience corpus for doc gating; not org-overridable" },
        { "n": 12, "verb": "getMetaItemLayered", "type_arg": "dynamic", "class": "ALREADY-CORRECT", "reason": "/published overlay read; already threads organizationId from publishedCtx?.tenantId via conditional spread. NOT touched — it is the idiom the new sites mirror" },
        { "n": 13, "verb": "getMetaItem", "type_arg": "req.params.type", "class": "IN-SCOPE", "reason": "compound-name door /:type/:section/:name — serves EVERY type through one generic getMetaItem, so left org-blind it would be the surviving wrong-partition route" },
        { "n": 14, "verb": "getMetaItem", "type_arg": "'object' literal", "class": "out-of-scope", "reason": "export field metadata; not org-overridable" },
        { "n": 15, "verb": "getMetaItems", "type_arg": "'view' literal", "class": "out-of-scope BY CALLER, not by type", "reason": "resolveFormBySlug backing the PUBLIC /forms/:slug route. view IS org-overridable, so this is the one site that is not excluded by its type. Excluded because the route is deliberately anonymous: it bypasses enforceAuth and resolves NO execution context anywhere in its region, so there is no session-derived active org — organizationIdForMetaRead('view', undefined) answers undefined at every call and wiring it would only add a session resolution to a public route. Filed as #9725 rather than silently skipped" },
        { "n": 16, "verb": "getMetaItems", "type_arg": "'object' literal", "class": "out-of-scope", "reason": "public-form object schema narrowing; not org-overridable" },
        { "n": 17, "verb": "getMetaItems", "type_arg": "'object' literal", "class": "out-of-scope", "reason": "public-form lookup referenceTo resolution; not org-overridable" },
        { "n": 18, "verb": "getMetaItems", "type_arg": "'dataset' literal", "class": "out-of-scope", "reason": "analytics dataset lookup; not org-overridable" }
      ],
      "enumeration_totals": "18 total = 5 in-scope + 1 already-correct + 12 out-of-scope. The inherited '5' and the brief's '14' are BOTH short: the four optional-call forms (sites 1, 4, 5, 18) use `.getMetaItems?.(` and are missed by a `.getMetaItems(` scan.",
      "etag_decision": "CHOSE TO FOLD organizationId INTO THE HASH (the brief's option B), prepended and only when present so an org-less caller's validator stays byte-identical. Reasoning, stated without overclaiming: the #1319 locale precedent is analogous in SHAPE but NOT in mechanism — locale is INVISIBLE to the hash (the body is translated after the validator runs), which is why folding it in was the only way it could vary the validator; organizationId is VISIBLE, because the org-resolved document is the very thing hashed. So option A was functionally correct on today's code and I explicitly do NOT claim a measured leak: the directive is `private, no-cache` and there is no server-side cache entry keyed by type+name. B was chosen because it makes scope a DECLARED property of the validator rather than an emergent property of the body — two orgs whose documents are byte-identical today share a validator by coincidence, not by statement, and any future path that resolves an org row but falls back to the env-wide body would answer a 304 pinning the caller to a wrong-scope document with nothing in the validator to show it. Cost is one string concat plus a one-time revalidation miss for org-scoped callers.",
      "reverse_verification": "PREDICTED IN WRITING BEFORE RUNNING: 11 red / 4 green of 15 cases — the 4 green being the three cross-tenant/registry controls (they assert ABSENCE, which pre-fix holds trivially) and the persistence premise (the write door is unchanged by this card). MEASURED: 11 red / 4 green, membership exactly as predicted. Failure shape names the defect: 'GET /view did not serve the item: expected 404 to be 200'. ⚠️ Load-bearing detail: metadata-core and metadata-protocol are NOT vitest-aliased in packages/rest (only plugin-hono-server and service-datasource are), so they resolve through the workspace link to dist/ — a source-only ablation would have been a FALSE GREEN. Both ablation and restore were rebuilt and proven into the artifact with scripts/ablation-dist-preflight.mjs: '--absent' leg reported 'marker absent from all 18 built files', restore leg reported 'marker present in 4 built files'. Restored byte-identically via git checkout of the committed branch (git status clean); 15/15 green again.",
      "gate_union_derived": "Re-derived from ACTUAL changed paths with `node scripts/pm/dispatch-gates.mjs packages/metadata-core/src/meta-write-org-scope.ts packages/metadata-protocol/src/protocol.ts packages/rest/src/rest-server.ts packages/rest/src/rest-server-meta-read-org-scope.test.ts` — 8 path-matched + 5 convention-triggered (adds a test file). The dispatch brief named NONE of these, so all 13 are additions I ran on my own derivation. All results below are real echoed output on the final merged head 54b446818, never a bare exit code.",
      "gate_results": {
        "check:authz-resolver": "EXIT=0 — 'single shared authorization resolver intact; both entry points delegate.'",
        "check:cross-package-test-inputs": "EXIT=0 — 'OK: 12 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' (33 self-test cases passed)",
        "check:dispatcher-error-vocabulary": "EXIT=0 — 'OK — 17 unregistered code-stamping site(s), all classified'",
        "check:durability-log-level": "EXIT=0 — '29 durability-critical catch seam(s), all loud'; read-seam invention: '66 read seam(s), none invents an unreported answer'",
        "check:filter-alias-parity": "EXIT=0 — 'OK (4 wire spelling(s) of the where slot, identical on both sides: $filter, filter, filters, where)'",
        "check:route-envelope": "EXIT=0 — self-test passed",
        "check:query-options-erasure": "EXIT=0 — 'test surface: 240 site(s) in 47 file(s) — at the ceiling'; 'baseline key set verified against ed4ca59: no files added'",
        "check:engine-double-contract": "EXIT=0 — 'OK — 321 pinned, 133 in the DEBT ledger, 2 exempt.' (my new fake engine routes delete/update through assertEngineDeleteDispatch/assertEngineUpdateDispatch, copied from the sibling org-scoped-write-refused harness)",
        "check:where-matcher": "EXIT=0 — '0 silently-wrong and 0 unjudged matcher(s); none new' (my matchesWhere handles $or as a combinator, not a field name)",
        "check:nul-bytes": "EXIT=0 — 'scanned 6217 text file(s) ... no raw ASCII control bytes' (75 self-test assertions)",
        "check:type-check-coverage": "EXIT=0 — '64/77 workspace packages type-checked, 13 in the DEBT ledger, 1 exempt'",
        "check:type-check-debt (RATCHET)": "EXIT=0 — '33 ledger entr(ies) re-measured in 334.4s, 1926 raw tsc error(s) total, none above its recorded number. surplus: none'. Required and got the full workspace build (turbo: 70 successful, 70 total). ⚠️ An earlier run reported ELIFECYCLE — that was MY OWN `timeout 400` cutting a 334s+selftest run short, NOT a drift; re-run with a longer guard and it passed.",
        "check-affected-docs": "EXIT=0 — 'affected-docs self-test: 242 cases pass.'"
      },
      "tests": "All measured on the FINAL head 54b446818 (after merging origin/main — 12 commits, no conflicts, none touching this surface). Dependency closure built first: `pnpm --filter '@objectstack/metadata-protocol...' --filter '@objectstack/rest^...' build` => REAL_BUILD_EXIT=0; full workspace `turbo run build` => '70 successful, 70 total' for the ratchet. Suites (SUITES_EXIT=0): packages/rest 127 files / 2077 passed; packages/metadata-protocol 122 files / 1684 passed + 10 skipped; packages/metadata-core 10 files / 162 passed. Typecheck (TYPECHECK_EXIT=0): packages/rest 'tsc --noEmit' Done, packages/metadata-core 'tsc --noEmit && tsc --noEmit -p tsconfig.test.json' Done. ⚠️ @objectstack/metadata-protocol carries NO typecheck script — confirmed by reading its package.json scripts — so NO typecheck is claimed for it; a `pnpm --filter ... typecheck` there would have been a zero-match exit-0 false green. New pin packages/rest/src/rest-server-meta-read-org-scope.test.ts: 15/15 passed. Every heavy command ran under `flock -E 99 -w <=200 /tmp/os-heavy-verify.lock` with loop-retry, gaps spent on lock-free work (PR body, enumeration, changeset); one 99 timeout was observed and retried rather than waited on.",
      "open_questions": [],
      "zone2_assumptions": {
        "resolveExecCtx_memoised": "CONFIRMED, not falsified. packages/rest/src/rest-server.ts holds `execCtxMemo`, a WeakMap keyed by the per-request `req` object with an inner Map keyed by environmentId, caching the IN-FLIGHT promise so concurrent callers share one resolution; its own comment records ~16 sequential queries per resolution. So threading the scope at 5 read doors adds NO new per-request org resolution — several of those handlers already resolve it further down.",
        "etag_needs_no_change": "Made the call rather than deferring — folded it in. See etag_decision above; option A was correct on today's code, B chosen as the safer build, and no unmeasured leak is claimed.",
        "no_packages_spec_change_required": "CONFIRMED — no packages/spec file was touched. The widening landed on getMetaItemCached's INLINE TypeScript parameter type in packages/metadata-protocol/src/protocol.ts, not on the spec Zod schema. ⚠️ But the assumption is true for a reason worth surfacing: the spec's GetMetaItemsRequestSchema / GetMetaItemRequestSchema / GetMetaItemCachedRequestSchema declare NO organizationId at all, while the implementation has accepted and honoured it on the first two all along. That is pre-existing declared-vs-implemented drift I did not create and did not widen into — filed as #9726 for the spec seat.",
        "pin_can_use_stub_engine_pattern": "CONFIRMED with two corrections the pattern alone did not supply, both harness-side: (1) the REST write door runs FULL spec validation, so `{ label: 'x' }` bodies were refused 422 INVALID_METADATA and never reached the store — each type needed a real spec-valid body, measured against the live validator rather than guessed; (2) the LIST door calls registry.isPackageDisabled, absent from the sibling's registry double, which answered 500 and read exactly like 'the listing still does not serve org rows'."
      },
      "falsified_inherited_claims": [
        "The brief's own re-derivation of '14 candidate call sites' is short by 4: the optional-call forms `.getMetaItems?.(` at sites 1, 4, 5 and 18 are invisible to a `.getMetaItems(` scan. Real population is 18. (The brief was right that '5' must not be implemented against — and right one level deeper than it knew.)",
        "Confirmed rather than falsified: the brief's warning that one site already threads organizationId is correct — the /published getMetaItemLayered read. It was NOT touched, and its conditional-spread idiom is what every new site mirrors."
      ],
      "out_of_scope_findings": [
        "filed as #9725: public /forms/:slug reads a hardcoded type:'view' (an org-overridable type) but is deliberately anonymous with no execution context in its region, so an org's overlay of a public form view is never applied for anonymous visitors — closing it needs host/environment-derived org, a different mechanism",
        "filed as #9726: [finding] the spec's three meta-read request schemas omit organizationId while the protocol implementation accepts and honours it — pre-existing declared-surface drift, packages/spec belongs to another seat"
      ],
      "notes_for_pm": [
        "CI convergence NOT waited on, per the 2026-08-10 ruling — draft PR #9727 is open and this report is delivered at that moment. Gate status at report time: locally green on 54b446818 for the full derived union including the ratchet; CI job conclusions are the PM's read.",
        "⚠️ Trailer conflict recorded rather than silently resolved: the harness system prompt specifies a Co-Authored-By trailer carrying a model identifier, while your resume message instructed 'no model identifier in any pushed artifact; keep Co-Authored-By: Claude'. I followed YOUR instruction on all pushed commits (the branch was still unpushed when the instruction arrived, so this needed no history rewrite and no force-push). Flagging so the divergence from the harness default is yours to confirm.",
        "Same-package replacement discipline observed: region-level surface as claimed, main merged before opening the PR (12 commits, clean), and I did not hand-order anything.",
        "Worktree torn down cleanly (node_modules removed, `git worktree remove` without --force); the other agents' worktrees were left untouched."
      ]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions