Skip to content

docs: stop teaching the retired reference_to key and an object shape the repo never uses - #1809

Merged
os-zhuang merged 5 commits into
mainfrom
claude/issue-1686-metadata-brief-lookup-factory
Sep 9, 2026
Merged

os-zhuang merged 5 commits into
mainfrom
claude/issue-1686-metadata-brief-lookup-factory

Conversation

@claude

@claude claude Bot commented Sep 9, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1686

⚠️ Two declared deviations, both additive to the ruling, both isolated in their own commit so you can drop either without touching the ruled work. Neither reopens Zone 1. Measuring the brief revealed that the ruling's three named edits are correct but not sufficient: the same file teaches two more shapes the spec rejects, and one of them is made load-bearing by the ruled fix itself. Details and the question for the seat are in Declared deviations below. Everything the ruling asked for is delivered exactly as ruled.

Also declared: the ruling's line numbers were stale and I used the tree's. Ruling item 2 names AGENTS.md sites :62, :284, :141. Re-derived by text at my own commit, the three sites are :62, :141, :348 — matching the dispatch's own re-measurement. The count still balances at three. Control for the grep: masterDetail returns 3 lines in AGENTS.md, so the post-edit zero on reference_to is a real zero and not a broken search. No fourth site exists, and no other sentence in the file moves.


The two engine measurements, re-run at this commit

The ruling's pre-dispatch condition, explicitly not discharged by the triage seat. Both re-run against the pinned @objectstack/spec 17.3.0 (package.json pins it; the installed package self-reports it).

1. reference_to is not a key on FieldSchema

$ 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]))"

Verbatim receipt:

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.

Positive control — the canonical key parses, so the rejection is about the key and not the fixture:

{"success":true,"data":{"label":"Account","type":"lookup","reference":"crm_account", ...}}

⚠️ One transcription correction: the card and the R53 grading both render the hint as reference_to -> reference. The engine actually emits a Unicode arrow →. Immaterial to the ruling; recorded because the receipt was quoted as verbatim.

2. @objectstack/spec does not export ObjectSchema at the root

$ 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

A runtime in check cannot see type-only exports, and the brief used the name in a type position (as ObjectSchema), so the runtime reading alone would not have settled it. TypeScript settles it — on the brief's own two lines:

error TS2305: Module '"@objectstack/spec"' has no exported member 'ObjectSchema'.

⇒ the root exports neither a value nor a type by that name; the taught cast could never have compiled. The ruled replacement type-checks clean (tsc exit 0).


Zone 2 assumptions, tested

# PM assumption Verdict
2 The example should mirror a real file, and the 18 files are uniform Confirmed, and stronger than stated. 18/18 carry the byte-identical line import { ObjectSchema, Field } from '@objectstack/spec/data';; 18/18 call ObjectSchema.create(; 18/18 export a named const; 0/18 use a default export. src/objects/index.ts re-exports each by name (export { Contract } from './contract.object';).
3 :141 is the only checklist entry naming the key Confirmed inside AGENTS.md. ⚠️ But a checklist entry in a different file names it — see Acceptance notes.

One refinement on assumption 2: 17 of the 18 files do import from the root @objectstack/spec as well — for F, P, cel, expression. So the brief's error was never "the root import is wrong"; it was that the root import does not carry this symbol. The corrected example imports both entry points exactly as the tree does.


Declared deviations — a question for the seat

D1 — enable.search → enable.searchable (commit a679c3c) · forced by the ruled change

The ruling converts the example to ObjectSchema.create(...), a factory that parses. Under the as cast it replaces, nothing in the example was ever checked. Under .create(...) the brief's flagship example now throws:

Unrecognized key(s) on enable: search. Until this shape was closed an unknown flag was dropped without a word — the object shipped as if the author had never written it; enable is a closed vocabulary in which every flag carries an enforcement contract.

⇒ applying the ruling's three named edits and stopping would have shipped a brief whose lead example fails on first use — the exact defect class this card exists to remove, newly made load-bearing by the card's own fix. I judged shipping that indefensible, so I fixed it and am telling you rather than asking first.

searchable is the canonical spelling, so this preserves the author's intent — a rename, not a deletion. Probed against the closed vocabulary: trackHistory apiEnabled activities searchable files apiMethods accepted; search notes tasks events fullTextSearch rejected. Note the engine offers no "did you mean" here, unlike reference_to — this one is caught only by running the parse.

D2 — field example 4 to Field.formula(...) (commit 3b12fb6) · same class, NOT forced

This snippet sits outside the block the ruling converts, so it was broken before this PR and would have stayed broken. Same defect class, same file, and the engine names the fix:

Unrecognized key(s) on this field: formula, return_type. Did you mean formula → expression, return_type → returnType? …

⚠️ It is a trap past the spelling: taking that hint literally and writing returnType: 'currency' still fails, because the enum is number|text|boolean|date — currency is a field type, not a formula return type. An author who followed both the brief and the engine's own fix-up hint lands on a second rejection.

The tree answers identically in two places: quote_line_item and opportunity_line_item both write this exact field as Field.formula({ label: 'Total', expression: F`…` }) and omit returnType entirely (19 Field.formula calls in src/objects/; exactly one returnType in all of src/).

Why I judged this in-bounds rather than a new card: it is not a governance question. AGENTS.md names neither return_type nor returnType — zero occurrences (its single formula hit, :525, is the product's formula builder). So unlike the reference_to half, nothing ruled is touched. It is inside my declared file surface, mechanical, and its shape is pinned by the tree. Filing it instead would have needed another serial slot on a hot governed file behind three already-queued cards.

If you disagree with either, drop the commit — a679c3c and 3b12fb6 are independent and the ruled work is complete without them. Dropping D1 does mean shipping an example that throws.


Proof the corrected example actually works

Not "it looks right" — the two code blocks were extracted from the shipped file, spliced, and run:

  • tsc --noEmit --strict → exit 0, no diagnostics.
  • ObjectSchema.create(...) accepted it at runtime, emitting reference: 'crm_account' for the lookup, a CEL expression for the formula, and searchable: true in enable.

The before/after is a real reversal: every one of the four shapes this PR touches was rejected at the parent commit and is accepted now.


维护者速读(草稿)

改了什么 — AGENTS.md 三处、.github/instructions/metadata.md 一份简报,都不再教 reference_to 这个键。规范(@objectstack/spec)里根本没有这个键,正确写法是 Field.lookup('crm_account', ...) 工厂函数——仓库里 18 个对象文件 100% 都这么写。同时把简报里那段对象定义示例改成树里真实存在的形状:从 /data 导入、用会做校验的 ObjectSchema.create() 构造、具名导出。

为什么改 — 这是「文档教了一个运行时会拒收的形状」。更关键的是引擎的原话:这个形状被关掉之前,这类键是被静默丢弃的——字段照建,但作者写下的那条约束凭空消失,没有任何报错。AI 按简报写代码,写出来的就是这种少了约束的字段。AGENTS.md 是源头,简报只是忠实照抄,所以两个文件必须同一笔改,单改简报会让它和真相源文件打架。

风险与代价(含回滚) — 风险很低:改动全部是给 AI 看的文档,不含任何 src/ 代码,产品行为零变化,不发布给用户(changeset 用空 frontmatter 声明「不发布任何东西」)。回滚就是 revert 本 PR,不影响运行中的应用。⚠️ 需要您留意的是两处主动申报的越界(D1、D2):简报里另有两个同类错键,其中一个是被本次修复「激活」的——因为把不做检查的 as 强转换成了会校验的工厂,原本潜伏的错键现在会直接报错。这两处各自独立成一个 commit,您若不认可可以单独丢弃。

席位意见 —

你要做的 — 本 PR 触及受管面(AGENTS.md + .github/instructions/**),按 AGENTS.md:578 §How a green PR lands 必须保持 draft、不入队、不开自动合并,由维护者手动合并。请先就 D1 / D2 两处申报表态,再合并。


Acceptance notes

Out of scope, reported not fixed — .github/tasks/autonomous_feature_dev.md:20 still instructs the agent: "Ensure all foreign keys (reference_to) point to valid objects defined in Phase 2." That is the same defect class as this card and is a genuine fourth teaching site for the retired key — but it is outside my declared file surface (.github/instructions/metadata.md, AGENTS.md, .changeset/), so I did not touch it. It needs its own card. Flagging it because Zone 2 assumption 3 asked whether :141 was the only checklist entry naming the key: inside AGENTS.md it is, but this file has one too.

Noted, not filed — carriers exist for each, so none is orphaned:

  • Five test/*.ts files read reference_to as a tolerant alias (f.reference ?? f.reference_to ?? f.referenceTo). They pass today and this PR does not move them; the alias branch is simply dead tolerance. Carrier: whoever takes the autonomous_feature_dev.md card. ⛔ Not filed — dead code, not a reproducible defect.
  • test/opportunity-line-item-add-picker.test.ts:91 carries a comment calling reference_to "the authoring-side word AGENTS.md uses". This PR makes that sentence false. It is a stale comment in a test outside my file surface, and the test's behaviour is unaffected. Carrier: same card.
  • Field examples 1 and 2 in the brief remain raw object literals rather than Field.text(...) / Field.select(...). Both parse clean, so they are a style divergence from the tree, not a defect — deliberately left alone rather than polished into scope.

Guard not extended — per ruling item 5 and AGENTS.md, the #1233 guard reads paths, not identifiers, and extending it was refused twice. This PR adds no gate. The accepted cost stands: this class is caught by reading.


Gates

Full chain, run at 10bb230 (the final commit). Exit code captured into a variable before any pipe, and the heavy pair run under the shared verify lock.

gate exit note
pnpm validate 0
pnpm typecheck 0
pnpm lint 0
pnpm lint:i18n-gate 0
pnpm hygiene 0
pnpm hygiene:tokens 0 ratchet clean, and unchanged by construction — it measures src/**/*.ts and this diff touches no file under src/
pnpm build && pnpm test 0 os-verify-lock: VERDICT command-exit 0; 165 test files, 3453 passed, 1 skipped

That is every gate in pnpm verify (validate && typecheck && lint && lint:i18n-gate && hygiene && hygiene:tokens && build && test). Nothing narrowed, nothing deferred to CI.

Token ratchet reading, for the record: business semantics ~84,609 · interaction layer ~37,837 · authored total ~137,092 — identical to the parent commit.


Landing posture

⛔ Draft. ⛔ Not queued. ⛔ Auto-merge not armed. Maintainer merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq


Generated by Claude Code


Generated by Claude Code

…erence_to` key

`FieldSchema` has no `reference_to` key; the canonical spelling is `reference`,
and every lookup in this tree is authored through `Field.lookup(...)`. AGENTS.md
named the retired key at three sites, which is why the brief derived from it
taught the same key.

Measured at this commit against the pinned @objectstack/spec 17.3.0:
`FieldSchema.safeParse({type:'lookup',reference_to:'crm_account'})` fails with
`unrecognized_keys` and the engine's own "Did you mean `reference_to` ->
`reference`?" receipt. `reference_to` has zero occurrences in
`src/objects/*.object.ts` (18/18 files use `Field.lookup(...)`).

Sites re-derived by text at this commit: `:62`, `:141`, `:348` (the ruling's
`:284` had moved to `:348`). Control: `masterDetail` returns 3 lines, so the
zero on `reference_to` is a real zero. No other sentence in the file moves.

Refs #1686
Ruling: decision batch #74

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
…sing factory, named export

The Data Modeler brief taught a definition shape this repo does not have and the
spec does not accept: a root import, an `as ObjectSchema` cast, a default export,
and a raw `reference_to` key on the lookup example.

Measured at this commit against the pinned @objectstack/spec 17.3.0, with tsc
itself as the witness for the import half:

  $ tsc --noEmit  # on the brief's own two lines
  error TS2305: Module '"@objectstack/spec"' has no exported member 'ObjectSchema'.

The root exports neither a value nor a TYPE by that name, so the taught `as`
cast could never have compiled. `@objectstack/spec/data` exports it as a
function whose `.create` is a function. The ruled replacement type-checks clean
(tsc exit 0) and `ObjectSchema.create(...)` parses at runtime.

All 18 `src/objects/*.object.ts` files agree, and agree exactly: 18/18 carry the
identical line `import { ObjectSchema, Field } from '@objectstack/spec/data';`,
18/18 call `ObjectSchema.create(`, 18/18 export a named `const`, and 0/18 use a
default export. `src/objects/index.ts` re-exports each by name — `export {
Contract } from './contract.object';` — which is why the export must be named.

The lookup example now teaches `Field.lookup('crm_account', { ... })`, the only
form in the tree (58 calls). It emits `reference`, never `reference_to`.

Refs #1686
Ruling: decision batch #74, items 1 and 3

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
…he schema accepts

DECLARED DEVIATION (forced by the ruled change; not in the ruling's enumeration).

The ruling converts this example to `ObjectSchema.create(...)`, a factory that
PARSES. Under the `as ObjectSchema` cast it replaced, nothing in the example was
ever checked, so `enable.search` was inert. Under `.create(...)` the very example
the brief leads with now THROWS:

  Unrecognized key(s) on `enable`: `search`. Until this shape was closed an
  unknown flag was dropped without a word — the object shipped as if the author
  had never written it; `enable` is a closed vocabulary in which every flag
  carries an enforcement contract.

So applying the ruling's three named edits and stopping would have shipped 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 fix.

`searchable` is the canonical spelling and preserves the author's intent, so this
is a rename, not a deletion: probed against the closed vocabulary, `trackHistory`
`apiEnabled` `activities` `searchable` `files` `apiMethods` are accepted and
`search` `notes` `tasks` `events` `fullTextSearch` are rejected. Note the engine
offers NO "did you mean" for this one — unlike `reference_to`, it is caught only
by running the parse.

The whole corrected example now parses end to end.

Refs #1686

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
… are both rejected

DECLARED DEVIATION (same defect class as the ruled fix, same file; NOT in the
ruling's enumeration, and NOT forced by it — this snippet sits outside the block
the ruling converts, so it was broken before this PR and would stay broken).

Reported rather than assumed, and isolated in its own commit so the seat can drop
it without touching the ruled work.

Measured at this commit, the brief's field example 4:

  Unrecognized key(s) on this field: `formula`, `return_type`. Did you mean
  `formula` -> `expression`, `return_type` -> `returnType`? 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.

It is a TRAP beyond the spelling: taking the engine's hint literally and writing
`returnType: 'currency'` still fails, because the closed enum is
`number|text|boolean|date` — currency is a field TYPE, not a formula return type.
An author who followed both the brief and the fix-up hint would land on a second
rejection.

The tree answers it directly and identically in two places: `quote_line_item`
and `opportunity_line_item` both write this exact field as
`Field.formula({ label: 'Total', expression: F\`...\` })` and omit `returnType`
altogether — 19 `Field.formula` calls in `src/objects/`, exactly one `returnType`
anywhere in `src/`.

Not a governance question: `AGENTS.md` names neither `return_type` nor
`returnType` (zero occurrences; its single `formula` hit at :525 is the product's
formula builder), so unlike the `reference_to` half this touches no ruled surface.

Refs #1686

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
…leases nothing

Refs #1686

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
@vercel

vercel Bot commented Sep 9, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
hotcrm Ignored Ignored Sep 9, 2026 12:00pm UTC

Request Review

Copy link
Copy Markdown
Collaborator

维护者速读(终稿 · 席位复核后)

repo:hotcrm 席 · session_017FzrA1G4U89KEMf7wfLmqq · R58 · 2026-09-09T12:1xZ。本条是终稿,取代 PR 正文里 dev 写的草稿;席位已对着实际 diff 逐条核过,不是照抄它的自述。

改了什么 —— 两个文件不再教 reference_to 这个键。

为什么改 —— 规范里根本没有 reference_to 这个键,引擎的原话是:

在这个形状被关掉之前,这类键是被静默丢弃的 —— 字段照建,少掉它本该施加的约束、保护或计算。

⇒ AI 照着简报写,写出来的是少了一条约束的字段,而且不报错。AGENTS.md 是源头,简报只是忠实照抄,所以两个文件必须同一笔改——单改简报会让它跟真相源文件打架。

风险与代价(含回滚) —— 很低。全是给 AI 看的文档,src/ 零改动,产品行为零变化,changeset 是空 frontmatter(声明本 PR 不发布任何东西)。回滚 = revert 本 PR。CI 在 10bb230 上 6/6 全绿。

席位意见 —— ⭐ 建议两处申报的越界(D1、D2)都保留,理由如下,但它们各自独立成 commit,您想丢哪个都能单独丢:

  • D1(enable.search → searchable,commit a679c3c)—— 强烈建议保留。 这不是夹带,是本次修复自己制造出来的问题:裁决要求把不做检查的 as 强转换成会校验的工厂,于是简报里原本潜伏的错键 enable.search 现在会直接抛错。只做裁决点名的三处就收手,等于发布一份「头号示例一跑就炸」的简报——正是这张卡要消灭的那类缺陷。⚠️ 而且这个键引擎不给「你是不是想写…」提示(不像 reference_to 会给),只有真正跑一遍 parse 才发现。
  • D2(公式字段示例,commit 3b12fb6)—— 建议保留。 同一份简报里另一个被拒收的键。dev 核过 AGENTS.md 对 return_type / returnType 零处提及,所以不推翻任何已裁内容;写法照抄树里两个真实文件。⚠️ 它顺手拆掉一个陷阱:照着引擎自己的提示改成 returnType: 'currency' 仍然会失败,因为那个枚举是 number|text|boolean|date,currency 是字段类型不是公式返回类型。

⚠️ 一处我认为可接受、但如实告诉您的:公式示例用了 F 这个模板标签,用注释说明了它来自 '@objectstack/spec',但没写进示例上方那行 import。两段代码在不同小节,本来就不是一次性复制的整体,所以可接受——只是在一份主题就是「示例必须真能跑」的文件里,这是最可能变成下一张卡的形状。

你要做的 —— ⚠️ 这张 PR 碰的两个文件都是治理面(AGENTS.md + .github/instructions/**),按 PR #1798 刚落地的 AGENTS.md:578 §How a green PR lands,它保持 draft、不入队、不挂自动合并,由您手工合并。席位已按规矩向 os-zhuang 与 hotlong 两个账号请审,⛔ 席位不批准、不合并任何治理面 PR。

⇒ 您只需要一个动作:认可 D1 / D2 保留(或指出要丢哪个),然后合并。

⭐ 顺带一提,dev 还测出第四处教这个退役键的地方——.github/tasks/autonomous_feature_dev.md:20——但它在申报的文件面之外,所以没有顺手改,而是报告了。席位已另立 #1812。合掉这张之后,那里就是全仓最后一处。


Generated by Claude Code

@zhuangjianguo zhuangjianguo added the needs-user-decision Needs the maintainer's call before work proceeds label Sep 9, 2026 — with Claude
@os-zhuang
os-zhuang marked this pull request as ready for review September 9, 2026 12:19
@os-zhuang
os-zhuang added this pull request to the merge queue Sep 9, 2026
Merged via the queue into main with commit bd6a5a5 Sep 9, 2026
10 checks passed
zhuangjianguo pushed a commit that referenced this pull request Sep 9, 2026
…etired `reference_to` key

The copy-paste brief that puts an agent in the Autonomous Development role closed
with a self-correction bullet telling it to check that "all foreign keys
(`reference_to`) point to valid objects". `FieldSchema` has no `reference_to` key;
before that shape was closed the key was dropped silently, so an author following
the bullet got a field created minus the constraint they wrote.

This was the fourth and last teaching site in the repo. The other three
(`AGENTS.md` `:62`, `:141`, `:348` and `.github/instructions/metadata.md`) were
corrected by PR #1809, which recorded this one as outside its file surface.

The replacement mirrors what landed rather than coining a third phrasing: "lookup /
master-detail targets" and "name real objects" are AGENTS.md's Phase-3 checklist
entry verbatim, and the `Field.lookup(...)` / `Field.masterDetail(...)` parenthetical
is the spelling AGENTS.md uses at `:62` and `:348`. The brief's own "defined in Phase
2" anchor is kept. The bullet is the only line that moves.

Measured at this commit, zeros paired with controls: `reference_to` has 0 matches
under `.github/` (control `Field.lookup` returns 2 there) and 0 teaching sites
repo-wide. The 10 remaining matches are non-teaching and untouched: 3 in PR #1809's
changeset, 2 under `docs/archive/2026-02/`, and 5 lines across 4 `test/*.ts` files
reading the key as a tolerant alias.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017FzrA1G4U89KEMf7wfLmqq
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cd CI plumbing and the verification pipeline documentation Improvements or additions to documentation needs-user-decision Needs the maintainer's call before work proceeds

Projects

None yet

3 participants