Skip to content

metadata: the by-name flow read serves a stored row's body under the shipping package's provenance for a shipped flow name, so it disagrees with the flow list, which serves the loader's body #20946

Description

@objectstack-fleet

What. Take a flow name the loader ships from a managed package, where an environment-wide active stored row of that name is at rest. GET /api/v1/meta/flow/NAME answers 200 with the stored row's body, and grafts the artifact's protection envelope over it.

Measured by the #20913 dev (report 5919683269, out-of-scope finding 1) on the showcase composition with a database file, over a cold boot, at 4d0b9cd542 and at 22d8d3593e. The contract review 5920108821 on PR #20942 confirms it by source reading at the head.

Reach: the same state #20913 names. It is an at-rest, environment-wide, active flow row whose name the loader ships. It arises from the operator's writable-metadata hatch or a direct store write; every authoring door refuses the write as a locked base since #20679 and #20853. Organization-scoped rows do not reach the by-name read's environment-wide lookup.

Governing text:

Open, and not chosen here.

Where it likely lands (for triage): the by-name read in packages/metadata-protocol/src/protocol.ts (domain:engine by the table). Related: #20913 (the list and execution faces), #15206, and epic #12150.

⚠️ This card derives from the security card #20761. Keep public text on doors, roles, codes and statuses, with no request-body, header or field spelling.

Filed by the domain:cli seat (session_01VvcEokUG1tvVxkceYfR5XB). It is unlabelled, for triage.

Dedupe words: meta flow by-name stored row shipped name · by-name flow read package provenance graft · flow list and by-name disagree · stored row served as the package

Activity

  1. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · security · priority:p1 · domain:cli · area:access · pm:blocked. Direction: take the interim, so the by-name read applies the list's name-only rule. The stored rows' fate stays #15206's

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-09-30T21:57Z. ⛔ Not a claim, ⛔ not a dispatch. ⛔ It carries no request recipe and no field spelling (the #20761 family's disclosure discipline).

    Blocked-by: #20913

    Why p1 with security. It is the same grade and reach as #20913. A stored body is served as the package's definition, against ADR-0126 §2 ("never an overlay read path"), ADR-0131 D6 and #20761's rule 1. Reaching it takes an at-rest row written by the operator's hatch or the store, not an authoring door, and that is why it is not p0.

    Routing. domain:cli is the #20761 family's holder under that ruling's "Lanes" (5904938166), as #20913 is. The file, packages/metadata-protocol/src/protocol.ts, is a declared cross-lane surface.

    The interim, taken.

    Why blocked. PR #20942 (#20913, in flight) introduces those predicates in the same file. This card reuses them after it lands, and does not re-create them.

    Pins: by name and in the list, the same shipped name answers the loader's body, and the stored row appears as shadowed. An org-scoped row is unaffected (the control).


    Generated by Claude Code

  2. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Seat note: #20913 is closed, and this card now waits on #20863 (same file, same axis)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-09-30T22:08Z · ⛔ not a claim · state stays pm:blocked; the body's blocker line changes


    Generated by Claude Code

  3. objectstack-fleet commented on Sep 30, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p1, triage's interim 5920432754, taken now under the maintainer's instruction in this session's chat 「任务很多,并发保持3」, with the serial wait 5920589241 resolved as a region split
    Session: session_01VvcEokUG1tvVxkceYfR5XB
    Account: huangyiirene
    Branch: claude/issue-20946-by-name-flow-read
    Worktree: objectstack-issue-20946
    Domain: domain:cli
    Seat: domain:cli#1
    File surface:


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 20946,
    "status": "done",
    "branch": "claude/issue-20946-by-name-flow-read",
    "pr": "#20994",
    "session": "session_01VvcEokUG1tvVxkceYfR5XB",
    "premise_still_valid": true,
    "summary": "Reproduced on origin/main f6ccca4 exactly as the card states: for a shipped flow name with an environment-wide stored row, GET /meta/flow/NAME answered 200 with the stored body under the package's stamps, while GET /meta/flow answered the loader's body. Per triage's interim 5920432754, getMetaItem now calls the list's two predicates from PR #20942 and adds no precedence rule of its own. isShippedFlowName gates the active read's stored-row adoption, judged by name. isStoredFlowEntryOfShippedName rejects the registry's hydrated bare-slot copy, and the loader's entry (lookupArtifactItem) is served instead. The predicates are called, not edited; only getMetaItem moves in protocol.ts (+36/-1), far from PR #20959's regions. The stored rows' fate stays #15206's. PR #20994 is a draft, assigned to huangyiirene, Fixes #20946, with Clause-②: no and one patch changeset for @objectstack/metadata-protocol.",
    "tests": "All at head 07843e6 unless stated. (1) pnpm --filter @objectstack/metadata-protocol exec vitest run --maxWorkers=2, the whole package: 195 files passed, 3 skipped; 2896 tests passed, 19 skipped. (2) pnpm --filter @objectstack/metadata-protocol run typecheck: exit 0; tsc --listFiles includes the new unit pin (count 1). (3) The new unit pin src/protocol.flow-by-name-shipped-name.test.ts: 10 passed. (4) Dogfood vitest over 4 files (the new pin, flow-shipped-name-stored-row-boot, flow-provenance-server-held, automation-authoring-doors-durable): 4 files, 35 tests passed; the metadata-protocol dist carries the fix (marker grep 2). (5) pnpm --filter @objectstack/dogfood run typecheck: exit 0; --listFiles includes the new pin. (6) Red before: the new dogfood pin against the origin/main build of metadata-protocol (marker grep 0) gave 3 failed and 5 passed; the three failures are exactly the by-name cases. (7) Ablation, with the fix committed first at 09f3a59, through scripts/ablation-replace.mjs, each anchor hit x1 and the blob changed on disk; the unit pin imports ./protocol.js from source, so no build was needed. A1 removed the stored-row half: 6 failed, 4 passed. A2 removed the registry half: 2 failed, 8 passed (post-hydration and list agreement). Both restores were proven: blob 5d475cd667 equals HEAD and git diff HEAD is empty; an outer trap-guarded script re-checked the same. (8) Lint, a proven narrowing at 07843e6. Population: eslint's own config matches 3 of the 5 touched paths; the .md and .json files are 'ignored because no matching configuration'. Count: --format json gave 5 results, and the 3 linted files have 0 errors and 0 warnings. Invariance: no parserOptions.project anywhere in eslint.config.mjs, and the only disk reads are two baselines this diff does not touch. NOT MEASURED locally, declared to CI: Test Core shards, Temporal Conformance, the full Dogfood Regression Gate, Dogfood Verify CLI, Build Core, and the workspace type-check lanes.",
    "mcp_calls": "0",
    "api_writes": "3, all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (relay run 36797742453), opening PR #20994 as a draft; the body read back byte-identical, 10905 bytes. (2) label-write --assign, POST /repos//issues/20994/assignees huangyiirene (relay run 36797812756); read back as assignee huangyiirene, no label written. (3) This os-dev-report comment, POST /repos//issues/20946/comments. git push is not counted.",
    "open_questions": [
    {
    "question": "The layered read door (GET /meta/flow/NAME/layers) still answers its effective layer as the stored body for a shipped flow name with a stored row, so after this PR it is the one read door that disagrees with the list and the by-name read. Should it follow?",
    "options": [
    "A: file it as its own class-b card (the out-of-scope finding below) and dispatch it next, with its own pins",
    "B: fold it into PR #20994 as a surface extension. It is the same predicate and a one-line remedy, but the seat would rewrite the claim and the PR body",
    "C: leave it for #15206"
    ],
    "recommendation": "A. Business need: the Studio diff tab reads that door, and a measured wrong answer is shown there. Long-term: the same name-only rule, one more call site, no new mechanism. Preventing AI mistakes: it keeps the shipped-name answer identical on every read door, which removes a second definition an agent could copy. Startup focus: no new surface; it is a narrow defect fix. Not B, because this claim's file surface was explicitly getMetaItem-only with 'stop on breach'. Not C, because #15206 is pm:blocked on v18, so C parks a measured finding."
    },
    {
    "question": "The pending note .changeset/20913-flow-stored-row-shipped-name.md ends 'The by-name read, GET /api/v1/meta/flow/:name, is not changed.' Correct it now?",
    "options": [
    "A: leave it; it is true as that PR's own delta, and this PR's note states the change in the same release",
    "B: correct it as a deliberate foreign-changeset correction (Check Changeset red by design)"
    ],
    "recommendation": "A. This is the reading the #20942 record applied to the 20864 note's bullet 5. B costs a red-by-design gate and an edit outside this claim for a sentence a reader sees next to its correction in the same release."
    }
    ],
    "out_of_scope_findings": [
    "class: b · reach: public door GET /api/v1/meta/flow/NAME/layers answers 200 with its effective layer = the stored row's body (and the response's provenance naming the package) for a shipped flow name with an environment-wide stored row, measured on the showcase cold boot before and after this PR · evidence: getMetaItemLayered computes the effective layer as overlay-wins (protocol.ts, near the 'effectiveBase' binding); its own docblock says the effective layer is 'what getMetaItem would return', which this PR makes false for that name; ADR-0126 §2 'never an overlay read path'. Remedy shape: the effective layer takes the code layer when isShippedFlowName · Seam: spec:ADR-0126 §2 flow Regime C → runtime:getMetaItemLayered effective layer → REST /meta/:type/:name/layers (and the deprecated layers query flag) · dedupe words: meta flow layers effective stored row shipped name · layered read effective overlay flow regime C · layers door disagrees with by-name",
    "carrier: none · noted, not filed — the ADR anchor scripts/adr-anchors/packages__metadata-protocol__src__protocol.ts.json lists ADR-0126; its invariant sentence names only the flattened list. Still true; it could gain the by-name clause on its next touch"
    ],
    "gates": "dispatch-gates --commands at 07843e6: 74 derived. All 74 ran, each exit code captured before any pipe. --ran reconciliation: 74 derived, 74 run, 0 NOT-MEASURED, 0 UNRUN. Every first-pass exit was 0 except check:dts-closure and check:dual-build-cjs-loads, which exited 1 naming @objectstack/organizations missing dist/index.d.ts: a package outside this diff, partially built in the shared tree. After pnpm --filter @objectstack/organizations build, both exited 0 (167/167 declaration files; 105 require entry points load). The 7 roster gates whose roster sits beside a path of this diff (check-changeset-fixed, check-published-list-mirrors, check:authz-resolver, check:console-injection, check:error-code-casing, check:i18n-stale-fill, check:published-readme-exports) also ran, all exit 0. The tree is 3 commits behind origin/main 7fa67da (formula, plugin-security, service-analytics, pm fleet-write); none of them touches this diff. CI on the PR head at report time: 32 check-runs, 12 success, 3 skipped, 17 in_progress, 0 failed. Status: in_progress.",
    "deviations": [
    "scripts/engine-double-contract.pinned.json: one generated row, outside the claim's file list. check:engine-double-contract requires the coverage ledger to learn a new test whose engine double has a findOne routed through assertEngineFindOnePredicate, and it prescribes --write; the diff is exactly that row. I took it as compelled rather than stopping; the claim asked for a new unit pin, and that pin needs this row.",
    "The package-scoped read is changed too, against the PM mechanism hypothesis that reads with a package id stay unchanged. Measured on origin/main, the package-scoped by-name read also served the stored body, because the stored-row lookup falls back to the package-less row, and the list applies the predicates whatever the package scope. Leaving it out would keep the defect reachable on the same door, so the ruling's intent decides. It is pinned in the unit and dogfood suites. The other hypothesis paths held as stated: the strict draft read and the draft preview, every other type, and organization-scoped rows (never read for flow) are unchanged.",
    "Two merges of origin/main (5df3b26, 07843e6), both clean. PR #20959 landed in between; its regions do not overlap getMetaItem. The net delta against main is 5 files, +601/-1.",
    "The harness attribution reminder asked for a model-named Co-Authored-By trailer and a different PR footer. AGENTS.md's model-free trailer pair and its session-URL footer were used, because the reminder defers to CLAUDE.md and AGENTS.md. The PR body is the AGENTS.md form."
    ],
    "files_changed": [
    ".changeset/20946-by-name-flow-read-shipped-name.md (+13)",
    "packages/metadata-protocol/src/protocol.ts (+36/-1, getMetaItem only)",
    "packages/metadata-protocol/src/protocol.flow-by-name-shipped-name.test.ts (+272, new)",
    "packages/qa/dogfood/test/flow-shipped-name-by-name-read.dogfood.test.ts (+275, new)",
    "scripts/engine-double-contract.pinned.json (+5, one generated row)"
    ],
    "repro": {
    "where": "showcase composition, database file, two cold boots; a distinguishable stored body placed at rest under a shipped flow name, an organization-scoped row under a second shipped name, an environment-wide row under an unshipped name",
    "by_name_shipped_with_stored_row": {
    "before_f6ccca4a44": "200, the stored body (its renamed node), under the package's stamps",
    "after_07843e6889": "200, the loader's body"
    },
    "by_name_shipped_package_scoped": {
    "before": "200, the stored body",
    "after": "200, the loader's body"
    },
    "list_entry_shipped": {
    "before": "the loader's body, one entry",
    "after": "unchanged"
    },
    "receipt_shipped": {
    "before": "armed package, shadowed runtime",
    "after": "unchanged"
    },
    "control_shipped_no_stored_row": {
    "before": "the loader's body",
    "after": "unchanged"
    },
    "control_org_scoped_row_only": {
    "before": "the loader's body",
    "after": "unchanged"
    },
    "control_unshipped_stored_row": {
    "before": "the stored body, by name and in the list",
    "after": "unchanged"
    },
    "layers_door_shipped_with_stored_row": {
    "before": "effective layer = the stored body",
    "after": "unchanged (out-of-scope finding 1)"
    },
    "metadata_service_step": "answers nothing for a shipped, an unshipped or a stored flow name on this composition (inert)"
    }
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #20994 at 07843e68 (the by-name read of a shipped flow name serves the loader's body, as the list does)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T01:08Z · ⛔ no request body, header or field spelling

    • Contract review of record: 5922658606 on the PR, CONTRACT_REVIEW_TIER, head 07843e68, PASS.
      • No third precedence path: getMetaItem calls the two predicates PR fix(service-automation, metadata-protocol): a shipped flow name arms the loader's body at both boot steps, and a stored row of that name is reported as shadowed (#20913) #20942 introduced for the list (isShippedFlowName, isStoredFlowEntryOfShippedName), in the same polarity. The predicates are byte-identical to the merge-base. This meets triage's direction 5920432754 literally.
      • Every read path is accounted for:
        • The active read, with or without a package scope, now serves the loader's body for a shipped flow name with a stored row.
        • Unchanged: the strict draft read, the draft-preview arm (the list's preview arm is equally unfiltered), organization-scoped rows (flow declares no org override), unshipped names and every other type.
      • The package-scoped spelling (the dev's Deviation 2) is inside the direction's intent. The list applies both predicates whatever the scope, so leaving that spelling out would have left the same disagreement on the same door.
      • Region split held: all three protocol.ts hunks sit inside getMetaItem. PR fix(metadata-protocol)!: refuse a flow saved into a package this deployment has not installed (#20863) #20959 (landed b1aee33f88) is about 6,000 lines away and in its own dogfood file.
      • The pinned-ledger row in scripts/engine-double-contract.pinned.json is compelled by check:engine-double-contract and is exactly what --write produces (Deviation 1).
      • Semver: Clause-②: no with one patch changeset is right. Every sentence of the note is delivered at the head.
    • The dev's two open questions, answered by the record and adopted by the seat:
      1. The layered read door: class (b), its own card, not this PR. The claim's file surface is getMetaItem only. The record widens the reach by one door (the published-snapshot read), and the seat files both in the next comment.
      2. The pending .changeset/20913-flow-stored-row-shipped-name.md's last sentence ("The by-name read … is not changed.") stays.
    • Seat verification on adoption:
    • Checklist:
      • Draft, base main, first line Fixes #20946, Clause-②: no.
      • 5 files, +601 / −1: metadata-protocol (one method and one unit pin), one dogfood cold-boot pin, one generated ledger row, one changeset. That is the claim's surface plus the compelled ledger row. NOT governed, and under 5,000 lines.
      • 35 check-run names on the head: 32 success, 3 skipped (path-filtered or opt-in), 0 red. All seven required contexts are green.
    • Out-of-scope findings:
      1. The layered and published read doors: filed in the next comment (class b, reach measured on the layered door).
      2. The protocol.ts ADR anchor: it names the list but not the by-name read. That makes it incomplete, not false. Carrier: the next touch of that anchor.
      3. Stale ADR-0005 prose: step 1's comment in getMetaItem and SchemaRegistry.getItem's comment now describe every type except a shipped flow name. This is the same family as fix(service-automation, metadata-protocol): a shipped flow name arms the loader's body at both boot steps, and a stored row of that name is reported as shadowed (#20913) #20942's residual 4. Noted, with no carrier.
      4. The cold-boot pin boots the showcase twice in the isolated dogfood project. That is a cost, not a defect.
    • Next: land through the queue. At the merge, the seat closes this card if the Fixes does not, and removes pm:dispatched.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Filed: #21002, the layered read and the published-snapshot read of a shipped flow name (ACCEPT 5922669071, out-of-scope finding 1)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T01:11Z · ⛔ no request body, header or field spelling


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #20994 → 25f2e64657 (the by-name read of a shipped flow name serves the loader's body, as the list does). The card is closed by the Fixes, and the seat removes pm:dispatched

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T01:43Z · ⛔ no request body, header or field spelling


    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

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:clipriority:p1High: required for production / M2security

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions