Skip to content

fix(rest): fold the switched-off action names into the cached object read's ETag - #28094

Merged
objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-kwainq
Oct 30, 2026
Merged

objectstack-fleet[bot] merged 6 commits into
mainfrom
claude/project-thread-kwainq

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Fixes #28044

维护者速读

Clause-②: no
Metadata-protocol: none

What changes

  • packages/rest/src/meta-item-read-routes.ts, the cached branch of GET /api/v1/meta/:type/:name:
    • For an object, the disabledActions served beside the item is now a fifth dimension folded into the ETag that this layer already builds and checks for objects (audienceFolds). The fingerprint is a hash of the list exactly as served.
    • When the key is absent (no ledger answered), nothing is folded, so the ETag stays byte-identical to the one before this change.
    • [] folds a fingerprint of its own, so a ledger being attached or detached also changes the ETag.
    • besideJudged, which skips the 304, is now set only by the flow registration. That verdict is in no validator, so a flow read that carries it still never gets a 304.
  • packages/rest/src/meta-object-cached-disabled-actions.test.ts: the old pin "a read that carries the key is never answered 304" is replaced by five rest: a cached GET /meta/object/:name that carries disabledActions is never answered 304 — fold the switched-off names into the ETag so object reads revalidate again #28044 pins:
    • an unchanged object with an unchanged switch set gets 304;
    • switching an action off gets 200 with the new list and a new ETag;
    • switching an action back on gets 200 with [] and a new ETag;
    • absent and [] give different ETags, in both directions;
    • control: an object with no switched-off actions keeps its 304, and its ETag stays "etag_task".
  • packages/spec/src/api/protocol.zod.ts: only the GetMetaItemResponseSchema.disabledActions .describe() string. It said the list is "never part of the ETag, so a cached read that carries it is never answered 304"; it now says the list is folded into the ETag, so a cached read revalidates to 304 only while the switched-off set is unchanged. The generated content/docs/references/api/protocol.mdx line follows. No shape changes.
  • A patch changeset for @objectstack/rest and @objectstack/spec.

The response body is unchanged and no accepted input changes. The list read GET /meta/object has no cached or ETag door (meta-collection-routes.ts has no ETag handling), so it has nothing to pin.

Why

#27964 put disabledActions beside the object read but kept it out of the validator. To avoid sending a stale list with a 304, it skipped the 304 for every read that carried the key. On a default deployment the ledger is attached, so every cached object read was a full 200. The maintainer approved #27964 as built and asked for the 304s back. Folding the served list into the validator means a 304 is correct again: the ETag now covers what is actually sent.

Verification

Measured before the fix (tree c1b52de)

A cached object read with disabledActions: ['showcase_mark_done'] was sent back its own ETag as If-None-Match and got 200, not 304 (AssertionError: expected 200 to be 304). The ETag was "etag_task" whatever the switch set held. The new pins: 5 failed, 3 passed. One part of the control already passed before the fix (no ledger → 304); its [] part failed (200).

After the fix and ablation (commit 8efc06e)
  • The pins file: 8/8 passed.
  • Ablation: meta-item-read-routes.ts restored to its c1b52de version; the same 5 pins went red, 3 passed. Restored with git checkout HEAD -- <path>; git diff HEAD empty.
Package suite and gates (commit 8efc06e)
  • pnpm --filter @objectstack/rest test: Test Files 353 passed (353); Tests 6801 passed, 332 skipped (7133).
  • pnpm --filter @objectstack/rest typecheck: OK.
  • eslint on the two changed .ts files with --no-inline-config: exit 0.
  • dispatch-gates --commands: 60 derived; 59 ran with exit 0. check:dual-build-cjs-loads needs a whole-tree build, so it is left to CI.
Spec string and merge of main (head b45e452)
  • pnpm gen:schema and pnpm gen:docs in packages/spec: exit 0; only content/docs/references/api/protocol.mdx changed among tracked files.
  • After merging origin/main (c4390f7): the pins file plus the meta-item-read tests, 11 passed; packages/spec src/api/protocol* tests, 225 passed.
  • check-commit-card-trailers --range origin/main..HEAD: exit 0.

🤖 Generated with Claude Code

https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL

…read's ETag

The default cached GET /meta/object/:name serves disabledActions beside
the item, but kept it out of the validator and therefore stood every
304 down for a read that carried the key. On a default deployment (the
activation ledger attached) no object read ever revalidated to 304.

Fold the served list into the ETag the REST layer already folds and
judges for objects, so an unchanged object under an unchanged switch
set revalidates to 304 and a switch flipped off or on answers 200 under
a new ETag. Absent folds nothing (the ETag stays byte-identical); []
folds a fingerprint of its own. Only the flow registration, which is in
no validator, still stands the 304 down.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
…read's ETag

The disabledActions describe string said the list was never part of the
ETag, so a cached read carrying it was never answered 304. The REST door
now folds it in, so that sentence would contradict the runtime.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
@github-actions github-actions Bot added size/m documentation Improvements or additions to documentation tests tooling labels Oct 30, 2026
@github-actions

github-actions Bot commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/rest, @objectstack/spec, touching 5 documentable anchor(s).

7 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/wire-format.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/concepts/metadata-lifecycle.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/data-modeling/formulas.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/deployment/production-readiness.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/protocol/kernel/http-protocol.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/protocol/objectql/state-machine.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/ui/forms.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))

⛔ 3 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-1.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))
  • content/docs/releases/v17/17-7.mdx (via /meta/object/:name (route, a path literal in a comment on a changed line))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • the SDK route bridge reached 58 of 210 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 171 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 59547058aeb3eccde6f267293367dabf5ef82e59 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 7c1a3044673f58300ebeed81119a2aa297eacc45 — the merge of head cd36d2103e04e824c8dbb0dc973e87a0709dc2bf into base 59547058aeb3eccde6f267293367dabf5ef82e59, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 7c1a3044673f58300ebeed81119a2aa297eacc45 && git checkout 7c1a3044673f58300ebeed81119a2aa297eacc45
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 59547058aeb3eccde6f267293367dabf5ef82e59 cd36d2103e04e824c8dbb0dc973e87a0709dc2bf && git checkout -B drift-repro 59547058aeb3eccde6f267293367dabf5ef82e59 && git merge --no-ff cd36d2103e04e824c8dbb0dc973e87a0709dc2bf

node scripts/docs-audit/affected-docs.mjs --json 59547058aeb3eccde6f267293367dabf5ef82e59

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 59547058aeb3eccde6f267293367dabf5ef82e59 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head b45e452 · 2026-10-30T12:36Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b45e4528df7096902b522423304ffaa5bec9e5f5
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts + packages/rest/src/meta-item-read-routes.ts, packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-action-activation-reads.ts, packages/metadata-core/src/object-schema-fls.ts, content/docs/references/api/protocol.mdx

Rendered 2026-10-30T12:46Z on the schema face (reviewFace hits: packages/spec/src/api/protocol.zod.ts). Inputs: card #28044 (body + 3 comments), PR #28094 body, file list and net diff against main 8cb8c7f4, the check-runs on the head. The remainder of the diff (.changeset/*, the .test.ts, the generated protocol.mdx) is the lane seat's ACCEPT at TIER_DEFAULT; the rest code was READ to judge the schema file, not reviewed.

① Derived judgments

  • Accept set: unchanged — right. No request shape, header or query moves; GetMetaItemResponseSchema and GetMetaItemCachedResponseSchema keep every key and type (only one .describe() string changes).
  • Public surface, HTTP behaviour: a cached GET /meta/object/:name that carries disabledActions is now answerable 304 — right. Read in meta-item-read-routes.ts on the head: besideJudged is now Object.keys(cachedRegistration).length > 0 (flow registration alone stands the 304 down), and the If-None-Match compare at the fingerprinted ETag runs for objects (audienceFolds). The protocol's own ifNoneMatch is already undefined for objects, so getMetaItemCached never judges them; the REST compare is the one judge.
  • Validator: the served list is folded as a fifth dimension — right. cachedDisabledActionsFingerprint(cachedDisabledActions.disabledActions) hashes exactly the value placed in cachedBeside, so the ETag describes the representation sent (the getMetaItemCached folds the SUPPLIED organization scope into the ETag, but the effective scope can differ — a cache validator may not describe the representation actually served #16525 lesson the card names). undefined folds '' and foldVisibilityFingerprintIntoEtag(etag, '') returns etag unchanged, so a no-ledger object keeps its byte-identical ETag; [] goes through objectFieldVisibilityFingerprint(['[]']), a one-element input, so it hashes to a non-empty dimension and absent ≠ []. Order is deterministic: switchedOffActionsOf (protocol-action-activation-reads.ts) walks body.actions in declaration order, so the same switch set always serialises the same way and a 304 is not lost to ordering.
  • Response body: unchanged — right; cachedEnvelope and translateMetaEnvelope inputs are untouched.
  • GET /meta/object (list read): the dev's claim that it has no cached/ETag door holds — meta-collection-routes.ts on the head contains no ETag, If-None-Match or 304 handling, so nothing there to fold or pin.
  • Generated docs: content/docs/references/api/protocol.mdx:1462 follows the new describe string exactly; no other generated line moved — right.

② Semver level

  • Changeset .changeset/28044-object-read-etag-folds-disabled-actions.md: @objectstack/rest: patch, @objectstack/spec: patch — matches what the diff publishes (a behaviour fix in rest; a describe-string change in spec that ships in the generated docs and .d.ts). ⛔ Not skip-changeset, correctly: both are released packages.
  • Clause-②: no on the PR body and in the changeset body, no arm — the closed spelling clause2-line.mjs reads; correct, since the accept set and the response shape do not widen or narrow (a 304 the route already answered for other objects is not a surface change).
  • Metadata-protocol: none on both carriers — correct: nothing an author can write changes. AGENTS.md step 3: kind none ⇒ patch. ✓ Both lines present on both carriers; the Check Changeset and PR-body guards check-runs are green.

③ Boundary flags

  • Dev flag: check:dual-build-cjs-loads left to CI → answered by the head's check-runs below (Lint · CI, release & engine gates green; Build Core still pending at render time).
  • Dev flag: the list read has no cached door → verified in the tree (①), answered.
  • Card: "a possible touch in packages/metadata-protocol" → not needed; the protocol's disabledActions === undefined stand-down at protocol.ts:14562 is unreachable from the REST object door (REST passes ifNoneMatch: undefined under audienceFolds) and remains correct for any other caller of getMetaItemCached. Answered.
  • Card: "Correct the two docblocks' 'never answered 304' sentence in the same PR" → NOT met on this head; see ④ (must-fix). The seat's claim narrowed its spec surface to the one .describe() string, but the card's requirement and the file's own consistency are the standard here.
  • Card open_questions: none filed. No other dev flag.
  • Check-runs on b45e4528 at render: green — Auto Label, Build Docs, Build UI Deps, Check Changeset, Check Documentation Links, Check PR Size, Dogfood Regression Gate, Flag docs affected, Governed Surface Queue Guard, Lint & Repo Gates, Lint · CI/release/engine, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling, Lint · scoped ×3, Live E2E UI, PR-body guards, Plan Test Core shards, Spec property liveness, Test UI, Type Check · debt ledger, Type Check · source gates. Still pending: Build Core, Build & E2E UI, Test Core 1/8–8/8, Type Check UI, Type Check · consumer gates, Type Check · workspace. No failure.

④ Design judgment

packages/spec/src/api/protocol.zod.ts, judged against the card's ruling and its first consumer, packages/rest/src/meta-item-read-routes.ts on this head.

  • The changed .describe() on GetMetaItemResponseSchema.disabledActions ("read live and folded into the ETag, so a cached read revalidates to 304 only while the switched-off set is unchanged; it is absent on a draft read") now tells the truth about the REST door: the list IS folded (disabledActionsFingerprint is the outermost foldVisibilityFingerprintIntoEtag argument at the ETag build), a 304 comes back only when normalizeIfNoneMatch(If-None-Match) equals the folded value, and that value moves whenever the served list moves (off, on, attach, detach). The rest of the sentence (presence only on an object read with a ledger, [] vs absent, draft read absent, beside item) is unchanged and still true. ✓
  • MUST-FIX (FAIL) — left-behind contradiction in the same hits file. The docblock above GetMetaItemResponseSchema (head lines 636–639) still reads: "On the cached default door too, out of the ETag. A read that carries it is never answered 304, for the reason {@link GetMetaItemCachedResponseSchema} gives for registration." On this head that is false for the door it names (the cached default door answers 304 and the list is in its ETag), and it sits 100 lines above a .describe() that says the opposite. This is exactly the sentence card rest: a cached GET /meta/object/:name that carries disabledActions is never answered 304 — fold the switched-off names into the ETag so object reads revalidate again #28044 required corrected in the same PR ("Correct the two docblocks' 'never answered 304' sentence"), and it ships as TSDoc on the exported schema. Fix: rewrite the bullet to "On the cached default door too, folded into its ETag: a 304 comes back only while the switched-off set is unchanged (the REST door folds the served list; GetMetaItemCachedResponseSchema's own etag does not carry it)".
  • SHOULD-FIX — GetMetaItemCachedResponseSchema (head lines 2379–2400: the [#27842] docblock paragraph and the disabledActions describe, mirrored at protocol.mdx:1335) says the list is "never part of etag" and "a read that carries it is never answered notModified". Read against protocol.ts:14473–14577 these stay literally true of the PROTOCOL method's etag and notModified fields, so they are not false; but the reason given ("a 304 would keep the caller on the list it was served before the switch") now describes a problem the REST layer solves, and a reader of this schema alone will conclude the HTTP read is never 304. Add one clause pointing at the REST door's fold so the two schemas read as one mechanism. Not a FAIL on its own.
  • Tried to break the fold: (a) weak ETags — W/"…" is built after the fold and normalizeIfNoneMatch strips both forms, fine; (b) undetermined mask posture returns no ETag before the fold, fine; (c) cross-dimension collision between audienceFingerprint and disabledActionsFingerprint when one is empty — a theoretical 32-bit FNV collision of a withheld-view-name set against a JSON list string, the same property the existing intake/audience dimensions already have; not a finding; (d) the fold reads result.disabledActions as the protocol returned it, the same reference the envelope serves — no second read, no skew.

Implemented-by: claude/project-thread-kwainq
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: FAIL

…ist and the ETag

The GetMetaItemResponseSchema docblock still said a read that carries
disabledActions is never answered 304 on the cached door, and the cached
response schema did not say that the REST door folds the served list
into the ETag it checks. Both now say what the REST door does.

Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016WV4d1Np3hU7RQnA13YZcL
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head fd4f08b · 2026-10-30T12:50Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: fd4f08b58a8b175e10a59b2a0dc7804231761cea
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts + packages/rest/src/meta-item-read-routes.ts, packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-action-activation-reads.ts, packages/metadata-core/src/object-schema-fls.ts, content/docs/references/api/protocol.mdx

Delta review rendered 2026-10-30T12:52Z on the schema face (reviewFace hits: packages/spec/src/api/protocol.zod.ts), superseding record 6523188836 (head b45e4528, FAIL). Inputs: card #28044 (body + comments), PR #28094 body and file list, the one-commit delta b45e4528..fd4f08b5 and the net diff against main 8cb8c7f4, the check-runs on fd4f08b5. The delta touches two files only: packages/spec/src/api/protocol.zod.ts (prose: two docblock paragraphs and one .describe()) and the regenerated content/docs/references/api/protocol.mdx:1335. No code, test, changeset or PR-body carrier moved since the first record; ① and ② below restate the first record's judgments where they still stand and re-read the two files that changed.

① Derived judgments

  • Accept set / response shape: unchanged on the delta and on the net diff — right. The delta is prose only; GetMetaItemResponseSchema and GetMetaItemCachedResponseSchema keep every key and type.
  • HTTP behaviour (unchanged since b45e4528, re-read in the cited code): the cached GET /meta/object/:name folds the served disabledActions into the ETag as the outermost foldVisibilityFingerprintIntoEtag dimension (meta-item-read-routes.ts:883–895), besideJudged is set only by the flow registration, and the REST compare at :900 is the one 304 judge for objects (the protocol receives ifNoneMatch: undefined under audienceFolds). undefined folds '' (ETag byte-identical), [] hashes a non-empty dimension (absent ≠ []), order is deterministic (declaration order from switchedOffActionsOf) — right.
  • Protocol-level description (GetMetaItemCachedResponseSchema): "never part of etag" and "this method never answers notModified for a read that carries it" — right; read against protocol.ts:14473–14577 the method's etag hashes the document plus scope only, and the disabledActions === undefined guard at :14562 still stands down its own 304. The new parenthetical about the REST door is right against meta-item-read-routes.ts on this head.
  • Generated docs: protocol.mdx:1335 follows the new cached-response describe byte for byte; :1462 (the item-read describe corrected in the first round) is unchanged; nothing else in the generated file moved — right. The docblock edits are TSDoc and do not project into gen:docs, correctly.

② Semver level

  • Unchanged from the first record and still right: .changeset/28044-object-read-etag-folds-disabled-actions.md (@objectstack/rest: patch, @objectstack/spec: patch) is untouched by the delta; the added prose publishes as TSDoc/docs in @objectstack/spec, which the patch already covers. ⛔ Not skip-changeset.
  • Clause-②: no (no arm) and Metadata-protocol: none on the PR body and in the changeset body — the closed spelling clause2-line.mjs reads; correct (no accept-set or shape change; nothing an author can write changes). AGENTS.md step 3: kind none ⇒ patch.

③ Boundary flags

  • Record 6523188836 MUST-FIX (the GetMetaItemResponseSchema docblock bullet "out of the ETag … never answered 304") → answered: the bullet now reads "On the cached default door too, folded into its ETag" and states the REST door's fold, 304 only while the switched-off set is unchanged, 200 with the new list on a switch — true against meta-item-read-routes.ts on this head.
  • Record 6523188836 SHOULD-FIX (the GetMetaItemCachedResponseSchema [#27842] paragraph and disabledActions describe) → answered: both now say this method never answers notModified for such a read and that the REST door folds the served list into the ETag it checks instead; the generated protocol.mdx:1335 was regenerated to match.
  • Claim 6521616389 widened to those passages, prose only — the delta stays inside that surface (two docblock paragraphs, one describe string, the generated line). No other packages/spec line, no metadata-protocol path touched.
  • Sweep for other prose in the PR's files or the cited code that still contradicts the runtime: every remaining "kept out of the ETag" / "never answered 304" / "never answered notModified" sentence on this head (protocol.zod.ts:610, :732, :2396; protocol.mdx:1334, :1461; content/docs/automation/flows.mdx:2315; meta-item-read-routes.ts:763, :785) is about the flow registration, which the REST door still stands the 304 down for — all still true. Nothing left behind about disabledActions.
  • Earlier dev flags (list read has no ETag door; check:dual-build-cjs-loads to CI) stand answered as in the first record. Card open_questions: none.
  • Check-runs on fd4f08b5 at render: green — Auto Label, Check Documentation Links, Check PR Size, Flag docs affected by code changes, Governed Surface Queue Guard, PR-body guards, Spec property liveness. Pending (the push is minutes old): Check Changeset, Lint · CI/release/engine, Lint · ESLint, Lint · PM dispatch-gates, Lint · docs & code guards, Lint · repo & tooling, Lint · scoped ×3, Type Check · consumer gates, Type Check · debt ledger, Type Check · source gates, Type Check · workspace, and the build / test / UI jobs not yet reported. No failure. The first head b45e4528, which carries the identical code, had 26 green and none failed; the delta is prose plus a regenerated docs line, so the check:docs gate (Type Check · source gates) is the one to watch.

④ Design judgment

packages/spec/src/api/protocol.zod.ts, judged against card #28044 and its first consumer, packages/rest/src/meta-item-read-routes.ts on this head.

  • The GetMetaItemResponseSchema docblock bullet (head lines 636–642) now tells the truth: the REST door folds the list "exactly as served" (cachedDisabledActionsFingerprint(cachedDisabledActions.disabledActions) hashes the same reference cachedBeside serves), a revalidation is 304 only while the folded value matches, and a switch answers 200 with the new list. The .describe() on disabledActions (head :737–746, corrected in the first round) still agrees with it. ✓
  • The GetMetaItemCachedResponseSchema paragraph (head :2382–2390) and describe (:2399–2407) now draw the line in the right place: the protocol method's own etag cannot see the switch, so IT never answers notModified for such a read; the REST door does not rely on that stand-down and folds the list itself. Both halves hold in the cited code (protocol.ts:14562; meta-item-read-routes.ts:787, :894, :900). The two schemas now read as one mechanism. ✓
  • SHOULD-FIX (minor, not a FAIL): the same GetMetaItemResponseSchema docblock introduces the four bullets with "Four measured choices, each the one registration made" (head :620–621). The fourth choice is now, by design, NOT the one registration made — registration stays out of the ETag and stands the 304 down, disabledActions is folded in. One word fixes it ("the first three the one registration made" or "each measured against registration's"). Leave for a follow-up or the next touch of this docblock.
  • Tried to break the corrected prose against edge cases: the no-ledger object (key absent) — the bullet says nothing is folded and the first-round control pin holds the ETag at "etag_task", consistent; [] — "switched-off set is unchanged" covers the empty set, and attach/detach moves the ETag as the describe at :737 implies; a draft read — still "absent", and the cached branch is unreachable for a draft read (isDraftRead bypasses it). No contradiction found.

Implemented-by: claude/project-thread-kwainq
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract-review-claim: session_016WV4d1Np3hU7RQnA13YZcL · head cd36d21 · 2026-10-30T12:53Z

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: cd36d2103e04e824c8dbb0dc973e87a0709dc2bf
Local-runs: none
Scope: packages/spec/src/api/protocol.zod.ts + packages/rest/src/meta-item-read-routes.ts, packages/metadata-protocol/src/protocol.ts, packages/metadata-protocol/src/protocol-action-activation-reads.ts, packages/metadata-core/src/object-schema-fls.ts, content/docs/references/api/protocol.mdx

Second delta review rendered 2026-10-30T12:54Z on the schema face (reviewFace hits: packages/spec/src/api/protocol.zod.ts), superseding record 6523293304 (head fd4f08b5, PASS). Inputs: card #28044, PR #28094 body and file list, the one-commit delta fd4f08b5..cd36d210 and the net diff against main 8cb8c7f4, the check-runs on cd36d210. The delta is one TSDoc line in packages/spec/src/api/protocol.zod.ts:620–621; no other file, no code, test, changeset, generated line or PR-body carrier moved, so ① and ② of record 6523293304 stand as written and are restated only by reference.

① Derived judgments

  • Accept set, response shape, HTTP behaviour, protocol-level description and generated docs: unchanged on the delta; the judgments of record 6523293304 ① hold on this head (re-checked: git diff --stat origin/main...cd36d210 lists the same five files, with only protocol.zod.ts moving by one line since fd4f08b5). The TSDoc edit does not project into gen:docs, so content/docs/references/api/protocol.mdx rightly did not move.

② Semver level

  • Unchanged and still right: @objectstack/rest: patch, @objectstack/spec: patch; Clause-②: no (no arm) and Metadata-protocol: none on the PR body and in the changeset body. A TSDoc wording change publishes inside the spec patch already declared. ⛔ Not skip-changeset.

③ Boundary flags

  • Record 6523293304 SHOULD-FIX (the docblock intro "Four measured choices, each the one registration made") → answered: it now reads "Four measured choices, the first three the ones registration made:", which is true of the four bullets that follow — beside item, absent = no ledger, [] = ledger with nothing off (the three registration made), and the fourth, "folded into its ETag", which registration did not make and the REST door does not do for it (meta-item-read-routes.ts:785, besideJudged on registration alone).
  • No other open flag. Card open_questions: none. The claim's prose-only widening covers this line.
  • Check-runs on cd36d210 at render: green — Check Documentation Links, Check PR Size, Governed Surface Queue Guard, PR-body guards. Pending (the push is minutes old): Auto Label, Check Changeset, Flag docs affected by code changes, Spec property liveness, Lint · scoped · query-options erasure (queued), and every other lint / type-check / build / test / UI job not yet reported. No failure. For the record: the superseded head fd4f08b5 shows Lint & Repo Gates and TypeScript Type Check as failure — those are the aggregate jobs of run 41058350780, whose conclusion is cancelled (superseded by this push) with every child lane cancelled, not a red gate. The check:docs gate (Type Check · source gates) has therefore not yet reported green on any head carrying the regenerated protocol.mdx:1335; it is the one to watch before landing.

④ Design judgment

packages/spec/src/api/protocol.zod.ts, judged against card #28044 and its first consumer packages/rest/src/meta-item-read-routes.ts on this head: the one changed line removes the last inconsistency inside the GetMetaItemResponseSchema docblock; the four bullets, the disabledActions describe (:737–746), the GetMetaItemCachedResponseSchema paragraph and describe (:2382–2407) and the generated protocol.mdx:1335 / :1462 all say the same thing the REST door does — the served list is folded into the object read's ETag, 304 only while the switched-off set is unchanged, the protocol method's own etag does not carry it. Every remaining "kept out of the ETag / never answered 304 / never notModified" sentence in the PR's files and cited code is about the flow registration and still true. No must-fix, no should-fix.

Implemented-by: claude/project-thread-kwainq
Reviewed-by: session_016WV4d1Np3hU7RQnA13YZcL

VERDICT: PASS

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#4 · os-elon-musk · session_016WV4d1Np3hU7RQnA13YZcL

维护者速读

  • 改了什么:默认缓存门 GET /meta/object/:name 把随对象下发的 disabledActions(被启用台账关掉的动作名)折进 ETag;没变化时重新验证答 304,管理员开关动作后答 200 并换新 ETag。spec 里这个键的描述和两段文档块同步改成"折进 ETag"。
  • 为什么改:feat(spec,metadata-protocol,rest,objectql,runtime): GET /meta/object serves switched-off packaged actions beside the item, and the disabled-action refusal reads as a plain sentence #27964 为了正确性让每个带这个键的对象读都放弃 304;默认部署都挂着台账,所以控制台每次读对象都重新下载整个定义。
  • 风险与回滚:Clause-②: no;只动响应头里的校验值,响应体不变;没有台账的对象 ETag 与以前逐字节相同。回滚即还原本 PR,后果只是对象读又不答 304。
  • 先看:packages/rest/src/meta-item-read-routes.ts 的 besideJudged 与文件末尾的 cachedDisabledActionsFingerprint,再看 meta-object-cached-disabled-actions.test.ts。
  • 两分钟验证:pnpm --filter @objectstack/rest exec vitest run src/meta-object-cached-disabled-actions.test.ts,8 passed。
  • 需要你做的:无(非受管;契约复审 PASS;席位按本任落地授权落地)。

席位意见:ACCEPT。

席位核对

Head-sha: cd36d2103e04e824c8dbb0dc973e87a0709dc2bf

  • Shape: 5 files: the REST door's ETag fold and its 5 pins, the spec prose (the disabledActions .describe() on both response schemas and the two docblock passages), the regenerated protocol.mdx, and a patch changeset for @objectstack/rest and @objectstack/spec. check-governed-merges --pr 28094: NOT governed, +132 / −43.
  • PR body correction: the body describes the spec change as "only the .describe() string" and its last verification block names head b45e4528. Two later commits (fd4f08b, cd36d21) also corrected the GetMetaItemResponseSchema and GetMetaItemCachedResponseSchema docblocks and the cached disabledActions .describe() on contract review, prose only; claim 6521616389 was widened to cover them. The spec api/protocol* tests (225) passed after each of those commits.
  • Contract review: FAIL 6523188836 on b45e4528 (one docblock still said the list was never in the ETag) → fixed → PASS 6523293304 on fd4f08b5 → its one should-fix taken → PASS 6523337114 on this head, no findings.
  • Gate check-runs on the head: 50 runs, 41 success, 9 skipped, 0 failure/pending (Type Check · source gates with check:docs included); mergeable_state clean. The red aggregates on b45e4528 and fd4f08b5 were lanes cancelled by the later pushes.
  • Landing: under the maintainer's landing grant for this seat term (seat 4 thread, 2026-10-29 22:04Z): pr_ready + automerge_enable through the relay.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 30, 2026 13:18
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 30, 2026
Merged via the queue into main with commit 11a7f96 Oct 30, 2026
51 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-kwainq branch October 30, 2026 13:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

1 participant