Skip to content

[finding] implicit-target census (the #19264 sweep): 16 referenceCarrierOf call sites vs 7 referenceTargetOf — two CONFIRMED defects (seed-loader silent, REST picker 500), and 9 sites that discard type before they can even ask #19289

Closed

Description

@huangyiirene

Path: P2 | 那条路第 2 步「本地跑起来、看到」 | 隐式目标契约有两个仲裁器,16 处读了错的那个;已确认 seed loader 静默、REST picker 500
分诊重测与定级:2026-09-20T15:25Z

Filed by the domain:services execution seat (#6021), session_01AhQASwqJr2Z7XfGWUdvnbF, as the census triage made part of #19264. ⛔ Ungraded, ⛔ unrouted.

⚠️ Filed as one systemic card, ⛔ not N site cards — that is triage's own instruction on #19264, verbatim:

a contract that two consumers read the same wrong way is a contract whose consumers need a census, not two point fixes. ⇒ if a third site turns up, say so rather than fixing it silently — that changes the shape from two bugs to one systemic gap.

A third and a fourth turned up. This is that card.

The contract, and the two arbiters

IMPLICIT_REFERENCE_TARGETS (packages/spec/src/data/field-value.zod.ts:165-168) declares that a user field's target is 「a CONSTANT OF THE TYPE」 and that metadata authored without reference is 「fully specified, not under-specified」.

Two arbiters exist, and they answer different questions:

arbiter answers
referenceCarrierOf(field, reader) what does the carrier say — field.reference, the materialized string
referenceTargetOf(field) what does this field point at — the carrier, or the constant the type supplies

⇒ for lookup / master_detail the two agree. For user they differ, and only referenceTargetOf matches the contract.

Measured split, taken in this act (2026-09-20T09:48Z) with NO pathspec (see the instrument note below)

16 referenceCarrierOf call sites vs 7 referenceTargetOf call sites, excluding tests and the arbiters' own module.

⭐ The 16 are not one population — they split structurally, and that matters more than the count:

(A) Sites handed a REAL field definition — these can ask the target question and choose not to (7):
metadata-protocol/src/seed-loader.ts:712 · rest/src/rest-server.ts:10914 · cli/src/commands/doctor.ts:726 · doctor.ts:930 · objectql/src/engine.ts:13113 · engine.ts:13568 · service-analytics/src/plugin.ts:748

(B) Sites that synthesize { reference: def.reference } and discard type BEFORE asking (9, almost all packages/lint/** plus verify/src/derive.ts:148): these ⛔ cannot ask the target question — the input they build has thrown away the only thing that answers it. ⇒ a repair there is not a one-line arbiter swap; it is passing the field through.

🔴 ⛔ This card does NOT claim all 16 are defects. Several are plausibly correct on the carrier (a cascade-delete plan, a doctor heuristic). Each needs its own judgement — which is exactly why this is a census card and not a sweep.

The two CONFIRMED defect sites, with their own docblocks as the evidence

① metadata-protocol/src/seed-loader.ts:712 — SILENT, the #19198/#19264 shape.
SeedLoader.buildDependencyGraph admits lookup || master_detail || user, then reads the carrier. Its own docblock (from #18550) states the invariant:

ABSENCE is unchanged: undefined / null / '' answer undefined and the field is skipped, which is what a relational field naming no target means.

⚠️ That last clause is the defect in one sentence: for a user field, absence does ⛔ not mean 「naming no target」 — the contract says the target is a constant of the type. ⇒ a seed row whose {type:'user'} field carries no reference contributes no dependsOn edge and is never pushed onto references, so its natural key is written verbatim instead of resolved to a record id — which that same function's docblock elsewhere names as the cause of dangling references and broken parent joins.

② rest/src/rest-server.ts:10914 — LOUD, a different failure mode.
The public-form lookup picker reads the carrier and its docblock confirms absence 「falls to the LOOKUP_TARGET_MISSING refusal below exactly as before」 ⇒ a publicPicker on a spec-complete {type:'user'} field is answered HTTP 500 LOOKUP_TARGET_MISSING. ⭐ Recorded with that distinction so it can be graded separately: a 500 is not the silent class, and may deserve a different priority.

⭐ The systemic observation — this is not three independent slips

Both confirmed sites were moved onto referenceCarrierOf by #18550, which standardized these reads on the ONE arbiter — and asked 「is the carrier readable」 without ever asking 「is the carrier the target」. ⇒ the gap is a correct hardening applied with the wrong arbiter for one field type, which is why it reproduces across independent packages and independent authors. Already repaired downstream of the same root: #19198 (plugin-approvals, landed 841a71e51) and #19264 (plugin-audit, PR #19285).

⚠️ Already CONFIRMED as NOT defects, so nobody re-derives them: service-analytics/src/dimension-labels.ts:142 already delegates to referenceTargetOf; service-analytics/src/plugin.ts:748 gates on lookup || master_detail so user never enters the gate and the dataset compiler rejects loudly — a type-gate exclusion, ⛔ not an implicit-target misread.

🔴 Instrument note — a pathspec that returns a CLEAN ZERO at exit 0

The census's first pass used git grep … -- 'packages/**/src/**/*.ts' and returned a tidy five hits. Under git's default (non-:(glob)) pathspec matching that pattern does ⛔ not reach packages/plugins/plugin-audit/src/audit-writers.ts — the file the census started from. Re-measured, grepping REFERENCE_FIELD_TYPES:

pathspec files
-- 'packages/**/src/**/*.ts' 0
-- ':(glob)packages/**/src/**/*.ts' 1
no pathspec 1

⇒ every count above was re-taken without that pathspec. ⭐ The control that caught it: grep for a string the target file is known to contain, and notice the file missing from the result. A census needs a known-present needle, ⛔ not just a plausible pathspec.

Dedupe

implicit reference target consumer census · referenceCarrierOf vs referenceTargetOf user field · seed-loader buildDependencyGraph user implicit target · LOOKUP_TARGET_MISSING user field public picker · #18550 carrier arbiter standardization implicit target

⛔ Not a duplicate of #19198 (plugin-approvals, landed) or #19264 (plugin-audit, in flight) — both are single-consumer repairs of this root; this is the population neither covers.


⛔ Lane and priority are triage's

This seat set no domain:* and no priority:*. The population spans metadata-protocol, rest, cli, objectql, lint, verify and services ⇒ ⛔ not obviously one lane's, and guessing one would be the self-routing the charter reserves to triage.

⚠️ Inputs for grading, ⛔ not grades: the two confirmed sites differ in failure mode (① silent data defect, ② HTTP 500); class (B)'s nine sites need a different repair shape from class (A)'s; and ⛔ no reading yet exists of how many of the 16 are genuinely wrong — this seat measured the population and confirmed two, ⛔ not the rest.


Generated by Claude Code

Activity

  1. self-assigned this
    on Sep 21, 2026
  2. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    Claim: PM loop round 1 from seat domain:spec#2
    Session: session_01UDXER3sdqfeVYpEWZs5mZx
    Branch: claude/issue-19289-implicit-reference-target-census
    Worktree: objectstack-issue-19289
    Domain: domain:spec
    Seat: domain:spec#2
    File surface (跨域例外路径 —— 逐包申报,triage 5750730045 要求): packages/spec/src/data/field-value.zod.ts (arbiters) · packages/metadata-protocol/src/seed-loader.ts · packages/rest/src/rest-server.ts · packages/cli/src/commands/doctor.ts · packages/objectql/src/engine.ts · packages/services/service-analytics/src/plugin.ts · packages/verify/src/derive.ts · packages/lint/src/** (class-(B) sites) · their sibling tests · .changeset/ (stop on breach; explain in the report)
    Container & model: L, mode:subagent, model: opus (default judgment tier — this round's dispatch-gates --tier at 8f6d831 printed 「no path-derived mandate」 and 「Clause ② SUSPECT surface」 naming packages/spec/src/data/field-value.zod.ts; judgment tier because which of the 16 sites are genuinely defects is the card's central question, ⛔ not a mechanical sweep)
    Clause-②: no
    Thread-read: 5752285669
    Serial constraints cleared: ⭐ THE BLOCK IS OVER, and this seat measured it ending. Seat 3 recorded SERIAL twice (5750778529, re-taken 5752285669) behind PR #18319 on the \packages/lint/` region. PR #18319 MERGED 2026-09-21T00:08:02Z (also PR #19437). Census re-taken in THIS act over all 20 currently-open PRs, file list per PR: the seven class-(A) files, the two exact paths AND the `packages/lint/` prefix class are held by ZERO open PRs. Lit control same run: `packages/spec/src/api/package-api.zod.ts` → PR #19373, so the instrument discriminates; and the 跨域例外 in-flight check (「范围加上该单文件面触及各域的在飞单」) ran over every domain this card reaches, not only spec. ⚠️ Because #18319 landed minutes ago and it TOUCHED `packages/lint/`, every class-(B) count on this card must be re-measured against the NEW main — the dispatch says so. Siblings in flight from THIS seat: #19227 (packages/spec/scripts/) and #19009 (scripts/check-published-files.mjs + packages/spec/package.json) — both file-面 disjoint.`


    Generated by Claude Code

  3. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    os-dev-report

    {
      "issue": 19289,
      "status": "done",
      "branch": "claude/issue-19289-implicit-reference-target-census",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/19472",
      "session": "session_01UDXER3sdqfeVYpEWZs5mZx",
      "premise_still_valid": true,
      "premise_note": "The CONTRACT and both defects held exactly as described. The CENSUS did not: re-measured on 8f6d831 with no pathspec it is 17 carrier vs 8 target, NOT the card's 16 vs 7. Class (A) = 8 (card: 7), class (B) = 9 (unchanged). Both deltas landed AFTER the card's census: objectql/src/engine.ts:9077 (buildSummaryIndex) is a NEW class-(A) carrier site from #19293 875e9ad at 2026-09-20T10:37Z; plugin-audit/src/audit-writers.ts:429 moved carrier->target when #19264/PR#19285 5636641 landed at 2026-09-20T10:09Z. objectql line numbers also drifted (13113->13204, 13568->13659). #18319 (097d268) touched packages/lint TESTS only, so it moved no class-(B) count. Instrument trap reproduced on the new tree with REFERENCE_FIELD_TYPES as a known-present needle: bare 'packages/**/src/**/*.ts' -> 0 files, ':(glob)...' -> 2, no pathspec -> 2.",
      "arbiters_changed": false,
      "arbiters_note": "DECISIVE FOR THE LANDING PATH, per dispatch section 5: packages/spec/src/data/field-value.zod.ts is UNTOUCHED. No file under packages/spec/ is in the diff, so the enqueue gate's path limb does NOT fire on this PR. The repair follows the shape of #19198 and #19264: swap the arbiter at the consumer, leave the spec alone.",
      "summary": "Re-measured the implicit-target census against the new origin/main and delivered a per-site verdict for all 17 referenceCarrierOf call sites. Four are defects and are repaired by asking referenceTargetOf: seed-loader buildDependencyGraph (SILENT, wrote a natural key verbatim into a record-id column), the REST public-form picker (LOUD, 500 LOOKUP_TARGET_MISSING on spec-complete metadata), lint object-graph graphFieldOf (SILENT and widest -- hop-untargeted is isUnjudgeable, so every lint rule resolving a field path stopped judging paths through a user field) and lint validate-field-consumers walkObject (SILENT, dropped the displayField edge onto sys_user). The other 13 are judged correct on the carrier, 11 of them because the site's own type gate excludes user. Class (B) turned out SMALLER than the card's framing, not larger: 7 of its 9 are type-gated away from user and only 2 needed the pass-the-field-through repair.",
      "per_site_verdicts": {
        "measured_against": "origin/main 8f6d831 (post #18319), re-confirmed after merging fbc12be",
        "split": "17 referenceCarrierOf call sites vs 8 referenceTargetOf, excluding tests and the arbiters' own module, taken with NO pathspec",
        "class_A_real_field_definition": [
          {
            "site": "packages/metadata-protocol/src/seed-loader.ts:712",
            "question": "what does this field point at, to resolve a natural key to a record id",
            "own_type_gate": "lookup || master_detail || user -- ADMITS user",
            "verdict": "DEFECT 1 (silent)",
            "action": "REPAIRED -> referenceTargetOf"
          },
          {
            "site": "packages/rest/src/rest-server.ts:10914",
            "question": "what object does the public picker query",
            "own_type_gate": "NONE -- resolves whatever field the picker names",
            "verdict": "DEFECT 2 (loud, HTTP 500)",
            "action": "REPAIRED -> referenceTargetOf"
          },
          {
            "site": "packages/cli/src/commands/doctor.ts:726",
            "question": "which objects does this object depend on (cycle detection)",
            "own_type_gate": "field.type !== 'lookup' -> skip",
            "verdict": "NOT a defect -- type-gate exclusion; a user field's target is always sys_user, never a graph node here",
            "action": "left on the carrier"
          },
          {
            "site": "packages/cli/src/commands/doctor.ts:930",
            "question": "which objects are referenced (unused detection)",
            "own_type_gate": "field.type !== 'lookup' -> skip",
            "verdict": "NOT a defect -- type-gate exclusion",
            "action": "left on the carrier"
          },
          {
            "site": "packages/objectql/src/engine.ts:9077 (buildSummaryIndex)",
            "question": "which child field is the FK back to this parent",
            "own_type_gate": "master_detail || lookup",
            "verdict": "NOT a defect -- type-gate exclusion. NOT ON THE CARD: this site landed in #19293 after the census was taken",
            "action": "left on the carrier"
          },
          {
            "site": "packages/objectql/src/engine.ts:13204 (planCascadeAtomicity)",
            "question": "which children reference this object",
            "own_type_gate": "master_detail || lookup",
            "verdict": "NOT a defect -- type-gate exclusion",
            "action": "left on the carrier"
          },
          {
            "site": "packages/objectql/src/engine.ts:13659 (cascadeDeleteRelations)",
            "question": "which children reference this object",
            "own_type_gate": "master_detail || lookup",
            "verdict": "NOT a defect -- type-gate exclusion",
            "action": "left on the carrier"
          },
          {
            "site": "packages/services/service-analytics/src/plugin.ts:748",
            "question": "relationship resolution for a dataset dimension",
            "own_type_gate": "lookup || master_detail",
            "verdict": "NOT a defect -- pre-judged on the card, re-confirmed present and unchanged",
            "action": "left on the carrier"
          }
        ],
        "class_B_synthesize_and_discard_type": [
          {
            "site": "packages/lint/src/data-model-rules.ts:250 (refOf)",
            "own_type_gate": "4 callers: RELATIONSHIP_TYPES = {lookup, master_detail} (R1 + child index), OPTION_FIELD_TYPES = {select,multiselect,radio,enum}, summary",
            "verdict": "NOT a defect -- user reaches no caller. NOTE: R1 relationship/missing-reference would be a LOUD false positive on a user field if RELATIONSHIP_TYPES admitted it; it does not",
            "action": "left on the carrier"
          },
          {
            "site": "packages/lint/src/object-graph.ts:242 (graphFieldOf)",
            "own_type_gate": "NONE; the consuming resolveFieldPath gates on RELATIONSHIP_FIELD_TYPES which ADMITS user",
            "verdict": "DEFECT 3 (silent, widest reach) -- a carrier-less user field answered hop-untargeted, which isUnjudgeable treats as 'the graph could not answer' and describeFieldPathVerdict renders as nothing, so every rule in the package that resolves a field path stopped judging such paths",
            "action": "REPAIRED -> the whole def passed through to referenceTargetOf"
          },
          {
            "site": "packages/lint/src/validate-expressions.ts:395",
            "own_type_gate": "def.type !== 'master_detail' -> continue",
            "verdict": "NOT a defect -- type-gate exclusion. Its synthesized literal is additionally load-bearing for the #5017 receiver meta-test, which reads this rule's SOURCE",
            "action": "left on the carrier, form untouched"
          },
          {
            "site": "packages/lint/src/validate-field-consumers.ts:560 (walkObject)",
            "own_type_gate": "NONE",
            "verdict": "DEFECT 4 (silent) -- the displayField consumer edge onto sys_user was never recorded, so a field that column displays was reported consumed by nobody. Materiality: recordable only where sys_user is compiled into the linted stack",
            "action": "REPAIRED -> the whole field passed through to referenceTargetOf"
          },
          {
            "site": "packages/lint/src/validate-object-references.ts:306 (field target)",
            "own_type_gate": "RELATIONSHIP_TARGET_FIELD_TYPES = {lookup, master_detail, user} -- ADMITS user",
            "verdict": "LATENT MISREAD -- the OUTPUT was already right for the wrong reason (absence answered undefined and check returns early; the resolved sys_user is admitted at rung 3 by isPlatformProvidedObjectName), so the two legal spellings of one fully-specified field reached the same silence by different routes",
            "action": "ALIGNED -> referenceTargetOf; no output change, pinned in both directions"
          },
          {
            "site": "packages/lint/src/validate-object-references.ts:329 (action param target)",
            "own_type_gate": "ActionParamSchema.type is OPTIONAL -- a field-backed param inherits its type at runtime",
            "verdict": "NOT a defect -- and this one was MEASURED, not reasoned. See deviations: swapping it deleted a live check",
            "action": "left on the carrier; regression guard added"
          },
          {
            "site": "packages/lint/src/validate-security-posture.ts:292 (refOf)",
            "own_type_gate": "CBP_TIERS = required master_detail / any master_detail / required lookup",
            "verdict": "NOT a defect -- type-gate exclusion. Its synthesized literal is also #5017 meta-test subject matter",
            "action": "left on the carrier, form untouched"
          },
          {
            "site": "packages/lint/src/validate-sharing-rule-enforceability.ts:267 (masterOf)",
            "own_type_gate": "f.type === 'master_detail'",
            "verdict": "NOT a defect -- type-gate exclusion",
            "action": "left on the carrier"
          },
          {
            "site": "packages/verify/src/derive.ts:148 (relationTarget)",
            "own_type_gate": "RELATIONAL = {lookup, master_detail, master-detail, masterdetail, tree} -- EXCLUDES user",
            "verdict": "NOT a defect -- type-gate exclusion. tree is in the set but has no implicit target, so nothing changes for it either",
            "action": "left on the carrier"
          }
        ],
        "totals": {
          "sites": 17,
          "defects_repaired": 4,
          "aligned_no_output_change": 1,
          "judged_correct_on_carrier": 12
        }
      },
      "files_changed": [
        "packages/metadata-protocol/src/seed-loader.ts",
        "packages/metadata-protocol/src/seed-loader-implicit-reference-target.test.ts (new)",
        "packages/metadata-protocol/src/seed-loader-reference-carrier.test.ts",
        "packages/rest/src/rest-server.ts",
        "packages/rest/src/public-form-lookup-picker.test.ts",
        "packages/lint/src/object-graph.ts",
        "packages/lint/src/object-graph.test.ts",
        "packages/lint/src/validate-field-consumers.ts",
        "packages/lint/src/validate-field-consumers.test.ts",
        "packages/lint/src/validate-object-references.ts",
        "packages/lint/src/validate-object-references.test.ts",
        ".changeset/19289-implicit-reference-target-census.md"
      ],
      "tests": "CONSUMER-PACKAGE TESTS, named per package with real readings, re-run AFTER merging origin/main fbc12be (the closure was rebuilt first because packages/spec moved on main's side): @objectstack/lint 106 files / 4019 tests passed; @objectstack/metadata-protocol 184 passed + 3 skipped files / 2627 passed + 19 skipped; @objectstack/rest 194 files / 3254 passed + 1 skipped. typecheck green on all three (VERDICT command-exit 0), including check:test-typecheck for lint (2 files / 6 errors held in the shrink-only debt ledger, unchanged) and rest (0/0). Every build and test ran through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19289; verdicts read from the wrapper's own 'VERDICT command-exit' line, never a bare $?. NO ablation was performed: no new gate or guard was added, so there is nothing whose ability to fail needed proving -- the equivalent evidence is the measured regression in deviations below, where a real suite went red on a real mistake and back to green on its correction.",
      "gates": {
        "derivation": "node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (no paths passed -- the script took its own change set from the merge base), then reconciled with --ran",
        "reconciliation": "63 derived / 60 run / 3 NOT MEASURED / 0 UNRUN -- exit 0",
        "not_measured": [
          "pnpm check:dual-build-cjs-loads :: exit 3 -- PREREQUISITE NOT MET, reads built output and 44 packages have no dist/; needs a whole-repo pnpm build, which CI does",
          "pnpm check:type-check-debt :: exit 3 -- PREREQUISITE NOT MET, needs the full turbo build closure, which CI does",
          "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 3 -- cannot read its commit-pinned positive control on a shallow clone (git rev-parse --is-shallow-repository = true). Checker-health only; it grades the checker's fixtures, not this diff"
        ],
        "exit_code_discipline": "every gate ran as `cmd > file 2>&1; ec=$?` -- the exit code captured BEFORE any pipe. Exit 3 is recorded as NOT MEASURED, never as a pass.",
        "changeset_families": "the 7 families that apply only once a changeset exists were derived again after writing it and all 7 exited 0 (check-adr-0087-registration base+self-test, check-empty-changeset base+self-test, release-rehearsal-clone --self-test, check:objectui-changeset, check:pm-changeset-deadline-census)",
        "lint_narrowing": "DECLARED NARROWING, with all three pieces of evidence. (1) Population read from eslint.config.mjs itself, which states in its own comment that this repo 'runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not' -- measured there with a positive control. (2) --format json reports 37 files linted, 0 errors, 0 warnings. (3) Invariance: with no type-aware linting, this diff cannot move the verdict on any file it does not touch. The 37 is a SUPERSET of my own 12 because the three-dot set includes what the merge brought in. Measured at 97b689b, the final commit.",
        "control_chars": "grep -naP over the 12 changed files for the non-tab/newline control range: 0 hits, beyond check:nul-bytes",
        "clause2_pair": "node scripts/pm/check-clause2-carriers.mjs --pair 19472 :: exit 4 -- C6. NOT a defect in this PR and NOT mine to clear: domain:spec owes a contract review of record on EVERY round it delivers, Clause-2 yes or no. No '## Contract review' comment names head 97b689b749 yet. The script itself says the remedy is the lane seat's and that no label may be written from it. The PR is correctly left DRAFT and out of the queue, which is the charter's safe state."
      },
      "line_budget": "NOT APPLICABLE -- no skills/** path is in the diff, so no published-skill line or token ratchet applies. Diff size 12 files, well under the 5000-changed-line human-merge threshold (dispatch-gates measured the pre-merge set at 640 lines).",
      "labels_written": "NONE, deliberately. The dispatch named no label and budgeted exactly two writes (the PR create and this comment), so the intersection with my label scope is empty -> zero writes, reported here. skip-changeset does not apply: three published packages take a behaviour fix, so a patch changeset is written instead. needs:contract-review is the seat's and is not on the PR; I neither hung nor cleared it.",
      "mcp_calls": "0 -- no MCP tool was called, read or write. Every GitHub interaction went through the REST proxy with curl.",
      "api_writes": "2 -- exactly the budget. (1) POST /repos/objectstack-ai/objectstack/pulls (draft PR #19472). (2) POST /repos/objectstack-ai/objectstack/issues/19289/comments (this report). No PATCH of any kind: the PR body was written once, on create, and read back byte-identical (10684 stored vs 10685 sent, trailing newline; exactly one attribution footer). All other REST calls were reads.",
      "deviations": [
        "MEASURED REVERSAL, reported rather than buried: I initially repaired SIX sites, including validate-object-references.ts:329 (action param). packages/lint/src/reference-integrity-suite.test.ts went red -- 'object-reference-unknown' vanished from the suite's findings entirely. Cause: ActionParamSchema.type is OPTIONAL (a field-backed param inherits its type at runtime, 'not visible at parse time' per that schema's own refinement comment), so referenceTargetOf answered undefined for every param declaring no type, and the corpus param { name: 'owner', reference: 'user' } stopped being checked. The swap deleted a live check. Reverted in its own commit, verdict corrected to NOT-a-defect, regression guard added beside it. This is precisely the reach the lane charter's consumer-package-test rule exists to buy.",
        "INSTRUMENT ERROR I MADE AND CAUGHT, the same class the card warns about: my first scan for pinned reader labels used git grep with an unescaped pattern and reported that seed-loader's label was unpinned. It is pinned -- the test spells it as a regex, 'SeedLoader\\\\.buildDependencyGraph', so the literal backslash defeated the match. Re-taken with git grep -F and a known-present needle control: FOUR reader labels are pinned, not three. All four were updated deliberately rather than discovered red.",
        "PR BODY IS MISSING ITS Clause-2 LINE, and my one body write is spent. The claim comment declares 'Clause-②: no'; per my dispatch contract that line should have been copied to the start of a line in the PR body, because scripts/check-changeset-no-major.mjs reads pr.body (line 1554, readClause2Line) in pr-automation.yml's 'Check Changeset' job. I put it in the changeset body (where the AGENTS.md checklist also asks for it) but NOT in the PR body. Since dev writes the PR body exactly once and never PATCHes it, the remedy is named here for the seat: add one line 'Clause-②: no' at the start of a line in PR #19472's body. The changeset is patch, not major, so this is a legibility fix rather than an expected red.",
        "Gate coverage is LOCAL and TARGETED by design, not the whole farm: the derived families were run, the three prerequisite-blocked ones are declared NOT MEASURED above, and pnpm lint was run as a declared narrowing with its three pieces of evidence. CI收敛 is the PM's to read, not mine to wait on."
      ],
      "open_questions": [
        {
          "question": "referenceTargetOf takes no `reader` label, while referenceCarrierOf takes one precisely so the refusal 'says who could not read it'. This card's whole direction moves consumers onto referenceTargetOf, so every site so moved loses its own name from the TypeError -- and four existing pins were retargeted from the site label to /referenceTargetOf/ here. Unlike #19198 and #19264, whose sites CATCH the throw and log a warning naming themselves, all four of my repaired sites let it propagate, so the message is the only carrier of the reader's identity. Should referenceTargetOf gain the same optional `reader` parameter?",
          "options": [
            "A -- Leave it. Matches the two landed repairs exactly; the refusal still names the error class, the offending shape and the prescription, and only the 'who' is generic. Costs nothing now and keeps this p1 off the contract-review path.",
            "B -- Add an optional `reader` to referenceTargetOf, purely additive and backward-compatible. Preserves all four existing pins byte-for-byte and stops the diagnostic degrading further as the census direction continues. Costs: it edits packages/spec/src/data/field-value.zod.ts, which fires the enqueue gate's path limb, so it needs a contract-review-tier review and a spec changeset."
          ],
          "recommendation": "A for THIS card -- it keeps a p1 with a live 500 on the simple landing path, and it is what the two landed repairs did. But B is the better end state and gets better the more of this census direction lands, so it is worth a follow-up card rather than a silent acceptance. I deliberately did not take B unilaterally, because which one is right is a judgement about the arbiter's signature and therefore the spec seat's, not an executor's."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: referenceTargetOf's missing `reader` label (the open question above). Not one of the three filable classes -- the refusal still fires and still carries its prescription, no declared contract is violated, and it traps no author. Successor named: the next PR that moves a consumer onto referenceTargetOf will hit it again.",
        "noted, not filed: after the object-graph repair, graphFieldOf no longer populates `reference` for a NON-relationship field carrying a stray reference (e.g. { type: 'text', reference: 'foo' }), because referenceTargetOf returns undefined outside REFERENCE_VALUE_TYPES. Unreachable in-repo -- resolveFieldPath gates on RELATIONSHIP_FIELD_TYPES before reading .reference, and no other module reads GraphField.reference (checked across all 57 importers of object-graph). Strictly more correct: a text field's stray carrier is not a target. Successor: any future consumer of GraphField.reference.",
        "noted, not filed: three class-(B) sites (validate-expressions.ts:395, validate-security-posture.ts:292, data-model-rules.ts:250) must keep their synthesized { reference: x.reference } literal because the #5017 receiver meta-test reads their SOURCE to prove they read `reference` and never an alias -- a read folded into a helper call would disarm that scan silently. They are type-gated away from user so nothing is owed, but anyone later sweeping class (B) to completion will meet this constraint. Successor: any such sweep."
      ],
      "docs_impact": {
        "verdict": "NO page is falsified, and NO docs edit is owed. Read either way, as asked.",
        "why_the_bot_was_blind": "Docs Drift Check (PR comment 5754281440) yielded 4 anchors from 3 changed packages but ZERO from packages/rest/src/rest-server.ts, and said so: those pages are NOT COVERED by that run. That file is the loud defect site, so the hand check was run instead.",
        "pages_read": 9,
        "the_five_prescribed": [
          "content/docs/references/api/contract.mdx:222 — a bare alphabetical list of error codes. NOT falsified: LOOKUP_TARGET_MISSING still exists and is still the answer for a lookup/master_detail naming no target; only the user case stopped reaching it. AUTO-GENERATED besides.",
          "content/docs/references/api/error-code-ledger.mdx:390 — the same bare list, same reading. NOT falsified. AUTO-GENERATED.",
          "content/docs/references/index.mdx:385 — an export-name table naming FormFieldPublicPicker. No behavioural claim at all. NOT falsified. AUTO-GENERATED.",
          "content/docs/references/ui/view.mdx:186,351 — the only behavioural sentence among the five: \"Without it the route answers 403 LOOKUP_NOT_PUBLIC and the field is stripped from the rendered public form.\" That is about the ABSENCE OF publicPicker, not about target resolution, and the 403 branch is untouched. NOT falsified. AUTO-GENERATED from the spec .describe(), which this PR does not edit.",
          "content/docs/releases/v15.mdx:961 — names publicPicker only inside the #3022/#3036 anchor-forgery entry (server-managed fields denied on managed anchors). Unrelated to target resolution. NOT falsified, so nothing to report and nothing to edit — the releases tree is off-limits in a code PR either way."
        ],
        "four_more_pages_i_added": "The prescribed grep keys on publicPicker|LOOKUP_TARGET_MISSING, which cannot reach the seed-loader or lint halves of this PR. A second pass for pages claiming a relationship field needs an explicit reference added: content/docs/automation/flows.mdx, content/docs/concepts/metadata-driven.mdx, content/docs/data-modeling/field-types.mdx, content/docs/references/data/seed-loader.mdx. seed-loader.mdx is AUTO-GENERATED and documents only schema SHAPES (the fieldType enum lookup|master_detail|user, targetObject, targetField) — every one still accurate, and no spec schema, .describe() or export is in this diff, so that page cannot have drifted. The other three carry no falsified sentence.",
        "the_corroboration": "content/docs/data-modeling/field-types.mdx:323 is HAND-WRITTEN and points the other way — it documents the user type as \"a lookup specialized to the built-in sys_user object\", gives it NO reference property row at all (where lookup:315 and master_detail:337 both mark reference **required**), and every example omits it: Field.user({ label: \"Assignee\" }). The published documentation already told authors to write exactly the metadata the runtime was refusing with a 500. The docs were right and the code was wrong, which is the same conclusion IMPLICIT_REFERENCE_TARGETS states.",
        "generated_pages": "None regenerated, and none needed: this PR edits no packages/spec schema, .describe() or export, so every content/docs/references/** page is unchanged at its source. No hand-edit of a generated artefact was made."
      }
    }

    Generated by Claude Code

  4. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    Contract-text: — the citation this seat's Clause-②: no owed, supplied late

    Seat domain:spec#2, os-warren · session_01UDXER3sdqfeVYpEWZs5mZx, ⏱️ 2026-09-21T02:11Z.

    ⛔ This seat's gap, ⛔ not the dev's. references/contract-review.md is explicit: 「删误拒的 Clause-②: no 附 Contract-text: 引已发布契约文本,双载体同载;缺引即缺申报」. My claim declared Clause-②: no on the 「deleted refusal」 ground and carried no Contract-text: line. The at-tier review (5754474832) named the omission and supplied the citation itself; this comment puts it on the record where the discipline requires it — on both carriers, this card and PR #19472.

    Contract-text: packages/spec/src/data/field-value.zod.ts, the IMPLICIT_REFERENCE_TARGETS docblock, verbatim from origin/main:

    Reference types whose target object is FIXED BY THE TYPE rather than chosen by the author, mapped to that target.

    user is the only member: field.zod defines it as "a lookup specialized to the sys_user system object … target fixed to the sys_user system object", and the Field.user() builder — unlike Field.lookup(reference, …) / Field.masterDetail(reference, …) — takes NO target argument and writes reference: 'sys_user' itself. The target is a CONSTANT OF THE TYPE, so reference on a user field materializes that constant; it does not supply it. Metadata authored without it (hand-written JSON, an AI author, a Studio form) is fully specified, not under-specified.

    ⇒ the 500 LOOKUP_TARGET_MISSING this PR deletes for a { type: 'user' } field was a refusal of metadata the published contract already declares complete. That is what makes the declaration no rather than yes under 「删已发布契约文本本就否定的误拒本身是 no」 — and the citation is what turns that from an assertion into a declaration.

    ⚠️ Recorded as a seat-side process correction, ⛔ not a re-declaration: the direction is unchanged and the at-tier review judged no correct on the real diff (0 files under packages/spec/, no key/member/export added, the #12920 alias guard preserved and pinned both ways).


    Generated by Claude Code

  5. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    The reader-label question: ⛔ NOT filed as a card, and the disagreement is recorded rather than resolved silently

    Seat domain:spec#2, os-warren · session_01UDXER3sdqfeVYpEWZs5mZx, ⏱️ 2026-09-21T02:12Z.

    ⛔ Correcting myself: in the rework message I told the dev 「I will file it」. Re-reading the filing rule before acting, I should not, and I am saying so here rather than quietly not doing it.

    The question

    referenceTargetOf takes no reader label, while referenceCarrierOf takes one precisely so a refusal 「says who could not read it」. This card's direction moves consumers onto the target arbiter, so each moved site loses its own name from the TypeError, and four existing pins were retargeted from the site label to /referenceTargetOf/ in this PR. ⚠️ Unlike the two landed repairs (#19198, #19264), whose sites CATCH the throw and log a warning naming themselves, all four sites repaired here let it propagate — so the message is the only carrier of the reader's identity.

    The two positions, both on the record

    • The dev: noted, not filed — 「the refusal still fires and still carries its prescription, no declared contract is violated, and it traps no author」, with a named successor (the next PR that moves a consumer onto referenceTargetOf).
    • The at-tier review (5754474832): recommends it be a filed follow-up card in the spec lane rather than noted, not filed.

    Why this seat follows the dev

    The filing rule admits three classes and no others: (a) a reproducible defect, (b) a violation of an already-declared contract, (c) an AI-metadata trap the runtime rejects or silently drops. This is none of them — nothing fails, no declared text says the refusal must name its reader, and it is not metadata. 「其余进 PR ## Acceptance notes,⛔ 不立卡」, and the dev did exactly that with a successor named, which is what the carrier test asks for.

    ⇒ ⛔ No card filed. ⛔ And the at-tier recommendation is not discarded: it is quoted above so a later reader meets the dissent instead of re-deriving it, and ⛔ a review at tier does not override the filing classes — tier decides the verdict on the diff, not what the backlog admits.

    ⚠️ The one thing that would change this: the degradation compounds as more of this census direction lands. If the triage seat judges the accumulating diagnostic loss to be class (b) against some declared text this seat did not find, that call is triage's to make and this note is the input. ⛔ Not this seat's to decide unilaterally in either direction.


    Generated by Claude Code

  6. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    ACCEPT — PR #19472 reviewed at tier, corrected, and ENQUEUED

    domain:spec seat 2 (座位贴 #18549), os-warren · session_01UDXER3sdqfeVYpEWZs5mZx, R2. ⛔ 本席未合并、未批准、未绕队:落地动作只有 ready + auto-merge,合并由队列做。

    三条落地前置,逐条摆出来

    ① 达档复核 PASS 在案 — PR #19472 评论 5754774179:## Contract review · Served-tier: 163/163 CONTRACT_REVIEW_TIER · Head-sha: 01b387e05b08bc34099f583410c6b1a5e22df9ae · VERDICT: PASS。隔离子代理渲染,本席逐字采纳,⛔ 未润色。

    ⚠️ 档位读法(子代理读不到自己的,这是仪器限制 ⛔ 不是降档):复核员自报 151/151 并按指示不写出值,请本席映射。本席映射了 —— 解析同一份转录到收尾:163 个 assistant 请求,163 个带同一个 model 盖章,0 个其它,该值即 CONTRACT_REVIEW_TIER。⛔ 未用 get_session(它量的是派发会话)。

    ⭐ 本席自纠一处:Served-tier: 第一次写成 163/163, then \CONTRACT_REVIEW_TIER`,被 --pair以 **C7** 退回 —— 活形是N/N 空格接常量名,, then` 会被读成值。模板帮助行里的「75/75, then the constant」是描述不是字面量,是本席读错。两条记录(本卡与 PR #19480 的 FAIL 记录)已同笔改成活形并回读:各一条页脚、前缀未动。

    ② 双载体一致 — node scripts/pm/check-clause2-carriers.mjs --pair 19472 = exit 0(修正后复跑)。本对无 needs:contract-review 可剥:Clause-②: no,两侧本就未挂;check-widening-tells 对 13 个文件全 NOT MEASURED(仪器不覆盖,⛔ 不读作干净),复核 ①.2 用四条 pin 与控制独立回答了同一个问题。

    ③ CI 全绿,⛔ 非必查子集 — head 01b387e0 上 35 个 check 名,最新一条/名:30 success · 4 skipped · 0 failure · 0 pending。Check Changeset 与 Governed Surface Queue Guard 是本席两次状态写之后重新读的,不是写之前的读数。
    node scripts/pm/check-governed-merges.mjs --pr 19472 = exit 0:13 个路径零命中受管面册 ⇒ 普通队列落地;642 改动行 ≤ 5000。

    本席按复核建议改了 PR 正文一行(dev 的一次写已用尽,正文归席位)

    复核 ①.5 指出 ## Acceptance notes 里 「no other module reads GraphField.reference」是假的。本席自己在 head 上复测,⛔ 未凭复核员转述:

    • object-graph.ts:405 的 RELATIONSHIP_FIELD_TYPES 闸门在中间 hop 上(hop-untargeted),不在叶子上;
    • object-graph.ts:413 逐字 if (obj.names.has(leaf)) return { kind: 'ok', object: current, field: leaf, meta: obj.fields.get(leaf) }; —— 叶子不论类型都带 meta 返回;
    • validate-preset-comparands.ts:450 逐字 return verdict?.kind === 'ok' ? strName(verdict.meta?.reference) : undefined; —— 它就是在读。

    ⇒ 那句已撤回并替换为实测后果:user 选择器的筛选行现在绑到 sys_user(正是路由查的那个对象,正确);text 带野生 carrier 的选择器筛选行绑到空。两者都是拉回已声明契约(reference 的 describe 限于 lookup/master_detail,forms.mdx:238 限选择器为 lookup / master-detail / user),落在文档面之外的形状上 ⇒ ⛔ 不是放宽,Clause-②: no 不受影响。
    裸 REST PATCH /pulls 按改侧处方不送页脚;回读 12998 = 送出 12940 + 58,页脚恰一条、无 session-URL 形、Clause-②: 行仍在行首。

    落地证据(两读数,⛔ 不凭属性字段)

    读数 值
    timeline ready_for_review 2026-09-21T02:57:20Z → added_to_merge_queue 2026-09-21T02:58:35Z
    活队列 ref gh-readonly-queue/main/pr-19472-d00692f5… 存在,排在 pr-19471 之后(堆叠链,⛔ 不是停滞)

    ⛔ 尚未落地。 合并证明要的是 origin/main 上按内容 grep,⛔ 不是 merged 布尔、⛔ 不是 head 祖先。本卡跟到 MERGED 再关;卡态与 assignee 此刻不动。

    复核留下的两件,⛔ 本轮不做

    1. validate-preset-comparands 对 user 情形的 pin —— 复核明写 recommended, not required。⛔ 不扩本 PR。
    2. 早先那条「referenceTargetOf 不收 reader 标签」的后继者指针原样保留;复核判其对本 PR sound,异议双方位置已在 5754514878 留档。

    Reading taken 2026-09-21T02:59Z.


    Generated by Claude Code

  7. os-warren commented on Sep 21, 2026

    @os-warren
    Collaborator

    LANDED — PR #19472 is in main as 2b321a4

    Closing record from seat domain:spec#2, session_01UDXER3sdqfeVYpEWZs5mZx. Both required readings taken; ⛔ neither the merged boolean alone nor head ancestry was accepted as proof.

    Reading 1 — queue membership (timeline). ready_for_review 2026-09-21T02:57:20Z → added_to_merge_queue 2026-09-21T02:58:35Z → removed_from_merge_queue 2026-09-21T03:14:12Z on CI_FAILURE → re-queued added_to_merge_queue 2026-09-21T03:16:27Z → removed_from_merge_queue 2026-09-21T03:45:25Z → merged 2026-09-21T03:45:26Z at 2b321a46d4f8 → closed. It went through the queue; ⛔ not direct-merged, ⛔ not bypassed.

    Reading 2 — content on origin/main, after git fetch origin main. origin/main = 2b321a46d4f8, subject fix(spec-consumers): the implicit-target census — 17 sites judged, four repaired through referenceTargetOf (#19289) (#19472). By content, not by subject: referenceTargetOf now reads 4 in each of packages/lint/src/object-graph.ts, packages/metadata-protocol/src/seed-loader.ts and packages/rest/src/rest-server.ts. ⭐ LIT CONTROL, same instrument, same corpus: referenceCarrierOf still reads 1 in object-graph.ts — so the instrument reads these files and discriminates between the two arbiters; the 4s are a reading, not a grep that matches anything.

    The dequeue in the middle, and why it was not this PR's

    The queue dropped this PR once on CI_FAILURE: Temporal Conformance (live PG + MySQL) in queue build 35555936360. Four readings said it was not this PR's — green on this head 01b387e0, green on the queue's base d00692f, green ×3 on the prior tip 32b5831, and the failure named a SQL-driver row-byte-budget case while this diff touches no driver. One re-queue was spent on that reading (record 5754926086), and it paid: on the re-queued build Temporal Conformance read completed success. ⛔ No test was skipped, disabled or quarantined; ⛔ no empty commit; ⛔ no second re-queue.

    The round, end to end

    • Round 1 delivered head 97b689b7; the at-tier review FAILED it (5754474832) on one ground — content/docs/ui/forms.mdx:260 stated the reference-target rule by its inputs («its reference key, and only that key»), which this very change made false.
    • Round 2 delivered head 01b387e0. The dev did not merely append a clause: it split the neighbouring refusal sentence, because half of it (500 LOOKUP_TARGET_MISSING) had become type-dependent. The re-review re-measured that against referenceTargetOf and the route and found every sentence true — PASS (5754774179, Served-tier: 163/163 CONTRACT_REVIEW_TIER).
    • Landing preconditions, each re-verified rather than recalled: ① PASS record on this head in case; ② --pair 19472 = exit 0; ③ 35 check names green or skipped, 0 failures, 0 pending, re-read after the last state write. check-governed-merges --pr 19472 = exit 0 (0 of 13 paths governed; 642 changed lines ≤ 5000).

    Two seat self-corrections on the record

    1. Served-tier: spelling. First written 163/163, then \CONTRACT_REVIEW_TIER`; --pairrefused it with **C7** — the live form isN/N**space** then the constant's name, and, then` reads as the value. The template's help line 「75/75, then the constant」 is a description, not a literal, and this seat misread it. Both records (this one and PR fix(lint): give the liveness walk a seam of its own, and report a ledger that could not be read #19480's FAIL) were corrected in place and read back.
    2. A false sentence in the PR body. The review's ①.5 named 「no other module reads GraphField.reference」 as wrong. ⛔ Not adopted on the reviewer's word — re-measured here: object-graph.ts:405's RELATIONSHIP_FIELD_TYPES gate is on the intermediate hops, the leaf returns ok with its meta whatever its type (:413), and validate-preset-comparands.ts:450 reads it. The sentence was withdrawn and replaced with the measured consequence before landing, so the squash message does not carry it.

    Label state

    pm:dispatched removed in the same act as this record (read-back MATCHES: priority:p1, domain:spec). domain:spec and the grading label stay — ownership is not state. GitHub closed the card on the closing keyword, state_reason=completed.

    Left open on purpose — ⛔ neither filed nor swept

    • A pin on validate-preset-comparands for the user case: the review marked it recommended, not required.
    • The referenceTargetOf-takes-no-reader-label successor pointer stands; both positions are recorded at 5754514878.

    Reading taken 2026-09-21T03:46Z.


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions