You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec(data): a user field accepts any authored reference ({ type: "user", reference: "probe_account" } parses and validates) and the runtime resolves to it, while Field.user and IMPLICIT_REFERENCE_TARGETS say the target is fixed to sys_user #24808
Filing gate: class b, declared ≠ enforced. The spec states one target, and the parse and runtime honour another. Reader: triage, for the grade. The fix is in packages/spec, so domain:spec by the anchoring rule. Filed by domain:spec seat 2 (#18549), session session_019oFY6Hi35J7atvgVFnmoaK, from the #24677 dev's out-of-scope finding. Contract review 6285549857 on PR #24800 confirmed it. ⛔ Not a claim.
Measured (origin/main, read from source; the dev's probe for the CLI)
Declared:
packages/spec/src/data/field.zod.ts:3188: the Field.user builder's docblock says "target fixed to the sys_user system object". The builder spreads ...config after reference: 'sys_user' (:3201-3204), so an authored reference overrides the fixed one.
packages/spec/src/data/field-value.zod.ts:159-169: IMPLICIT_REFERENCE_TARGETS says an authored reference on a user field only materializes that constant.
Not enforced:
referenceTargetOf (field-value.zod.ts:212) returns the authored carrier first.
No FieldSchema refinement compares a user field's reference with sys_user.
object.zod.ts:1656 refuses a foreign target for tree only.
Reach:{ type: 'user', label: 'Owner', reference: 'probe_account' } parses (ObjectSchema.safeParse), and os validate passes it (exit 0, "Validation passed", the CLI built from PR fix(lint)!: a lookup or master_detail into a stored-metadata-body table is refused at author time #24800's head 5e54169edc). The runtime then resolves the field's target to probe_account. So a field the author and the AI read as "a user" behaves as a lookup to another object.
The question for the grade
Refuse a user field whose reference is anything but sys_user, as tree already refuses a foreign target? Or redeclare user as "a lookup whose default target is sys_user"? The decision principle "declared but not honoured: implement or retire, never narrow at the consumer" applies. A refusal is a narrowing, so the protocol-class 「是否提前」 rule (6261447528) applies too.
Dedupe
The finding carried the dedupe words user field reference not sys_user, user type authored reference override, IMPLICIT_REFERENCE_TARGETS user and Field.user reference parse. PR #24800 covers only the family-table case: a user naming sys_metadata is refused there. No card covers the general override.
Decision filed · domain:spec seat 3 (#18883) · baozhoutao · session session_01D6F9axfoPzR7t8G1J7Ytzz · 2026-10-20T10:40Z · ⛔ Not a claim.
Under rule 6261447528, this card's 「是否提前」 question is now on decision card #24854, together with the other two pending domain:spec protocol cards. Nothing on this card moves until the maintainer answers there. The answer is recorded here.
Claim: PM loop round 18
Session: session_01D6F9axfoPzR7t8G1J7Ytzz
Account: baozhoutao (the seat's linked user as GET /user answers it; the card's assignee)
Branch: claude/issue-24808-user-field-reference-fixed
Worktree: objectstack-issue-24808
Domain: domain:spec
Seat: domain:spec#3 (seat post #18883)
File surface (at origin/mainb42c621c05 or later; stop on a breach and explain it in the report):
packages/spec/src/data/field.zod.ts (Field.user: the authored reference no longer overrides sys_user) and field-value.zod.ts (referenceTargetOf);
the object/field refinement or lint verdict that judges a user field's reference (precedent: the tree foreign-target refusal, object.zod.ts:1656);
if one applies, an ADR-0087 conversion that rewrites such a field to a lookup on its target;
any in-repo producer converted to Field.lookup;
tests, generated artifacts, and .changeset/24808-*.md.
Container & model: M, mode:subagent, model: default (no path-derived mandate)
Clause-②: no (provisional: the dev resets it from the measurement under 「甲」, yes (narrowing) if a conversion carries the refusal)
Responsibility: n/a — a declared fixed target that parse does not enforce (ruled A, #24854)
Thread-read: none
Serial constraints cleared: read at this act; the dev re-checks open PRs touching field.zod.ts / field-value.zod.ts / object.zod.ts before opening its own.
Under 「甲」 (#60156292005854, derivation on #24854):
First act: measure the producers that author user with a foreign reference.
If an ADR-0087 conversion can rewrite such a field (to a lookup on that object), A's refusal goes into 18.x with it.
Otherwise, 18.x warns and the refusal waits for 19.0.
Either way, producers found in this repository are converted in the same PR, per the director's delta.
Read on origin/mainb42c621c05 at 2026-10-20T15:47Z: Field.user spreads ...config after reference: 'sys_user', and referenceTargetOf returns the authored carrier first.
{
"issue": 24808,
"status": "done",
"branch": "claude/issue-24808-user-field-reference-fixed",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24942",
"session": "session_01D6F9axfoPzR7t8G1J7Ytzz",
"premise_still_valid": true,
"summary": "MEASUREMENT (objectstack-ai/objectstack @ b42c621c05): producers that author a `user` field with a reference other than sys_user number 0 in examples/**, 0 in package source, and 0 among `Field.user({ reference })` calls (67 Field.user lines, none passes reference). YAML: 0. Test fixtures: 2, packages/spec/src/data/field-value.test.ts:91 (my_people) and packages/lint/src/object-graph.test.ts:39 (crm_person). Sibling objectui: 1 renderer fixture, userCell.readGate-10535.test.tsx:157 (crm_member), read at an old local checkout b434f5f47. The scans were a literal regex, the Field.user call form, and a six-line window over 413 user-type lines in 176 files; the window's 81 other candidates were hand-read as neighbouring lookups. GA-GATE DECISION: NO conversion can carry it. ADR-0087 D2 is lossless-only, and user to lookup is not lossless. `user` is outside the delete walk (DELETE_SEMANTICS_REFERENCE_TYPES = master_detail/lookup/tree, objectql engine.ts), while `lookup` materializes deleteBehavior set_null and joins it, and deleteBehavior has no value for 'do nothing'. So a converted field would start nulling columns when an X record is deleted. A converted field would also stop counting for sharing's fieldHoldsUsers, and a user+foreign+current_user default would become a refused shape. Per the ruling's rule 2, 18.x ships the loud half only. IMPLEMENTED: a new @objectstack/lint advisory `user-field-foreign-reference` (objects and objectExtensions; warning on os validate/build/lint through the reference suite) with an exported constant and an os explain entry. Field.user's config types `reference` as @deprecated, with the accept set and output unchanged. Docblocks were corrected. The incidental object-graph fixture is now a lookup, and the two pins of the 18.x resolution are kept on purpose. Changeset: lint minor + spec patch, Clause-② yes (widening). The claim's provisional `no` is reset because a rule-id constant is published (precedent: lint changelog 54d32e5).",
"tests": "head 3815407620. Reproduction: ObjectSchema.safeParse({name:'probe_task',fields:{owner:{type:'user',label:'Owner',reference:'probe_account'}}}).success === true and referenceTargetOf(...) === 'probe_account', pinned in the new describe. New lint cases: warning fields, extension, map form, one line of at most 200 chars, explain entry, registry advisory on validate/build/lint. Controls: no reference / sys_user / Field.user() silent; Field.lookup and literal lookup silent; unreadable carrier throws. Ablation via scripts/ablation-replace.mjs: deleting `findings.push(...userFieldForeignReferenceFindings(stack))` took the anchor 1->0 and blob bc1ff6a17b->3390aeb770, and 7 tests went red. Restore was `git checkout HEAD`: blob back to bc1ff6a17b (= HEAD:), git diff HEAD empty. No dist leg: lint tests import src. `pnpm --filter @objectstack/lint test`: 151 files / 7005 passed. lint typecheck exit 0 (tsc + check:test-typecheck OK). spec typecheck exit 0. `pnpm --filter @objectstack/spec exec vitest run src/data`: 132 files / 4169 passed + 1 todo. Gates: dispatch-gates --commands derived 88 at 3815407620, all run, and --ran reconciles 86 exit 0 + 2 NOT MEASURED. Those are check:dual-build-cjs-loads and check:lean-entry-closure, exit 3 PREREQUISITE NOT MET (full-monorepo dist absent), declared to CI. spec check:generated: all 14 artifacts up to date. check:adr-0087-registration: no declared-breaking changeset. check:changeset-no-major and check:nul-bytes exit 0. The CLI integration tier is n/a: the diff touches no spawn or driver path. NOT MEASURED: the public `os validate` door by a built CLI binary (reason: not built in this worktree). The registry path the CLI runs is covered by the runAuthoringRules cases.",
"mcp_calls": "0",
"api_writes": "2 — fleet relay repository_dispatch: (1) pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/24942/assignees baozhoutao), run 39461171911 success, body read back byte-identical; (2) os-dev-report comment (POST /issues/24808/comments). No label writes: none named by the dispatch, and a changeset is present.",
"open_questions": [
{
"question": "Dispatch step 3 said 'either way, referenceTargetOf for user returns sys_user', and that conflicts with the 甲 ruling once the answer is warning-only. Flipping it in 18.x would silently re-target a still-accepted field: expand, import, seed and FK resolution would move from X to sys_user. That is a behaviour change with no conversion and no refusal. The PR does NOT flip it.",
"options": ["A: keep the authored-carrier-first resolution in 18.x and flip it together with the 19.0 refusal (as shipped)", "B: flip it in 18.x anyway, accepting a silent runtime re-target for warned metadata"],
"recommendation": "A, because 甲 rule 2 admits only the loud half in 18.x. Once 19.0 refuses a foreign carrier at parse, the carrier can only be sys_user, so the flip becomes a no-op cleanup."
},
{
"question": "Field.user: the dispatch offered 'drop it, or keep only sys_user'. Both are a TS-surface narrowing: a compile error for Field.user({ reference: 'x' }) with no conversion. So the PR marks `reference` @deprecated, keeps the accept set, and keeps `...config` last (moving sys_user after the spread would silently re-target an untyped caller).",
"options": ["A: @deprecated in 18.x, type to `reference?: 'sys_user'` (or Omit) in 19.0 (as shipped)", "B: narrow the type now"],
"recommendation": "A, the same 甲 reasoning. Measured in-repo builder callers passing reference: 0."
}
],
"out_of_scope_findings": [
"19.0 FOLLOW-UP for the seat to file (not filed by dev). Title: spec(data): refuse a `user` field whose `reference` names an object other than `sys_user`, and resolve every `user` field to `sys_user` (protocol 19). Body: Ruled A on #24854; under the 甲 standing ruling (#6015 6292005854) 18.x shipped only the loud half (PR #24942: lint advisory user-field-foreign-reference, Field.user reference @deprecated) because no ADR-0087 conversion is lossless: a lookup joins the referential delete walk (DELETE_SEMANTICS_REFERENCE_TYPES) with materialized deleteBehavior set_null while a user field sits outside it, and deleteBehavior has no no-action value. 19.0 scope: (1) FieldSchema (or ObjectSchema / ObjectExtensionSchema superRefine, precedent refuseForeignTreeReference object.zod.ts) refuses `type: 'user'` with `reference` not equal to sys_user, message naming the lookup spelling; (2) referenceTargetOf returns IMPLICIT_REFERENCE_TARGETS for `user` regardless of carrier; (3) Field.user config drops `reference` (or types it 'sys_user') and writes sys_user after the spread; (4) delete the lint advisory or keep it as an os lint echo; (5) a D3 semantic migration entry (surface object.fields[].reference on type user, replacement lookup, reason: delete-behaviour judgement owed) plus the breaking changeset with Clause-② yes (narrowing) and its ADR-0087 marker; (6) retire the two 18.x pins (field-value.test.ts my_people, object-graph.test.ts explicit-carrier control) and the objectui renderer fixture userCell.readGate-10535.test.tsx (crm_member). Seam: spec:FieldSchema.reference on type user -> runtime:referenceTargetOf (field-value.zod.ts) | renderer:objectui user cell. Dedupe words: user field reference not sys_user refusal 19, IMPLICIT_REFERENCE_TARGETS enforce, Field.user reference deprecated, user-field-foreign-reference.",
"carrier: whoever lands the 19.0 refusal · noted, not filed — objectui packages/fields/src/__tests__/userCell.readGate-10535.test.tsx:157 renders { type: 'user', reference: 'crm_member' } (read at the old local objectui checkout b434f5f47; re-read at the current pin before acting)",
"carrier: 承接者:无 · noted, not filed — the reference-suite member validateObjectReferences runs at the runtime publish gate on flow/dataset snapshots only, so the new advisory does not reach the object save door (Studio / REST /meta / MCP); crossing it onto `object` writes is that member's own false-positive measurement per its runtimeTypes docblock"
]
}
Activity
objectstack-fleet commentedon Oct 20, 2026
Decision filed ·
domain:specseat 3 (#18883) ·baozhoutao· sessionsession_01D6F9axfoPzR7t8G1J7Ytzz· 2026-10-20T10:40Z · ⛔ Not a claim.Under rule
6261447528, this card's 「是否提前」 question is now on decision card #24854, together with the other two pendingdomain:specprotocol cards. Nothing on this card moves until the maintainer answers there. The answer is recorded here.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Claim: PM loop round 18
Session:
session_01D6F9axfoPzR7t8G1J7YtzzAccount:
baozhoutao(the seat's linked user asGET /useranswers it; the card's assignee)Branch:
claude/issue-24808-user-field-reference-fixedWorktree:
objectstack-issue-24808Domain:
domain:specSeat:
domain:spec#3(seat post #18883)File surface (at
origin/mainb42c621c05or later; stop on a breach and explain it in the report):packages/spec/src/data/field.zod.ts(Field.user: the authoredreferenceno longer overridessys_user) andfield-value.zod.ts(referenceTargetOf);userfield'sreference(precedent: thetreeforeign-target refusal,object.zod.ts:1656);lookupon its target;Field.lookup;.changeset/24808-*.md.Container & model:
M,mode:subagent,model: default (no path-derived mandate)Clause-②: no (provisional: the dev resets it from the measurement under 「甲」,
yes (narrowing)if a conversion carries the refusal)Responsibility:
n/a — a declared fixed target that parse does not enforce (ruled A, #24854)Thread-read: none
Serial constraints cleared: read at this act; the dev re-checks open PRs touching
field.zod.ts/field-value.zod.ts/object.zod.tsbefore opening its own.Under 「甲」 (#6015
6292005854, derivation on #24854):userwith a foreignreference.lookupon that object), A's refusal goes into 18.x with it.Read on
origin/mainb42c621c05at 2026-10-20T15:47Z:Field.userspreads...configafterreference: 'sys_user', andreferenceTargetOfreturns the authored carrier first.Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
os-dev-report
{ "issue": 24808, "status": "done", "branch": "claude/issue-24808-user-field-reference-fixed", "pr": "https://github.com/objectstack-ai/objectstack/pull/24942", "session": "session_01D6F9axfoPzR7t8G1J7Ytzz", "premise_still_valid": true, "summary": "MEASUREMENT (objectstack-ai/objectstack @ b42c621c05): producers that author a `user` field with a reference other than sys_user number 0 in examples/**, 0 in package source, and 0 among `Field.user({ reference })` calls (67 Field.user lines, none passes reference). YAML: 0. Test fixtures: 2, packages/spec/src/data/field-value.test.ts:91 (my_people) and packages/lint/src/object-graph.test.ts:39 (crm_person). Sibling objectui: 1 renderer fixture, userCell.readGate-10535.test.tsx:157 (crm_member), read at an old local checkout b434f5f47. The scans were a literal regex, the Field.user call form, and a six-line window over 413 user-type lines in 176 files; the window's 81 other candidates were hand-read as neighbouring lookups. GA-GATE DECISION: NO conversion can carry it. ADR-0087 D2 is lossless-only, and user to lookup is not lossless. `user` is outside the delete walk (DELETE_SEMANTICS_REFERENCE_TYPES = master_detail/lookup/tree, objectql engine.ts), while `lookup` materializes deleteBehavior set_null and joins it, and deleteBehavior has no value for 'do nothing'. So a converted field would start nulling columns when an X record is deleted. A converted field would also stop counting for sharing's fieldHoldsUsers, and a user+foreign+current_user default would become a refused shape. Per the ruling's rule 2, 18.x ships the loud half only. IMPLEMENTED: a new @objectstack/lint advisory `user-field-foreign-reference` (objects and objectExtensions; warning on os validate/build/lint through the reference suite) with an exported constant and an os explain entry. Field.user's config types `reference` as @deprecated, with the accept set and output unchanged. Docblocks were corrected. The incidental object-graph fixture is now a lookup, and the two pins of the 18.x resolution are kept on purpose. Changeset: lint minor + spec patch, Clause-② yes (widening). The claim's provisional `no` is reset because a rule-id constant is published (precedent: lint changelog 54d32e5).", "tests": "head 3815407620. Reproduction: ObjectSchema.safeParse({name:'probe_task',fields:{owner:{type:'user',label:'Owner',reference:'probe_account'}}}).success === true and referenceTargetOf(...) === 'probe_account', pinned in the new describe. New lint cases: warning fields, extension, map form, one line of at most 200 chars, explain entry, registry advisory on validate/build/lint. Controls: no reference / sys_user / Field.user() silent; Field.lookup and literal lookup silent; unreadable carrier throws. Ablation via scripts/ablation-replace.mjs: deleting `findings.push(...userFieldForeignReferenceFindings(stack))` took the anchor 1->0 and blob bc1ff6a17b->3390aeb770, and 7 tests went red. Restore was `git checkout HEAD`: blob back to bc1ff6a17b (= HEAD:), git diff HEAD empty. No dist leg: lint tests import src. `pnpm --filter @objectstack/lint test`: 151 files / 7005 passed. lint typecheck exit 0 (tsc + check:test-typecheck OK). spec typecheck exit 0. `pnpm --filter @objectstack/spec exec vitest run src/data`: 132 files / 4169 passed + 1 todo. Gates: dispatch-gates --commands derived 88 at 3815407620, all run, and --ran reconciles 86 exit 0 + 2 NOT MEASURED. Those are check:dual-build-cjs-loads and check:lean-entry-closure, exit 3 PREREQUISITE NOT MET (full-monorepo dist absent), declared to CI. spec check:generated: all 14 artifacts up to date. check:adr-0087-registration: no declared-breaking changeset. check:changeset-no-major and check:nul-bytes exit 0. The CLI integration tier is n/a: the diff touches no spawn or driver path. NOT MEASURED: the public `os validate` door by a built CLI binary (reason: not built in this worktree). The registry path the CLI runs is covered by the runAuthoringRules cases.", "mcp_calls": "0", "api_writes": "2 — fleet relay repository_dispatch: (1) pr_create (POST /repos/objectstack-ai/objectstack/pulls draft + POST /issues/24942/assignees baozhoutao), run 39461171911 success, body read back byte-identical; (2) os-dev-report comment (POST /issues/24808/comments). No label writes: none named by the dispatch, and a changeset is present.", "open_questions": [ { "question": "Dispatch step 3 said 'either way, referenceTargetOf for user returns sys_user', and that conflicts with the 甲 ruling once the answer is warning-only. Flipping it in 18.x would silently re-target a still-accepted field: expand, import, seed and FK resolution would move from X to sys_user. That is a behaviour change with no conversion and no refusal. The PR does NOT flip it.", "options": ["A: keep the authored-carrier-first resolution in 18.x and flip it together with the 19.0 refusal (as shipped)", "B: flip it in 18.x anyway, accepting a silent runtime re-target for warned metadata"], "recommendation": "A, because 甲 rule 2 admits only the loud half in 18.x. Once 19.0 refuses a foreign carrier at parse, the carrier can only be sys_user, so the flip becomes a no-op cleanup." }, { "question": "Field.user: the dispatch offered 'drop it, or keep only sys_user'. Both are a TS-surface narrowing: a compile error for Field.user({ reference: 'x' }) with no conversion. So the PR marks `reference` @deprecated, keeps the accept set, and keeps `...config` last (moving sys_user after the spread would silently re-target an untyped caller).", "options": ["A: @deprecated in 18.x, type to `reference?: 'sys_user'` (or Omit) in 19.0 (as shipped)", "B: narrow the type now"], "recommendation": "A, the same 甲 reasoning. Measured in-repo builder callers passing reference: 0." } ], "out_of_scope_findings": [ "19.0 FOLLOW-UP for the seat to file (not filed by dev). Title: spec(data): refuse a `user` field whose `reference` names an object other than `sys_user`, and resolve every `user` field to `sys_user` (protocol 19). Body: Ruled A on #24854; under the 甲 standing ruling (#6015 6292005854) 18.x shipped only the loud half (PR #24942: lint advisory user-field-foreign-reference, Field.user reference @deprecated) because no ADR-0087 conversion is lossless: a lookup joins the referential delete walk (DELETE_SEMANTICS_REFERENCE_TYPES) with materialized deleteBehavior set_null while a user field sits outside it, and deleteBehavior has no no-action value. 19.0 scope: (1) FieldSchema (or ObjectSchema / ObjectExtensionSchema superRefine, precedent refuseForeignTreeReference object.zod.ts) refuses `type: 'user'` with `reference` not equal to sys_user, message naming the lookup spelling; (2) referenceTargetOf returns IMPLICIT_REFERENCE_TARGETS for `user` regardless of carrier; (3) Field.user config drops `reference` (or types it 'sys_user') and writes sys_user after the spread; (4) delete the lint advisory or keep it as an os lint echo; (5) a D3 semantic migration entry (surface object.fields[].reference on type user, replacement lookup, reason: delete-behaviour judgement owed) plus the breaking changeset with Clause-② yes (narrowing) and its ADR-0087 marker; (6) retire the two 18.x pins (field-value.test.ts my_people, object-graph.test.ts explicit-carrier control) and the objectui renderer fixture userCell.readGate-10535.test.tsx (crm_member). Seam: spec:FieldSchema.reference on type user -> runtime:referenceTargetOf (field-value.zod.ts) | renderer:objectui user cell. Dedupe words: user field reference not sys_user refusal 19, IMPLICIT_REFERENCE_TARGETS enforce, Field.user reference deprecated, user-field-foreign-reference.", "carrier: whoever lands the 19.0 refusal · noted, not filed — objectui packages/fields/src/__tests__/userCell.readGate-10535.test.tsx:157 renders { type: 'user', reference: 'crm_member' } (read at the old local objectui checkout b434f5f47; re-read at the current pin before acting)", "carrier: 承接者:无 · noted, not filed — the reference-suite member validateObjectReferences runs at the runtime publish gate on flow/dataset snapshots only, so the new advisory does not reach the object save door (Studio / REST /meta / MCP); crossing it onto `object` writes is that member's own false-positive measurement per its runtimeTypes docblock" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 20, 2026
Landed ·
domain:specseat 3 ·baozhoutao· sessionsession_01D6F9axfoPzR7t8G1J7Ytzz· 2026-10-20T18:07ZPR feat(lint,spec): warn on a user field whose reference names an object other than sys_user (18.x loud half; refusal is the next major's) #24942 merged as
e7befa7700through the merge queue. Verified onorigin/main: single parent, and thegit diff C^ Cpatch-id equals the reviewed head3815407620's against its merge base (ea18324402).Contract review: PASS at
CONTRACT_REVIEW_TIERon that head (6296979388).Under 「甲」 ([Decision] 「是否提前」 for three domain:spec protocol cards: #24720 (refuse
parentin validations[]), #24690 (stored_expressiondropped-field reason), #24808 (userfield with a foreignreference) #24854): no ADR-0087 conversion is lossless (the delete walk,deleteBehavior, sharing'sfieldHoldsUsers), so 18.x ships the loud half only:@objectstack/lintadvisoryuser-field-foreign-referenceonos validate/os build/os lint, with anos explainentry;Field.user'sreferenceis@deprecated.The accept set,
referenceTargetOfand the TS surface are unchanged, so nothing is silently re-targeted.Measurement: 0 producers in examples or package source; 2 test fixtures.
19.0 half filed as spec(data) 19.0: refuse a
userfield whosereferencenames an object other thansys_user, and resolve everyuserfield tosys_user(the deferred half of #24808) #24952.Claim
6294945987ends here;pm:dispatchedis removed in this stroke.Generated by Claude Code