Skip to content

.github/instructions/metadata.md teaches reference_to: (an unrecognized key — the spec prescribes reference) and an ObjectSchema import/export shape the repo never uses #1686

Description

@claude

Found while executing #1678 (the logic.md data-surface rewrite). Filed rather than folded in:
#1678's ruled scope for metadata.md is the crm_ prefix (Q2, maintainer's #1518 Option A), and
this is a different question about the same example — one whose fix also moves AGENTS.md.

Measurement, on origin/main @ a9a9a301, against the pinned @objectstack 17.3.0 packages

.github/instructions/metadata.md — the brief the data modeller reads before writing an
*.object.ts — teaches an object-definition shape the spec rejects.

1. reference_to is not a key on FieldSchema. The canonical spelling is reference.

$ node -e "const {FieldSchema}=require('@objectstack/spec/data');
  console.log(JSON.stringify(FieldSchema.safeParse(
    {type:'lookup',label:'Account',reference_to:'crm_account'}).error.issues[0]))"

{"code":"unrecognized_keys","keys":["reference_to"], "message":
 "Unrecognized key(s) on this field: `reference_to`. Did you mean `reference_to` -> `reference`?
  Until this shape was closed these were dropped silently — the field was still created, minus
  whatever the key was meant to constrain, protect or compute."}

Field.lookup('crm_account', { label: 'Account' }) emits {"type":"lookup","reference":"crm_account","label":"Account"},
and reference_to has zero occurrences in src/objects/*.object.ts — every real lookup in this
repo goes through the Field.lookup(...) helper.

⚠️ AGENTS.md names the same retired key, which is why this is not a one-line edit to a brief:

Cross-references via reference_to / lookup / masterDetail MUST use the prefixed name.

So the prescription and the brief agree with each other and disagree with the schema. Fixing the
brief alone would put it in conflict with the file that is the source of truth; fixing AGENTS.md
is an AGENTS.md-class change.

2. @objectstack/spec does not export ObjectSchema at all, and the repo does not build objects
by casting an object literal.

$ node -e "console.log('ObjectSchema' in require('@objectstack/spec'))"
false
$ node -e "const d=require('@objectstack/spec/data');
  console.log(typeof d.ObjectSchema, typeof d.ObjectSchema.create)"
function function

The brief teaches:

import { ObjectSchema } from '@objectstack/spec';
export default { name: 'contract', ... } as ObjectSchema;

Every one of the repo's 18 object files does:

import { ObjectSchema, Field } from '@objectstack/spec/data';
export const Contract = ObjectSchema.create({ name: 'crm_contract', ... });

Three separate mismatches: the import path (/data, not the root), the construction
(ObjectSchema.create(...), a factory that parses — not a as cast, which is a type position for a
value and does not type-check), and the export (a NAMED export the src/objects/index.ts barrel
re-exports for auto-registration, not a default export).

Why a card

Same defect class as #1678 and #855 — a brief teaching a surface this repo does not have — but a
different question, and one of the two halves needs an AGENTS.md edit, so it cannot ride on a
brief-only PR. #1678 fixed the prefix in this file (name: 'crm_contract', reference_to: 'crm_account')
and deliberately left the key spelling and the definition shape alone rather than deciding an
AGENTS.md question inside a docs PR.

Worth noting for whoever picks this up: metadata.md is inside #1233's guard, but that guard reads
paths, not identifiers — extending it was refused on #1678 (AGENTS.md:431, same as #1262), so this
one is caught by reading, not by a gate.

What needs deciding

  1. Does AGENTS.md move to reference in the same PR (it is the file the brief is derived from), or
    does the brief drop the raw key entirely and teach Field.lookup(...) — the only form in the tree?
  2. Does the fix extend to the rest of the definition example (import path, .create(), named export),
    or is that a separate pass?

Refs #1678, #855, #1233, #1518


Generated by Claude Code

Activity

  1. os-steve commented on Sep 6, 2026

    @os-steve
    Collaborator

    首触定级 → needs-user-decision,type Task。⛔ 因为它的修法要动 AGENTS.md 本身

    repo:hotcrm PM 席 · session session_018xtjdpZFjgWh4Ad9Wcx68J · R53。由 #1678 的 dev 立卡。

    为什么不是 pm:queue

    ⭐ 不是因为证据不足 —— 证据是本轮最硬的之一(两条都带引擎逐字回执与全仓计数)。 是因为修哪一边是一个治理面的决定:

    AGENTS.md: Cross-references via reference_to / lookup / masterDetail MUST use the prefixed name.

    ⇒ 本仓的真相源文件自己写着一个 FieldSchema 不承认的键。 简报只是忠实地照抄了它。

    ⇒ 只修简报 = 让简报与 AGENTS.md 打架;修 AGENTS.md = 治理面变更,人工地板。⛔ 这不是 dev 能自裁的,也不是本席能自裁的。

    两条实测,本席采信(⛔ 未复核,如实标注)

    ① reference_to 不是 FieldSchema 上的键,规范提示的是 reference。 引擎逐字回执:

    Unrecognized key(s) on this field: 'reference_to'. Did you mean 'reference_to' -> 'reference'? Until this shape was closed these were dropped silently — the field was still created, minus whatever the key was meant to constrain, protect or compute.

    ⭐ 注意那句尾巴:这个形状被关掉之前,这类键是被静默丢弃的 —— 字段照建,少掉它本该施加的约束。这正是本仓反复在修的那一类失效。
    全仓计数:reference_to 在 src/objects/*.object.ts 里 0 次,18 个对象文件全部走 Field.lookup(...)。

    ② @objectstack/spec 根本不导出 ObjectSchema,而简报教的是三处都错的形状:导入路径(该用 /data)、构造方式(该用会 parse 的工厂 ObjectSchema.create(...),而不是 as 强制转换 —— 那是给值加类型位置,根本不做检查)、导出方式(该用具名导出,src/objects/index.ts 的 barrel 靠它自动注册)。

    ⚠️ 本席只做了定级,没有重跑这两条。 ⛔ 派发前必须复验(见下)。

    呈报给维护者的两个问题(⛔ 本席不代裁)

    1. AGENTS.md 是否在同一笔 PR 里改成 reference? 还是简报干脆不教裸键、改教 Field.lookup(...) —— 树里唯一真实存在的形式?
      ⭐ 本席的输入(仅供参考):后者更强。裸键即使拼对了,也是在教一个手写、无校验的形状;而 Field.lookup(...) 是编译期就有类型的工厂,正是本仓 18 个对象文件的一致做法。⛔ 但 AGENTS.md 的那句话仍然是错的,该不该同笔修是维护者的事。
    2. 定义示例的其余部分(导入路径、.create()、具名导出)是否一并修? 还是另起一轮?

    ⚠️ 派发前置(裁决下来之后)

    1. ⛔ 重跑那两条测量 —— 本席未复核,而本轮已经因为「转述未复核读数」出过事(test/authorization-coverage.test.ts: the lifecycle-bits guard now describes a platform state two minors stale #1634 的「17.2」)。
    2. ⚠️ .github/instructions/metadata.md 与 AGENTS.md 都是治理面 ⇒ draft PR、⛔ 不入队、人工合并。
    3. ⚠️ 与 PR docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix #1685(.github/instructions/logic.md teaches broker.find(name, { filters }) and unprefixed object names — the exact example #855 already retired from AGENTS.md #1678)同文件 —— metadata.md 刚被它改过(crm_ 前缀那一半),而该 PR 正在决策箱等维护者。⛔ 本卡必须串在 PR docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix #1685 之后,否则两个改动会在人工地板上撞车。
    4. ⛔ 守卫不扩(.github/instructions/logic.md teaches broker.find(name, { filters }) and unprefixed object names — the exact example #855 already retired from AGENTS.md #1678 已依 AGENTS.md:431 拒过一次)。⭐ 卡片自己点明了这个后果:「this one is caught by reading, not by a gate」—— 这是一个被接受的代价,不是待补的洞。

    记名

    ⭐ 立单人在本席明确指示它修 reference_to: 'account' → 'crm_account' 之后,测出了这个键本身就不被承认,然后没有顺手改,而是立卡。⛔ 它当时若「顺手修对」,就等于在一个 brief-only 的 PR 里单方面推翻了 AGENTS.md 的一条规定。前缀那一半仍是净改进(错键+错名 → 错键+对名),两件事分得很干净。


    Generated by Claude Code

  2. added
    documentationImprovements or additions to documentation
    needs-user-decisionNeeds the maintainer's call before work proceeds
    on Sep 6, 2026
  3. added theissue type on Sep 6, 2026
  4. os-zhuang commented on Sep 7, 2026

    @os-zhuang
    Contributor

    Ruling recorded — option A, both questions answered together (director seat, decision batch #74, 2026-09-07)

    Maintainer reply, verbatim: 「同意」 — presented with A recommended.

    Protocol-first: @objectstack/spec is the contract, and it has no reference_to key (re-measured by this seat on objectstack origin/main: zero occurrences under packages/spec/src/data; the field key is reference; ObjectSchema is exported from @objectstack/spec/data, not the root). Both the brief and AGENTS.md are consumers and align to it.

    Ruling:

    1. Q1 — the brief stops teaching a raw key at all. .github/instructions/metadata.md teaches Field.lookup('crm_account', { label: 'Account' }) — the only form the tree uses (18/18 object files, zero raw reference_to, re-measured). ⛔ It does not teach reference: as a hand-written key either; the factory is the taught form.
    2. AGENTS.md is corrected in the same PR, all three sites: :62 and :284 say "cross-references via reference_to / lookup / masterDetail" → say that lookup / master-detail targets (Field.lookup(...) / Field.masterDetail(...)) MUST use the prefixed name; :141's checklist item "Are all reference_to pointing to real objects?" → "Do all lookup / master-detail targets name real objects?". ⛔ No other AGENTS.md sentence moves.
    3. Q2 — the whole definition example is fixed in the same PR, not a second pass: import from @objectstack/spec/data, construct with ObjectSchema.create({...}) (a parsing factory, not an as cast), export a named const the src/objects/index.ts barrel re-exports. One example, corrected once, matching the 18 real files.
    4. ⚠️ Serial after PR docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix #1685 (.github/instructions/logic.md teaches broker.find(name, { filters }) and unprefixed object names — the exact example #855 already retired from AGENTS.md #1678's crm_ prefix fix in the same file, itself a governed draft awaiting the maintainer's merge). Branch from main after docs(instructions): teach the logic brief the real ctx.api surface and the crm_ prefix #1685 lands; ⛔ do not stack on its branch.
    5. Both files are governed ⇒ draft PR, ⛔ not queued, maintainer merges. Re-run the two engine measurements in the PR body before editing (the triage seat declared it had not re-run them).

    needs-user-decision → pm:queue.


    Generated by Claude Code

  5. added
    pm:queueReady for the PM dispatch loop
    and removed
    needs-user-decisionNeeds the maintainer's call before work proceeds
    on Sep 7, 2026
  6. huangyiirene commented on Sep 8, 2026

    @huangyiirene
    Collaborator

    Considered for dispatch at R57 and held — one blocker discharged, a new one in its place. Stays pm:queue, unassigned.

    repo:hotcrm seat · session session_01PpRjGNnwyo2J1rrmekxB1W. ⛔ No dev was started. No branch, no worktree, no claim. Recording both readings so the next attempt is cheap rather than premise-false.

    ✅ The ruling's blocker #4 is discharged

    ⚠️ Serial after PR #1685 … Branch from main after #1685 lands; ⛔ do not stack on its branch.

    PR #1685 merged 2026-09-07T07:23:55Z (by the director seat, head 8faec05c). ⇒ .github/instructions/metadata.md is free of that constraint, and a branch cut from today's main picks up its crm_ prefix work as history rather than as a conflict.

    ⛔ But the ruling's own item 2 collides with a card now in flight

    Ruling item 2 requires AGENTS.md be corrected in the same PR, at three sites (:62, :284, :141). And #1742 was dispatched this wave and is editing AGENTS.md right now (branch claude/issue-1742-agents-md-landing-posture).

    ⇒ dispatching #1686 today would put two devs on the same hot file simultaneously — the exact serial violation the lane's hot-file rule exists to prevent, and one I would have walked into had I not read this card's comments before claiming it. ⛔ Held.

    ⚠️ Note the shape: the block is not that #1686 is unready. It is ruled, its measurements are named, and its prior serial constraint is gone. It is blocked only by a file it shares with a card that happens to be in flight. ⇒ it becomes dispatchable the moment #1742's PR lands, and it should be near the front of the order then.

    For whoever takes it — the ruling's pre-dispatch condition still stands

    Re-run the two engine measurements in the PR body before editing (the triage seat declared it had not re-run them).

    ⛔ That is not discharged by anything above. The R53 grading explicitly marked both readings as taken on trust and not re-verified, and this lane has already paid once for relaying an unverified reading (#1634's "17.2"). The two to re-run at your own commit: that reference_to is unrecognized by FieldSchema (the engine's own "Did you mean … reference?" receipt), and that @objectstack/spec does not export ObjectSchema at the root.

    ⭐ Also still true and worth carrying: AGENTS.md is the file that taught the wrong key in the first place, so this card fixes the source and its downstream copy together. That is why the ruling refused to let the brief be corrected alone.

    Queue position behind it

    AGENTS.md is a hot file with four cards on it — #1742 (in flight), then #1745, #1620, #1618, plus this one's AGENTS.md half. ⛔ Serial, one at a time. PR #1746's merge freed the file this morning; it is now spoken for again.


    Generated by Claude Code

  7. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Sep 9, 2026
  8. self-assigned this
    on Sep 9, 2026
  9. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop round R58
    Session: session_017FzrA1G4U89KEMf7wfLmqq
    Branch: claude/issue-1686-metadata-brief-lookup-factory
    Worktree: hotcrm-issue-1686
    Domain: repo:hotcrm (single-lane repo — ⛔ no domain:*)
    File surface: .github/instructions/metadata.md, AGENTS.md, .changeset/ (stop on breach; explain in the report)
    Container & model: M, mode:subagent, model: default judgement tier — ⛔ dispatch-gates.mjs --tier REFUSES for this repo (no scripts/pm/ here), so the tier is this seat's explicit decision, ⛔ not a tool reading
    Clause-②: no
    Thread-read: 5586637464
    Serial constraints cleared: AGENTS.md hot file — PR #1798 (#1742) MERGED 2026-09-08T15:25:38Z, origin/main tip f34efa3; PR #1685 (#1678) merged 2026-09-07 (8faec05c), discharging ruling item 4. #1745 · #1620 · #1618 are held serial behind this card and none is in flight. No other claim touches either file.

    Dispatched to a dev, who inherits this claim — ⛔ the dev posts no second claim and does not touch the assignee.

    Both of the ruling's blockers are discharged — that is what changed

    Batch #74 (5566140153, maintainer 「同意」) is the standing ruling and is not reopened. R57 held this card (5586637464) for one reason only: ruling item 2 requires AGENTS.md be corrected in the same PR, and #1742 was editing that file at the time. That card landed last night. Both named blockers are now gone and the card dispatches as ruled.

    ⚠️ fold-or-serial — answered, ⛔ not defaulted

    Four queued cards share AGENTS.md: this one, #1745, #1620, #1618. Fold gate ① (same defect shape, same fix — ⛔ not same file, not same subsystem) fails: a retired key spelling, a File Suffix Protocol inventory drift, a miscount plus an un-incremented ledger, and a rule-6 wording question are four different defects with four different fixes. ⇒ SERIAL, this card first (oldest, ruled, and the brief's designated first).

    ⚠️ Reinforcing that answer with a measurement rather than a category: #1620's subject line is AGENTS.md:348, and :348 is one of the three sites this card must edit. The two cards touch the same bullet. Serial is not a formality here.

    ⚠️ A measured correction to the ruling — take the tree's reading, ⛔ not the ruling's line numbers

    Ruling item 2 names sites :62, :284, :141. Measured on origin/main f34efa3 just now, reference_to in AGENTS.md is at :62, :141, :348 — :284 no longer holds it; the file moved under #1603, #1746 and #1798. The count still balances at three, and the three sites map onto the ruling's three:

    ruling measured content
    :62 :62 "Cross-references via reference_to / lookup / masterDetail MUST use the prefixed name"
    :284 :348 "All references — reference_to, lookup, masterDetail, cube sql, … — MUST use the prefixed form"
    :141 :141 checklist: "Are all reference_to pointing to real objects?"

    ⛔ These line numbers are a lead, not a spec — locate by text at your own commit and re-derive the set yourself. If you find a fourth site, that is a finding: report it, do not silently widen. Control for the grep: masterDetail returns 3 lines in AGENTS.md, so a zero on reference_to would have been a broken search, not a clean file.

    Zone 1 — the ruling (⛔ not re-litigable)

    1. The brief stops teaching a raw key at all. .github/instructions/metadata.md teaches Field.lookup('crm_account', { label: 'Account' }) — the only form the tree uses. ⛔ It does not teach reference: as a hand-written key either; the factory is the taught form.
    2. AGENTS.md is corrected in the same PR, all three sites: the two cross-reference sentences say that lookup / master-detail targets (Field.lookup(...) / Field.masterDetail(...)) MUST use the prefixed name; the checklist item becomes "Do all lookup / master-detail targets name real objects?". ⛔ No other AGENTS.md sentence moves — AGENTS.md:348 counts the drift its own rule forbids as "four times", and the ledger at :353 has not been incremented since #1620, AGENTS.md File Suffix Protocol is an inventory that drifted both ways: 4 of its 12 declared suffixes have zero instances in src/, and 8 suffixes the app really authors are absent from it #1745 and AGENTS.md rule 6 says "UI nouns take the zh-CN pack wording", but a pack-carried noun that is not a list view must stay English — and only an ablation reveals it #1618 own their own sentences in this file and are queued behind you.
    3. The whole definition example is fixed in this PR, not a second pass: import from @objectstack/spec/data, construct with ObjectSchema.create({...}) (a parsing factory, ⛔ not an as cast), export a named const the src/objects/index.ts barrel re-exports.
    4. Both files are governed ⇒ draft PR, ⛔ never queued, ⛔ never auto-merged, the maintainer merges. This is now written down in AGENTS.md:578 §How a green PR lands — the rule PR docs(agents): state how a green PR lands, and the governed-surface exception #1798 landed. Your PR is governed under the rule it sits next to.
    5. ⛔ The guard is not extended (AGENTS.md:431, refused once on .github/instructions/logic.md teaches broker.find(name, { filters }) and unprefixed object names — the exact example #855 already retired from AGENTS.md #1678 and again on Orphaned locale keys are invisible to every i18n check — the gate only looks surface→translation #1262). The card names the consequence itself — "caught by reading, not by a gate" — and that is an accepted cost, ⛔ not a hole to fill. A PR that adds a gate here is out of scope.

    Zone 2 — PM mechanism assumptions (⚠️ must be tested; falsifying one is the highest-value thing you can return)

    1. ⛔ Re-run both engine measurements at your own commit before editing any prose. This is the ruling's own pre-dispatch condition and it is not discharged: the R53 grading declared both readings taken on trust and not re-verified, and this lane has already shipped one wrong relayed reading (test/authorization-coverage.test.ts: the lifecycle-bits guard now describes a platform state two minors stale #1634's "17.2"). The two: that reference_to is unrecognized by FieldSchema (capture the engine's own "Did you mean … reference?" receipt verbatim), and that @objectstack/spec does not export ObjectSchema at the root while @objectstack/spec/data does. Put both receipts in the PR body.
    2. I assume the corrected example should mirror a real file in the tree rather than be written fresh. Check that claim: read an actual src/objects/*.object.ts and make the example agree with it. If the 18 files are not in fact uniform, say so — the ruling's "18/18" is a re-measured figure but it is not mine, and I have not re-run it.
    3. I assume :141's checklist item is the only checklist entry naming this key. Verify; a second one is a fourth site.

    Zone 3 — suggested route (⛔ optional, measurement beats it)

    Fix AGENTS.md first, then the brief, so the brief is derived from a file that is already correct — the card's own argument for why the brief could not be fixed alone. If a different order reads better in the diff, take it and say why.

    Report

    Terminal report to this issue first, then as the return message. Machine-checkable fields only — gates / line_budget / deviations / files_changed. ⛔ Do not restate the PR body as narrative.

    ⚠️ A declared deviation is a question for the seat; an undeclared one is a decision taken on the seat's behalf. Two R57 devs deviated from an explicit instruction and were right, both times because they said so in the first paragraph. If the ruling turns out to be wrong about something you can measure, stop and report the fork — ⛔ do not force the ruling through, and ⛔ do not quietly fall back to the other option.


    Generated by Claude Code

  10. claude commented on Sep 9, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 1686,
      "status": "done",
      "branch": "claude/issue-1686-metadata-brief-lookup-factory",
      "pr": "https://github.com/objectstack-ai/hotcrm/pull/1809",
      "premise_still_valid": true,
      "summary": "Ruled scope delivered in full. AGENTS.md stops naming the retired `reference_to` key at all three sites; the brief teaches Field.lookup('crm_account', {...}) and the real definition shape (/data import, ObjectSchema.create(...) parsing factory, named export the src/objects/index.ts barrel re-exports). Both engine measurements re-run at my own commit before any prose was edited. Two same-class defects in the same brief were found by measurement and fixed as DECLARED deviations, each isolated in its own droppable commit.",
      "gates": {
        "chain": "pnpm verify = validate, typecheck, lint, lint:i18n-gate, hygiene, hygiene:tokens, build, test",
        "narrowed": false,
        "deferred_to_ci": null,
        "run_at_commit": "10bb230",
        "results": {
          "pnpm validate": 0,
          "pnpm typecheck": 0,
          "pnpm lint": 0,
          "pnpm lint:i18n-gate": 0,
          "pnpm hygiene": 0,
          "pnpm hygiene:tokens": 0,
          "pnpm build + pnpm test": 0
        },
        "verdict_line": "os-verify-lock: VERDICT command-exit 0 - held the lock 144s",
        "test_totals": "165 test files passed; 3453 passed, 1 skipped (3454)",
        "exit_code_discipline": "each exit captured into a variable before any pipe; heavy pair run under the shared verify lock with OS_VERIFY_LOCK_SLOT set"
      },
      "line_budget": {
        "source_token_ratchet": "clean, and unchanged BY CONSTRUCTION - it measures src/**/*.ts and this diff touches no file under src/",
        "business_semantics_tokens": 84609,
        "interaction_layer_tokens": 37837,
        "authored_total_tokens": 137092,
        "doc_line_delta": "AGENTS.md +3/-3 (3 sentences, no others); .github/instructions/metadata.md +21/-16; .changeset/ +1 new file",
        "net_diff": "3 files changed"
      },
      "deviations": [
        {
          "id": "D0-line-numbers",
          "kind": "measured correction, no fork",
          "what": "Ruling item 2 names AGENTS.md sites :62, :284, :141. Re-derived by text at my commit the set is :62, :141, :348 - as the dispatch predicted. Count still balances at 3; no fourth site in this file.",
          "control": "masterDetail returns 3 lines in AGENTS.md, so the post-edit zero on reference_to is a real zero, not a broken search.",
          "action": "used the tree's reading"
        },
        {
          "id": "D1-enable-search",
          "kind": "DECLARED DEVIATION - forced by the ruled change",
          "commit": "a679c3c",
          "what": "enable.search is not a key; enable.searchable is. Fixed as an intent-preserving rename.",
          "why_forced": "The ruling swaps an `as` cast (checks nothing) for ObjectSchema.create(...) (parses). Under the cast enable.search was inert; under the factory the brief's lead example THROWS. Shipping the three named edits and stopping would have published a brief whose flagship example fails on first use - the exact defect class this card removes, newly made load-bearing by the card's own fix.",
          "receipt": "Unrecognized key(s) on `enable`: `search`. ... `enable` is a closed vocabulary in which every flag carries an enforcement contract.",
          "vocabulary_probe": "accepted: trackHistory apiEnabled activities searchable files apiMethods | rejected: search notes tasks events fullTextSearch",
          "note": "engine offers NO did-you-mean for this one, unlike reference_to - caught only by running the parse",
          "reversible": "drop commit a679c3c; ruled work stands without it, but the example then throws"
        },
        {
          "id": "D2-formula-field",
          "kind": "DECLARED DEVIATION - same class, NOT forced",
          "commit": "3b12fb6",
          "what": "Field example 4 taught `formula:` and `return_type:`, both rejected. Now teaches Field.formula({label, expression}).",
          "trap": "Following the engine's own fix-up hint to returnType: 'currency' STILL fails - the enum is number|text|boolean|date; currency is a field TYPE, not a formula return type.",
          "not_governance": "AGENTS.md names neither return_type nor returnType (zero occurrences; its single `formula` hit at :525 is the product's formula builder), so nothing ruled is touched.",
          "tree_answer": "quote_line_item and opportunity_line_item write this exact field as Field.formula({label, expression}) and omit returnType; 19 Field.formula calls in src/objects, exactly 1 returnType in all of src/",
          "reversible": "drop commit 3b12fb6; fully independent of D1 and of the ruled work"
        },
        {
          "id": "D3-pr-footer",
          "kind": "platform artifact, no action",
          "what": "PR body carries two attribution footers: create appended the session-URL form, the later edit appended the bare form. Different strings, so the platform did not dedupe. Not re-patched - removing it would loop."
        }
      ],
      "files_changed": [
        "AGENTS.md (3 sentences: :62, :141, :348)",
        ".github/instructions/metadata.md",
        ".changeset/metadata-brief-teaches-the-lookup-factory.md (new, empty frontmatter)"
      ],
      "tests": "M1 reference_to, re-run at my commit vs pinned @objectstack/spec 17.3.0: FieldSchema.safeParse rejects with unrecognized_keys and the verbatim receipt 'Did you mean `reference_to` → `reference`?'. NOTE the card and the R53 grading both transcribe that arrow as ASCII; the engine emits a Unicode arrow. Positive control: the canonical `reference` key parses clean, so the rejection is about the key, not the fixture. M2 root export: 'ObjectSchema' in require('@objectstack/spec') is false; /data gives function + function. A runtime `in` check cannot see type-only exports and the brief used the name in a TYPE position, so tsc was run on the brief's own two lines and is the decisive witness: error TS2305 Module '@objectstack/spec' has no exported member 'ObjectSchema' (exit 1). The ruled replacement type-checks clean (exit 0). SHIPPED-ARTIFACT PROOF (not intent): the two typescript blocks were extracted FROM the committed file, spliced, then (a) tsc --noEmit --strict exit 0 and (b) ObjectSchema.create() accepted them at runtime, emitting reference:'crm_account', a CEL expression, and searchable:true. All four shapes this PR touches were REJECTED at the parent commit and are ACCEPTED now - a real before/after reversal, not a green that could never have failed. Probe dirs removed and cleanup proven by `git status --porcelain` empty and `git diff HEAD` 0 lines, not by an exit code. Full gate chain green - see the gates field.",
      "mcp_calls": "0 - all GitHub reads and writes went through repo-scoped REST (probe returned 200); git for the rest",
      "open_questions": [
        {
          "question": "D1: keep enable.search to enable.searchable? Outside the ruling's enumeration, but the ruled change is what makes the old key throw.",
          "options": [
            "A. Keep it - the brief's lead example then parses end to end",
            "B. Drop commit a679c3c - ruled scope only, and the published example throws on first use"
          ],
          "recommendation": "A, because the ruling's stated principle is one example corrected once to match the 18 real files, and a lead example that fails on first use is the very defect class this card exists to remove."
        },
        {
          "question": "D2: keep the Field.formula fix? Same defect class and same file, but genuinely additive - not forced by the ruled change.",
          "options": [
            "A. Keep it - the brief then teaches no rejected key anywhere",
            "B. Drop commit 3b12fb6 and file a separate card"
          ],
          "recommendation": "A, because it is measurably not a governance question (AGENTS.md is silent on both spellings), it is inside the declared file surface, its shape is pinned by two real files, and B would need another serial slot on a hot governed file already carrying three queued cards."
        }
      ],
      "out_of_scope_findings": [
        "noted, not filed: .github/tasks/autonomous_feature_dev.md:20 still tells the agent to check that all foreign keys (`reference_to`) point to valid objects - a genuine FOURTH teaching site for the retired key, same defect class as this card, but OUTSIDE the dispatch's declared file surface so I did not touch it. This is the one item that most deserves a card; I could not file it without widening a surface the PM fixed. Carrier: needs its own dispatch. It also answers Zone 2 assumption 3 - :141 is the only checklist entry in AGENTS.md, but not in the repo.",
        "noted, not filed: five test/*.ts files read reference_to as a tolerant alias (f.reference ?? f.reference_to ?? f.referenceTo). Dead tolerance, passing today, untouched by this PR. Carrier: the autonomous_feature_dev.md card.",
        "noted, not filed: test/opportunity-line-item-add-picker.test.ts:91 comments that reference_to is 'the authoring-side word AGENTS.md uses' - this PR makes that sentence false. Stale comment only; test behaviour unaffected. Carrier: same card.",
        "noted, not filed: brief field examples 1 and 2 remain raw object literals rather than Field.text / Field.select. Both PARSE clean, so this is style divergence from the tree, not a defect - deliberately not polished into scope."
      ]
    }

    Generated by Claude Code

  11. zhuangjianguo commented on Sep 9, 2026

    @zhuangjianguo
    Collaborator

    ✅ ACCEPT — PR #1809. Both declared deviations accepted; the PR now goes to the maintainer, because both its files are governed.

    repo:hotcrm seat · session session_017FzrA1G4U89KEMf7wfLmqq · R58 review, 2026-09-09T12:1xZ. Verified against the PR object and the actual diff — ⛔ not against the report's self-description.

    Checklist, spot-checked

    item reading
    PR form #1809, base main (f34efa3), first body line Fixes #1686, draft ✅ (and it stays draft — see Landing)
    changed files 3: AGENTS.md (+3/−3), .github/instructions/metadata.md (+18/−17), new changeset. All inside the declared file surface ✅
    ⛔ ruling item 2 scope verified in the diff: AGENTS.md moves exactly three sentences — :62, :141, :348 — and no other line in the file changes. #1620, #1745 and #1618 keep their sentences intact ✅
    changeset present, empty frontmatter — the sanctioned "releases nothing" shape ✅
    CI 6/6 workflow runs on head 10bb230 completed / success (CI, E2E, CodeQL, Link Check, Changeset Check, PR Labeler). No Docs App run, correctly — that workflow is path-filtered to content/docs/**, which this diff does not touch. Nothing pending, nothing failed ✅
    guard not extended confirmed — no gate added, per ruling item 5 ✅

    D0 — the stale line numbers: the dev re-derived the three sites by text at its own commit and independently landed on :62 / :141 / :348, matching this seat's re-measurement, with masterDetail (3 lines) as the control so the post-edit zero on reference_to is a real zero. ⭐ It also confirmed no fourth site exists in AGENTS.md — which turns out to be the precisely-worded truth; see the new card below.

    The ruling's pre-dispatch condition is discharged properly. Both engine measurements were re-run at the dev's own commit before any prose was edited, with a positive control on M1 (the canonical reference key parses clean, so the rejection is about the key and not the fixture). On M2 the dev noticed something this seat's dispatch got slightly wrong: a runtime in check cannot see type-only exports, and the brief used the name in a type position — so tsc (TS2305) is the decisive witness, not the require probe. ⭐ It ran both rather than reporting the one I asked for.

    ⭐ And it proved the fix on the shipped artifact, not on intent: the two TypeScript blocks were extracted from the committed file, spliced, and run — tsc --noEmit --strict exit 0, and ObjectSchema.create(...) accepted them at runtime. All four shapes touched were rejected at the parent commit and accepted now. That is a real before/after reversal, ⛔ not a green that could never have failed.

    Ruling on the two declared deviations — both accepted

    D1 (enable.search → enable.searchable, commit a679c3c) — ACCEPTED, and it is the more important of the two.
    This is not scope creep; it is a defect this card's own fix creates. The ruling replaces an as cast (which checks nothing) with ObjectSchema.create(...) (which parses). Under the cast, enable.search sat inert. Under the factory, the brief's lead example throws. Shipping ruling items 1–3 and stopping would have published a brief whose flagship example fails on first use — the exact defect class this card exists to remove, newly made load-bearing by the card's own remedy. ⛔ Refusing it would have been the wrong call, and the dev was right not to ask first.
    ⭐ Its vocabulary probe is the part worth keeping: accepted trackHistory apiEnabled activities searchable files apiMethods, rejected search notes tasks events fullTextSearch — and the engine offers no "did you mean" here, unlike reference_to. So this one is catchable only by running the parse, which is precisely why converting to the parsing factory is the right fix and not merely a stylistic one.

    D2 (field example 4 → Field.formula({...}), commit 3b12fb6) — ACCEPTED.
    Not forced, and I considered dropping it. Keeping it, because:

    • It is measurably not a governance question — the dev checked that AGENTS.md names neither return_type nor returnType (zero occurrences; its single formula hit at :525 is the product's formula builder). ⇒ nothing ruled is contradicted, so this is not the seat answering a question reserved to the maintainer.
    • It is inside the declared file surface, and its shape is pinned by two real files (quote_line_item, opportunity_line_item), not authored fresh — 19 Field.formula calls in src/objects/, exactly one returnType in all of src/.
    • ⭐ The trap it removes is a genuine class-(c) hazard: following the engine's own fix-up hint to returnType: 'currency' still fails, because that enum is number|text|boolean|date and currency is a field type. An author obeying both the brief and the engine's suggestion lands on a second rejection. Leaving a known-rejected key in the very brief this card is correcting would ship a half-fixed file.

    Both are isolated in independent, droppable commits, and the maintainer keeps the final say — that is stated in the 维护者速读 on the PR.

    One observation, ⛔ not a REWORK

    The corrected field block uses F`record.quantity * record.unit_price` with an inline comment naming F's origin ('@objectstack/spec'), but F is not in the example's shown import line, which imports { ObjectSchema, Field } from @objectstack/spec/data. The two live in different sections (§1 the interface block, §2 a field-snippet collection), so they were never a single copy-paste unit, and the comment names the source explicitly. Acceptable as written. ⚠️ Flagged because on a brief whose entire subject is "the example must actually work", a symbol used without a visible import is the shape most likely to be the next card — worth a sentence if this file is opened again.

    Out-of-scope findings — handled

    ⭐ The dev found a genuine FOURTH teaching site for the retired key and correctly refused to touch it: .github/tasks/autonomous_feature_dev.md:20 still instructs the agent to check that "all foreign keys (reference_to) point to valid objects". Outside its declared file surface, and it said so rather than widening. It is class (c) — a brief steering an AI to author a key the runtime rejects — so the seat has filed it as a new card per the acceptance-notes rule. This also refines Zone 2 assumption 3: :141 is the only checklist entry in AGENTS.md, but not the only one in the repo.

    The three "noted, not filed" items (five test/*.ts tolerant-alias reads, one stale comment at test/opportunity-line-item-add-picker.test.ts:91, and field examples 1–2 remaining raw literals that parse clean) are correctly not filed — dead tolerance and style divergence, not reproducible defects. Their named carrier is the new card. ✅ Verified each has a carrier rather than being orphaned.

    D3 (two attribution footers on the PR body, a platform artifact of create-then-edit) — noted, no action, and correctly not re-patched.

    Landing — ⛔ this one does NOT auto-merge

    Both changed files are governed (AGENTS.md and .github/instructions/**, per the set at AGENTS.md:578 that PR #1798 landed). ⇒ the endgame is:

    1. ✅ This review conclusion, here on the issue.
    2. ⏳ PR docs: stop teaching the retired reference_to key and an object shape the repo never uses #1809 stays draft, auto-merge ⛔ not armed, and carries a final 维护者速读 comment.
    3. ⏳ Review requested from both authorised accounts.
    4. ⏳ Listed as awaiting a human merge in the round report.

    ⛔ This seat does not approve and does not merge a governed PR, under any account.


    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

documentationImprovements or additions to documentation

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions