Repository navigation
lint: a lookup or master_detail field whose reference is a stored-metadata-body table saves, builds and validates, but the engine never hydrates it — refuse it at authoring time #24677
Description
Filing gate ①, a metadata-authoring trap: the authoring doors accept a declaration the runtime can only refuse. It is the authoring half of #24358's open question (a), ruled B by the maintainer. Filed by domain:engine seat 1 (seat post #6367) · os-project-manager · session_01JtSQQ3EWsbNPzyYMug1tXy. ⛔ Not a claim. Grade and lane are triage's; the seat reads it as domain:spec (packages/lint, and packages/spec's field describe).
The ruling
#24358's dev report 6253491408 put one open question to the maintainer.
- The question: besides the engine guards PR fix(objectql)!: expand and the master-detail parent root never hydrate a stored-metadata-body table #24382 landed, should the authoring doors also refuse a
lookupormaster_detailwhosereferenceis a stored-metadata-body table? - The options: A, engine guards only; B, also an authoring refusal.
The maintainer, in this seat's session:
- 「24358 同意」, to the seat's recommendation B;
- then 「ok」, to the seat's follow-up recommendation on the rule's shape:
- the refusal keys on the existing closed set (
isStoredMetadataBodyObjectin@objectstack/spec/kernel), the same predicate every other refusal of this family reads; - ⛔ no new authorable object key now (an
enable.relationshipTarget-style flag was weighed: today it has no pull beyond these two platform tables, andenable.apiEnabled: falsealready covers an app's unexposed object at$expand, security(data, analytics): a lookup target's exposure declaration is not judged when the data door's$expand, or the dataset door's dimension-label pass, reads it — detail withheld pending maintainer #22661); - so that if an app needs it later, swapping the set for a declaration is a change of the predicate's source, not a new rule.
- the refusal keys on the existing closed set (
The trap (read on origin/main f89d29e382)
-
The family.
STORED_METADATA_BODY_OBJECTSissys_metadataandsys_metadata_history(packages/spec/src/kernel/stored-metadata-body-objects.ts). -
The runtime refuses every relationship read of it. This has been so since PR fix(objectql)!: the one-hop read through a reference never reads a stored-metadata-body table; the lint refuses it #24344 ([security] Stored-metadata-body family: a formula field's one-hop read through a reference is an exit the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24316) and PR fix(objectql)!: expand and the master-detail parent root never hydrate a stored-metadata-body table #24382 ([security] Stored-metadata-body family: two relationship hydrations outside the one-hop read (
expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358):- the one-hop formula loader refuses a hop into the family;
- the engine's
expandkeeps the field's stored id and never nests the row; - the master-detail
parentroot stays unbound in all three binders, so a rule readingparentrefuses every write it judges.
-
The authoring doors accept the declaration. [security] Stored-metadata-body family: two relationship hydrations outside the one-hop read (
expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358's dev measured it on a booted stack:- the metadata save door accepted a
lookup(1/1) and amaster_detail(1/1) into the family; os validateandos buildaccepted both (exit 0).
So an author (an AI writing a metadata app most of all) can declare a relationship that never works. A lookup only ever shows an id. A master-detail child with any
parentrule refuses every write, and nothing at save says why. - the metadata save door accepted a
-
The rest of the family already refuses at authoring time, with the one prescription
STORED_METADATA_BODY_PRESCRIPTION:- a hook bound to a family table (
HookSchema,data/hook.zod.ts); - a flow write node aimed at one (
FlowSchema,automation/flow-node-config-refusals.ts); - a formula hop through a reference into one (
packages/lint/src/validate-expressions.ts,storedMetadataBodyReferencesOf, [security] Stored-metadata-body family: a formula field's one-hop read through a reference is an exit the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24316).
The reference field itself is the one declaration still accepted.
- a hook bound to a family table (
Ask
- One authoring refusal. A
lookupormaster_detailfield whosereferenceis in the family is refused where the hop refusal already runs, soos validate,os buildand the object save door refuse it alike.- The refusal names the field, the target and the rule, and ends on
STORED_METADATA_BODY_PRESCRIPTION. - It keys on
isStoredMetadataBodyObject, the existing collector (storedMetadataBodyReferencesOf) or its twin. - Pick the issue kind by the lint's existing families; ⛔ no new error code if one fits.
- The refusal names the field, the target and the rule, and ends on
- The convention written where an author reads it: the
referencedescribe inpackages/spec/src/data/field.zod.ts, and the field-types page undercontent/docs/**. A reference to a stored-metadata-body table is refused, and why: its rows are served only through the metadata protocol's own door. - Pins:
- a
lookupand amaster_detailinto each family table are refused at each door; - a reference to an ordinary platform table still saves (the control);
- the existing hop refusal is unchanged.
- a
- ⛔ Not asked:
- no change to the engine guards (they stay for installed artifacts, which run no authoring rule);
- no new object key;
- no change to
apiEnabled's meaning.
- Exposure: 0 in-repo producers ([security] Stored-metadata-body family: two relationship hydrations outside the one-hop read (
expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358's dev: 0 files reference a family table; control, 26 reference an ordinary platform table). So the refusal breaks nothing shipped, and the changeset says what an app that declared such a field sees. Clause-②: a narrowing of what the authoring doors accept, sono (narrowing), with aCONTRACT_REVIEW_TIERreview on the narrowing face before the queue.
Dedupe
Semantic searches over this repository's issues, closed included:
- "lint refuse lookup master_detail reference to sys_metadata stored metadata body table at save": 34 hits. The nearest are [security] Stored-metadata-body family: two relationship hydrations outside the one-hop read (
expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358 (the engine guards, closed) and [security] Stored-metadata-body family: a formula field's one-hop read through a reference is an exit the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24316 (the one-hop refusal, closed); the rest are othersys_metadatadefects. - "authoring rule reference field into metadata table never expands": 5 hits, none on this rule. lint: no reference-integrity or option-key validation for app metadata #3583, closed, is lint reference integrity in general.
#22661 ($expand and apiEnabled, closed) is a different declaration. None asks for this refusal.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 20, 2026
Triage: p2 ·
bug· lanedomain:spec·area:records·pm:queue· triage seat ·session_01AavokzJ5DndAwitDXvKy4U· 2026-10-20T00:59ZTriage seat (objectstack-wide, seat post #6015). ⛔ Not a claim, ⛔ not a dispatch. Fresh read on
origin/main8e14ce0574.Direction is ruled. This is #24358's open question (a), ruled B by the maintainer (「24358 同意」, then 「ok」 to the rule's shape). It keys on the existing closed set
isStoredMetadataBodyObject, with ⛔ no new authorable key.Lane.
packages/lintplus the spec field describe, which isdomain:spec(SKILL.md:163,:165).Why p2 and not #24358's p1.
Protocol class (a new authoring refusal, a narrowing): the 「是否提前」 question goes to the maintainer. Triage lean: not for 18.0, since the runtime guard already holds.
findingcomes off.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Seat hold ·
domain:specseat 2 (#18549) · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-20T01:38Z · ⛔ Not a claim.This card is in this seat's lane queue, and the seat has a free slot. It is not dispatched: under rule
6261447528, a protocol-class card waits for the maintainer's 「是否提前」 answer. The seat puts the question to the maintainer in its session chat at 2026-10-20T01:38Z, together with #24668 and #24669. The answer is recorded here when it comes.Seat lean: do not pull it forward, as triage leans.
sys_metadata/sys_metadata_historyis refused, so nothing leaks.isStoredMetadataBodyObject), the rule shape the maintainer approved (「24358 同意」, then 「ok」). That makes it a small narrowing that a later minor can carry as a warning-first or 19.0 refusal, unless the maintainer wants it in 18.0.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Ruled: pull into 18.0 · recorded by
domain:specseat 2 (#18549) · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-20T03:39ZThe maintainer's word, in this seat's session chat at 2026-10-20T03:39Z, verbatim: 「都派」. It answers the 「是否提前」 question (rule
6261447528) that the seat put to the maintainer for this card, #24668, #24677 and #24669 together, after the seat hold6281303022. All three land in 18.0.Label change in this act:
target:v18is added, executing that word. The source is the maintainer, verbatim 「都派」, in seat 2's session chat. The grade, the lane andarea:*are unchanged.The seat dispatches in grade order as
batch 2slots free: #24668 and #24677 (p2) now, #24669 (p3) at the next free slot.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 1 · 2026-10-20T03:43Z · the authoring doors refuse a
lookup/master_detailwhosereferenceis a stored-metadata-body table (ruled B on #24358)Pulled into 18.0 on the maintainer's word in this session's chat, verbatim: 「都派」 (record
6282780489). The rule's shape was already ruled: 「24358 同意」, then 「ok」, as the card quotes. The seat'sbatch 2(「并发2」) holds it beside #24668.Session:
session_019oFY6Hi35J7atvgVFnmoaK(the seat; the dev is anos-devsubagent of this session)Account:
zhuangjianguo(the seat's linked user asGET /useranswers it; always the card's assignee)Branch:
claude/issue-24677-reference-into-metadata-body-refusedWorktree:
objectstack-issue-24677Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface (paths on
origin/main7e7e605de3or later; stop on a breach and explain it in the report):packages/lint/src/validate-expressions.ts(besidestoredMetadataBodyReferencesOf, about:1736) or a sibling rule module, plus its registration inauthoring-rules.tsand its runtime type set, so thatos validate,os buildand the object save door refuse it alike;referencedescribe inpackages/spec/src/data/field.zod.tsand the regenerated reference pages;content/docs/**;.changeset/24677-*.md;packages/metadata-protocol/src/**.test.ts(domain:engine, [PM seat] domain:engine — 🟢 os-project-manager #6367), and any consumer fixture the sweep finds.apiEnabled. Notcontent/docs/releases/.Container & model:
S–M,mode:subagent,model: default tierfor the draft. The review before the queue runs atCONTRACT_REVIEW_TIERon the narrowing face.Clause-②: no (narrowing) (as the card spells it; the dev re-spells it as the clause-② reader decides)
Responsibility: this lane's authoring doors, which accept a declaration the runtime only refuses · the platform path that covers the runtime half is PR fix(objectql)!: the one-hop read through a reference never reads a stored-metadata-body table; the lint refuses it #24344 / PR fix(objectql)!: expand and the master-detail parent root never hydrate a stored-metadata-body table #24382's engine guards; no authoring rule covers the declaration · it is reached by any author or AI declaring a relationship into
sys_metadata/sys_metadata_history. [security] Stored-metadata-body family: two relationship hydrations outside the one-hop read (expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358's dev measured 0 in-repo producersThread-read: 6282780489
Serial constraints cleared:
validate-expressions.ts,authoring-rules.ts,field.zod.tsorstored-metadata-body-objects.ts.validateListViewFieldRefsskips it and no other rule judges aviewwrite's binding #24668 (this seat, dispatched beside it) works invalidate-list-view-field-refs.ts/reference-integrity-suite.ts; disjoint, apart from possiblyauthoring-rules.tsregistration, which the second to land merges.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Dispatched ·
domain:specseat 2 · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-20T03:47Z · for claim6282819504.mode:subagent: the dev is anos-devsubagent of this session, on branchclaude/issue-24677-reference-into-metadata-body-refused, at the default tier.batch 2: it runs beside #24668. #24669 waits for the next free slot.The dev verifies the card's premises before editing, starting with the booted or real-door reading, and stops with a fork on any breach. The spec-lane fixture rule applies: the consumer packages run after the change, and every out-of-lane fixture is listed for the seat to declare. The engine rider for the protocol-layer pin is on #6367 (
6282830389).The dev reports here as an
os-dev-reportcomment and opens a draft PR. The contract review (narrowing face) runs before the queue.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{
"issue": 24677,
"status": "done",
"branch": "claude/issue-24677-reference-into-metadata-body-refused",
"pr": "#24800",
"session": "session_019oFY6Hi35J7atvgVFnmoaK — the dispatching seat's id (this run is its os-dev subagent; the relay resolved the same id)",
"premise_still_valid": true,
"summary": "The expression rule's per-object pass (runStackExpressionPasses, packages/lint/src/validate-expressions.ts) now refuses a lookup or master_detail whose reference is in STORED_METADATA_BODY_OBJECTS. It sits beside the #24316 hop refusal and is keyed on that refusal's own collector (storedMetadataBodyReferencesOf) and its per-object index. It is located at the field's reference (object 'NAME' · field 'FIELD' reference) under the existing expression-invalid rule: no new code, no registry change, and runtimeTypes unchanged, so os validate, os build and the object save door give one finding. The message names the field, its type, the target and the rule, and ends on STORED_METADATA_BODY_PRESCRIPTION. The FieldSchema.reference describe (field.zod.ts:1476, the field's own; :989 is a grid cell column's) and the field-types page state the convention, and the reference pages were regenerated by gen:docs only. Premises held. P1, before at 4b114ed: os validate and os build exit 0 on 4/4 family probes and 2/2 controls; the HTTP save door answered 200 on 4/4 and on 2/2 controls, with a gate-live control (sqrt formula) at 422. After, at 5e54169: CLI exit 1 on 4/4, controls exit 0; HTTP 422 INVALID_METADATA on 4/4, controls 200. P2: located as above. P3: 0 in-repo producers over examples, packages/apps, packages/platform-objects, the create-objectstack templates and packages/qa (repo-wide 0; 0 again on origin/main a43405d), against a control of 45 files that reference an ordinary platform table. files_changed: .changeset/24677-reference-into-stored-metadata-body-refused.md; content/docs/data-modeling/field-types.mdx; content/docs/references/data/field.mdx; content/docs/references/data/object.mdx; content/docs/references/system/migration.mdx; packages/lint/src/authoring-rules.ts (comment only; #24668 may also touch this file, and whichever lands second merges); packages/lint/src/validate-expressions.ts; packages/lint/src/validate-expressions.stored-metadata-body-reference.test.ts; packages/metadata-protocol/src/protocol.runtime-authoring-gate.test.ts (declared rider, #6367 comment 6282830389); packages/metadata-protocol/src/stored-metadata-body-family.pin.test.ts (declared rider: one family row, 28 to 29); packages/spec/src/data/field.zod.ts. deviations: (1) The refusal also covers a user field whose authored reference names the family, because the collector's type set is REFERENCE_VALUE_TYPES and the parse accepts { type: 'user', reference: 'sys_metadata' } (measured). It is pinned, and the collector's docblock, which claimed user never names the family, is corrected. (2) The changeset grades @objectstack/spec patch (the describe text only) and @objectstack/lint minor; the dispatch read as minor for both. (3) The branch merged origin/main once (37aa1d6). It is 10 commits behind a43405d at open, and none of those commits touches these files. (4) check:type-check-debt exited 3 in the battery (a lock queue-timeout, NOT MEASURED) and was re-run under the lock itself: exit 0. (5) The first ablation call queue-timed out (99, nothing mutated), and it had set OS_VERIFY_LOCK_SLOT inside -c rather than on the lock script; the re-run set it properly. (6) My own rm -rf over a glob that matched a node_modules symlink emptied this worktree's examples/app-todo/node_modules. It was restored with pnpm install --offline before any counted measurement. (7) test:repo and the cli integration tier are not run locally (the derivation does not name them); they are declared to CI. (8) The P3 control is 45 files, against the card's 26: the pattern is broader (string, JSON, builders, SystemObjectName). (9) The clone was deepened (--shallow-since=2026-07-13) for the history-reading gates. (10) I stopped and relaunched my own sweep and gate runners twice, for edits made mid-run. Every reading cited is from the final runs on 5e54169, except the ablation, which ran on 6c2c70f; the later commits change only the message text and add the user pin. (11) Commits carry the model-free trailer pair, and the PR body ends on the AGENTS.md session-URL footer, not the harness reminder's text. No subscribe_pr_activity. pr_body_edits: none.",
"tests": "Head 5e54169, each run under os-verify-lock.sh with --maxWorkers=2. lint: 'Test Files 149 passed (149) / Tests 6965 passed (6965)'; lint typecheck exit 0. metadata-protocol: 'Test Files 251 passed | 3 skipped (254) / Tests 20818 passed | 19 skipped (20837)'; typecheck exit 0. spec (--project local): '702 passed (702) / 21352 passed | 1 todo'; spec typecheck exit 0. objectql (--project local): 450 / 8490 passed. platform-objects: 79 / 1195. runtime (--project local): 430 / 6779 passed + 19 skipped. cli --project unit: 314 / 4641. os validate and os build on app-crm, app-multi-package, app-showcase and app-todo: 8/8 exit 0 ('Validation passed' / 'Build complete'). No consumer fixture outside lint, spec and content/docs went red, so there is none for a rider. New pins: the lint file has 21 cases (each family table x lookup/master_detail refused at the build, at runAuthoringRules validate/build and at the object door with the build's own finding; user case; stored self re-save refused; stored sibling not charged; 4 controls; hop refusal unchanged). The protocol door block has 8 cases (4 refusals with {code: INVALID_METADATA, status: 422} and nothing landing; draft promotion refused; 2 controls saved; door equals build). Ablation, through scripts/ablation-replace.mjs on 6c2c70f. Mutation: 'if (!target || !type) continue;' became an always-continue with marker 'ablation-24677': anchor 1 to 0, blob e85a4327e28f to 6564a909a8fd. Lint pins (src): 15 red / 11 green of 26; the green are 4 controls, the differential and the 6 hop pins. Lint rebuild: the JS pass emitted the mutation and ablation-dist-preflight found the marker in 4 dist files; the DTS pass failed on the mutation (TS2872/TS2345). The protocol door block (reads lint dist): 6 red / 2 green; the green are the controls. Restore (the tool's trap): blob e85a4327e28f equals HEAD, git diff HEAD empty, whole-tree status clean. Lint rebuilt exit 0; preflight --absent found the marker in 0 of 20 dist files; lint pins 26/26 and door block 8/8.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, on 5e54169: 111 commands. Each was run byte-for-byte with its exit captured before any pipe. --ran printed 'Run reconciliation — 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN' and '✓ dispatch-gates --ran: 111 derived famil(ies) accounted for — 111 run, 0 NOT-MEASURED (a DERIVED zero ...)'. All 111 exited 0, after pnpm check:type-check-debt's queue-timeout exit 3 was re-run under the lock (exit 0, '1 ledger entr(ies) re-measured ... none above its recorded number'). Local-only families: check:dev-prereqs exit 0 and check:skill-frame-freshness exit 0. spec check:generated: 'All 14 generated artifacts are up to date' after gen:docs. check:adr-0087-registration exit 0: the changeset is [BREAKING+bang+clause-②-narrowing], not-required (no-migration-prescription). check-changeset-no-major exit 0. A control-byte self-scan over the 11 changed files found no hit. CI check-runs on PR #24800 at report time: 'No other open PR may claim the same issue' success; 'Packed-tarball smoke (opt-in)' skipped; all others in_progress, among them Lint · repo & tooling gates, Lint · ESLint, Type Check · workspace / source gates / consumer gates / debt ledger, Lint · PM dispatch-gates, Check Changeset, Governed Surface Queue Guard and Check PR Size. They were not waited on, per os-dev.",
"mcp_calls": "0 — no MCP GitHub tool used; reads were single-card REST GETs (gh api) of #24677, #24358, #24316, comment 6282830389 and PR #24800",
"api_writes": "2 relay strokes (3 REST requests), plus git push. (1) fleet-write pr_create, run 39383397755: POST /repos/objectstack-ai/objectstack/pulls (draft) and POST /repos//issues/24800/assignees. Read back as 11709 bytes sent and 11709 stored, identical; assignee zhuangjianguo. (2) fleet-write comment: POST /repos//issues/24677/comments (this report). git push of the branch: 9 pushes (the empty-branch probe, then each commit or merge); not REST writes.",
"pr_body_edits": [],
"open_questions": [],
"out_of_scope_findings": [
"class: b · reach: os validate accepts { type: 'user', label: 'Owner', reference: 'probe_account' } on a probe app (exit 0, 'Validation passed', the CLI built from 5e54169); ObjectSchema.safeParse also accepts a user field with reference 'sys_metadata' · contract: Field.user's docblock (packages/spec/src/data/field.zod.ts, the user builder) says 'target fixed to the sys_user system object', and IMPLICIT_REFERENCE_TARGETS (field-value.zod.ts) says reference on a user field materializes that constant, it does not supply it · Seam: spec:FieldSchema type 'user' + reference → runtime:referenceTargetOf (packages/spec/src/data/field-value.zod.ts:207, which returns the authored carrier first) | renderer: not measured · dedupe words: user field reference not sys_user, user type authored reference override, IMPLICIT_REFERENCE_TARGETS user, Field.user reference parse"
]
}
Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:specseat 2 · sessionsession_019oFY6Hi35J7atvgVFnmoaK· 2026-10-20T08:39Z3983965d6ecbdcafb9a7d159542a6dbaffbd5306onmain.Fixes #24677closed this card ascompleted.origin/main.git patch-id --stableof the merge's diff equals that of the reviewed head5e54169edcagainst its merge base (e84f25a900). What landed is what the contract review6285549857passed.gh-readonly-queue/main/pr-24800-*is gone.pm:dispatchedwas removed in this act.domain:spec,area:records,priority:p2andtarget:v18stay.expand, the master-detailparentroot) are exits the family projection does not reach — reach not yet measured, detail withheld pending maintainer #24358, 「24358 同意」 then 「ok」; pulled into 18.0 on 「都派」):os validate,os buildand the object save door refuse alookup/master_detailfield whosereferenceis inSTORED_METADATA_BODY_OBJECTS, asexpression-invalidat the field'sreference. Ausernaming the family is refused too. Thereferencedescribe and the field-types page state the convention.packages/metadata-protocoltest files ([PM seat] domain:engine — 🟢 os-project-manager #63676282830389,6285334848).userfield accepts any authoredreference({ type: "user", reference: "probe_account" }parses and validates) and the runtime resolves to it, whileField.userandIMPLICIT_REFERENCE_TARGETSsay the target is fixed tosys_user#24808 (auserfield accepts any authoredreference, class b).mainpublishes nothing.@objectstack/lintminorand@objectstack/specpatchride the next Version Packages PR, which is a human act.Generated by Claude Code