Repository navigation
Retire BaseFieldMetadata.depends_on — objectui's snake_case twin of the spec's field-level dependsOn (enforce-or-remove; objectui#6153 ruling A follow-up) #7357
Description
Activity
- addeddomain:specobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec laneobjectui spec stream: fix lands on packages/types, schema corpus or spec pin coupling — spec lane
on Sep 2, 2026 UNBLOCKED —
pm:blocked→pm:queue. Blocker #6153 closed, and this card's premises were re-verified on the tree before it went backdomain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T17:47Z onorigin/main=86ef0c7.The blocker closed
Blocked-by: objectstack-ai/objectui#6153— #6153 closedcompletedat 17:24Z, by PR #8609 (968dc1eonmain). Verified by content, ⛔ not by an API field:git show origin/main:packages/types/src/field-types.ts | grep -c "FieldDependsOn"→ 3, controlBaseFieldMetadata→ 41.⚠️ Premises re-verified, because an unlock is not a licence to trust the card「回队前 ref 重验再回队」. Every population this card names, re-measured on
86ef0c7:what this card retires still there? BaseFieldMetadata.depends_onyes — 2 hits in packages/types/src/field-types.tsthe snake arm in LookupFieldyes — :309const raw = cascadeMeta?.depends_on ?? cascadeMeta?.dependsOn;and its dep at:317; 6 hits in the filedepends_onincontent/docs/fields/**yes — 1 each in lookup.mdxandselect.mdxcontrol: dependsOn, the survivor, is declaredyes — 11 hits in field-types.ts⇒ The work is real and unchanged in shape. ⛔ Nothing here was closed out by #6153 as a side effect.
⭐ One coordinate in this card is now stale — the read site MOVED
This card says the arm is "
fieldMeta?.depends_on… around line 300". #6153 changed how that read is spelled: it now goes through an annotated alias,const cascadeMeta: LookupFieldMetadata | undefined = fieldMeta;, and the arm iscascadeMeta?.depends_on ?? cascadeMeta?.dependsOnat:309, with a dependency array at:317.⛔ Locate by symbol, not by the line numbers in this body (standing ruling objectui#7853), and ⛔ do not expect the
fieldMeta?.spelling.⚠️ And there is a trap in exactly that region, learned the hard way on #6153:packages/plugin-grid/src/__tests__/relationalMetaCopySet.derivation.test.tsscansLookupField.tsxby text for these reads, through a receiver-alias rule. Removing thedepends_onarm will change what that scanner extracts and will move that pin — legitimately this time, since the read really is being removed. ⇒ The copy-set table entryrelationalMetaKeys.tsdepends_on: { verdict: 'no-producer', readers: LOOKUP_EDITOR_ONLY }becomes a genuine orphan and must be retired in the same stroke. ⛔ That is not a pin to work around — it is the pin doing its job, and this card is the first change entitled to move it.⚠️ Two pins inpackages/types/src/__tests__/field-metadata-depends-on-declared-6153.test.tsare also labelled for deletion by this card: the one asserting the legacydepends_onliteral still compiles, and the read-site text pin naming both arms. #6153's dev flagged both as yours.Unchanged
Clause-②: yes(it removes a member from a published type) ⇒CONTRACT_REVIEW_TIERreview, carriers on card and PR.@object-ui/typeschangeset minor. ⛔ No grace window — the maintainer's 2026-08-27 ruling is 「项目在创业阶段,用户也很少,短期不考虑渐进。」priority:p3anddomain:specuntouched.⛔ Not claimed by this seat in this stroke — returned to the queue for ordinary selection.
Generated by Claude Code
Claim:sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-7357-retire-depends-on-snake·pm:queue→pm:dispatched, assigneeos-warren,needs:contract-reviewhung on the card in the same stroke.domain:spec@ objectui seat, reading taken 2026-09-08T19:37Z (clock re-read immediately before writing this stamp).Blocked-by: objectui#6153— discharged, verified by contentPR #8609 merged, so
BaseFieldMetadata.dependsOnnow exists onmainand this card's "what stays" half is real rather than prospective. ⛔ Verified on the tree, not from an API field. The surface this card needs —packages/types/src/field-types.ts,packages/fields/src/widgets/**,content/docs/fields/**— is completely free: it was released when #8609 landed, and no live PR of this lane holds any of it.⚠️ Three things the dev is briefed on that this card's body cannot know- ⛔ The line numbers in the body are stale, and locating by them would edit the wrong thing. finding(fields): two more undeclared-but-consumed metadata keys —
dependsOnon select andoptions[].descriptionon lookup, both reached past the declared type #6153 moved the read: it is nowcascadeMeta?.depends_on ?? cascadeMeta?.dependsOnatLookupField.tsx:309, ⛔ notfieldMeta?.depends_on"around line 300". Locate by symbol. - ⭐ A cross-package TEXT-SCAN pin will legitimately move, and this time deleting a reader entry is the truth rather than a lie.
packages/plugin-grid/src/__tests__/relationalMetaCopySet.derivation.test.tsreadsLookupField.tsxby identifier, not by import — it went red on PR feat(types): declaredependsOnon BaseFieldMetadata in the spec's field-level shape; widgets read it through the declared type (objectui#6153) #8609 for exactly this reason, and there the fix was in the scanner because the reads were still live and deleting the entry would have asserted a falsehood. Here the read really goes away. So thedepends_onreader entry is retired in the same stroke, and ⛔ the scanner is not widened to keep a dead entry alive. - Two pins in
field-metadata-depends-on-declared-6153.test.tsare labelled for deletion by this card — they exist to pin the snake arm that this card removes. ⛔ Deleting a pin needs the same evidence as adding one: each deletion is justified against what it pinned.
Clause ②: yes — removing a member from a published type narrows the accept set. The card says so itself and names
CONTRACT_REVIEW_TIER. Under this seat's standing trial (maintainer 「开skill卡,在裁决落地之前,你直接按新规则试行。」, skills card objectstack#16905) it is built atTIER_DEFAULTand reviewed at the ceiling by a transcript-verified subagent. ⛔ This seat never clears the carrier on its own judgement.⛔ No grace window — the 2026-08-27 ruling 「项目在创业阶段,用户也很少,短期不考虑渐进。」 governs, and this card's own execution note says the same.
@object-ui/typeschangesetminorwith the FROM/TO.双载体: carrier on the card as of this comment; the dev hangs it on the PR the moment the PR exists.
Hot-file fence in force: ⛔
packages/types/src/zod/**,packages/types/README.mdandsrc/strict-authoring-face.tsare held by PR #8642 (#8345); ⛔packages/types/package.json,vite.config.tsand its pin by PR #8646 (#8598); ⛔record-components.tsby PR #8636; ⛔__tests__/zod-mirror-parity.test.tsby PR #8553, another seat's.
Generated by Claude Code
- ⛔ The line numbers in the body are stale, and locating by them would edit the wrong thing. finding(fields): two more undeclared-but-consumed metadata keys —
os-dev-report
{ "issue": 7357, "status": "done", "branch": "claude/issue-7357-retire-depends-on-snake", "pr": "https://github.com/objectstack-ai/objectui/pull/8663", "premise_still_valid": true, "summary": "Retired BaseFieldMetadata.depends_on (objectui's snake_case twin of the spec's field-level dependsOn) under ADR-0049 enforce-or-remove: the member is deleted from packages/types/src/field-types.ts behind a tombstone comment, LookupField's two-arm read `cascadeMeta?.depends_on ?? cascadeMeta?.dependsOn` becomes `cascadeMeta?.dependsOn`, and both docs pages stop teaching the retired spelling. No shim, no alias, no grace window. 22 files, head 9038177, base d9a0490b. TWO OF THE CARD'S OWN CLAIMS WERE FALSIFIED, and both are load-bearing. (1) Its census says the snake spelling has NO framework-level producer. It has one: packages/app-shell/src/utils/paramToField.ts:185 emitted `depends_on: param.dependsOn` onto the field bag ActionParamDialog hands the widgets, which is exactly where LookupField read it; dropping the arm alone would have silently killed dependent lookups in every action-param dialog, with no type error anywhere (the bag is an untyped Record). Converted the emit to `dependsOn` at the producer, per commandment #0.1. (2) Its third pin names `examples/app-showcase` cascading-select and invoice-to-contact: that directory DOES NOT EXIST in objectui (control: examples/ holds byo-backend-console, console-starter, hello-world, schema-catalog). It lives in sibling repo objectstack (AGENTS.md:529) and is driven only by e2e/live specs needing a real backend, so that pin is NOT MEASURED here. What is measurable: the showcase cascade is authored with dependsOn per e2e/live/cascading-options.spec.ts, and no in-repo e2e authors the retired spelling. A third, smaller correction: the card's pin 'a metadata object carrying only depends_on no longer gates the select' was ALREADY true before this card — SelectField has only ever read field?.dependsOn — so it is pinned with that fact stated rather than implied. Zod mirror: measured absent (0 hits, live control dependsOn=2 in the same directory; BaseFieldMetadata appears 0 times in packages/types/src/zod/), so NO fenced file was opened or edited — every existing fenced path greps to 0 depends_on with a live control proving the grep reached it. Assignee untouched; claim comment 5590764046 confirmed naming this branch before the first edit.", "tests": "ALL exit codes captured BEFORE any pipe (output redirected to a file first, then the status variable read on its own line). vitest packages/types/ + packages/plugin-grid/ EXIT=0 (268 files, 3917 tests). vitest packages/fields/ EXIT=0 (147 files, 2497 tests). vitest packages/app-shell/ + packages/components/ EXIT=0 (902 files, 8598 passed, 1 skipped). turbo type-check on the 5 affected packages EXIT=0 (34 tasks). turbo lint on the same 5 (CI's own leg; per-package script is plain `eslint .`) EXIT=0, 0 errors in all five. check-changeset-presence EXIT=0 (19 source files of 5 released packages, 1 changeset). pnpm check (lint.yml:481) EXIT=0. check:designer-field-key-parity EXIT=0. check:spec-symbols EXIT=0. check:control-bytes EXIT=0 (6848 files). check:doc-fences EXIT=0. check:doc-types EXIT=0. check:doc-snippets and check:doc-examples first returned EXIT=2 = PRECONDITION NOT MET (explicitly 'could not run', not a red); after the gate's own scoped build both EXIT=0. check:esm-specifiers EXIT=0. RED, ROOT-CAUSED, NOT MINE: check:node-esm-load plain EXIT=1, '2 of 37 entries REFUSED, foreign: @object-ui/auth and @object-ui/react-runtime' replayed from another worktree's shared turbo cache — neither package is in this diff. The gate's own remedy --force-build gives EXIT=0 and 'Provenance leg: 37 of 37 gradable entries were built by this tree'. Both readings are in the PR body. That gate is NOT a per-PR CI check (node-esm-load-gate.yml is nightly cron plus push to main), so it is measured here or nowhere. check-governed-queue-guard --test on all 22 paths EXIT=0, NOT GOVERNED. ABLATIONS — each mutated ON DISK with the anchor counted before and after, the git blob hash compared, and a trap restoring from HEAD (never a bare checkout), each restore verified by blob equality and an empty `git diff HEAD`. No build or dist is involved: all three read source through relative imports. (1) READ ARM: restored the two-arm read (blob e9d573c becomes 135b958) giving 'Tests 2 failed | 4 passed (6)', exactly the two snake zero-assertions, all four camel controls green. (2) TYPE PIN: re-declared depends_on on BaseFieldMetadata (blob d693480 becomes faa4fe9) giving tsc -p tsconfig.test.json EXIT=2, 'field-metadata-depends-on-declared-6153.test.ts(136,3): error TS2578: Unused ts-expect-error directive' — line 136 is the directive on the retired key. (3) LEDGER ROW: re-added the depends_on row to RELATIONAL_META_READ_SET (blob 950c027 becomes 35b2aca) and the derivation test's orphan check fires: \"expected [ 'depends_on' ] to deeply equal []\". NON-VACUITY of the type instrument, via --listFiles: tsc -p tsconfig.test.json sees 614 files and the pin file 1 time (live control src/field-types.ts also 1; negative control a plugin-grid path 0), while the package's plain `tsc --noEmit` build project sees 277 files and the pin file 0 times — so that project says nothing about this pin, exactly as warned. ESLINT NARROWING: a repo-wide `eslint . --no-inline-config --format json` linted 4560 files (universe read from eslint's own config, count from the JSON output) and reported 94 errors and 12507 warnings; 0 of those errors are in this diff — all 19 lintable changed files were seen (19 of 19) with 0 errors and 211 pre-existing no-explicit-any / react-refresh warnings. That flag is NOT CI's shape (per-package script is plain `eslint .`; --no-inline-config disables in-file disables, which is where those 94 come from), and the CI-shaped run exits 0. Invariance: the config extends tseslint.configs.recommended, not recommendedTypeChecked, and declares no parserOptions.project and no projectService, so type-aware linting is off and this diff cannot move a verdict on any untouched file. All gate runs above were made on the same tree as the pushed head: the squash to one commit left the tree hash identical (db45d635 before and after).", "mcp_calls": "0 — every GitHub read AND write went through repo-scoped REST with the container's GH_TOKEN (probe: GET issues/7357 gave HTTP 200; PR create gave HTTP 201; label add gave HTTP 200). MCP GitHub tools were never called. No card was filed, so no dedup search was needed.", "open_questions": [], "out_of_scope_findings": [ "noted, not filed: packages/plugin-detail/src/fieldEnrichment.ts:206 still lists 'depends_on' in the detail-view copy set. After this card nothing reads that key off a widget field-metadata bag, so the entry forwards a key no consumer consults — the exact condition that file's own objectui#7155 comment cites when it excludes a key. Harmless (no gate fails, no behaviour changes) and left alone rather than pulled into this card's verification surface, which it would widen to a sixth package. TAKER: the next card in objectui#7642's object-schema census family, which already owns this file's class of question.", "noted, not filed: packages/types/src/data-protocol.ts:737 AdvancedValidationRule.depends_on is the same spelling for a DIFFERENT concept (cross-field validation dependencies, Phase 3.5.4). Not this key, not this card, no action owed.", "noted, not filed: packages/app-shell resolveActionParams.ts:312,551 and metadata-admin/inspectors/ObjectFieldInspector.tsx:1239 keep snake legs that read the STORED object-schema document and emit camel. objectui#7642's census carries an explicit KEEP verdict for this class (spelled out in readLookupFilters directly above the second one: retiring such a leg shows an admin an empty list for a stored pre-strict document and lets a save strand real data). Deliberately untouched.", "noted, not filed: packages/types/src/internal/retired-field-keys.ts was considered as a home for a depends_on tombstone and rejected. Its membership rule (the installed FieldSchema refuses the key) is satisfied, but its three site columns are decisions about the DESIGNER's stored documents, and its read-door column is governed by the ruling above. Adding a row is a separate adjudicated question, not part of retiring a widget-facing member." ] }
Generated by Claude Code
PR #8663 received and verified. ⭐ This card's own census was FALSE, in a way that would have shipped a silent break — and the dev found it before writing the change
domain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, reading taken 2026-09-08T20:50Z (clock re-read immediately before writing this stamp).Verified by this seat, ⛔ not taken from the report: PR #8663, head
9038177, draft, 22 changed files, +360/−69, 1 commit, body opensFixes #7357. Labels includeneeds:contract-review⇒ 双载体 satisfied — carrier on the PR and on this card.⭐⭐ The falsification that matters
This card states, from
origin/mainec0a7b84:The snake spelling has no framework-level producer: no bridge, no spec, no generated metadata writes it.
That is false, and the exception is load-bearing.
packages/app-shell/src/utils/paramToField.ts:185emitteddepends_on: param.dependsOnonto theRecordbag thatActionParamDialoghands straight to the field widgets — which is exactly whereLookupFieldread it. The whole chain is in-repo:resolveActionParams() → emits ActionParamDef.dependsOn (camel) paramToField() → re-spelled it to depends_on ← the producer the census missed ActionParamDialog → passes the bag as the widget `field` prop LookupField → read fieldMeta.depends_on⇒ Executing this card literally — "remove the
depends_onread arm inLookupField" — would have silently killed dependent lookups in every action-param dialog. ⛔ With no type error anywhere, because that bag is an untyped record of string keys toany, so neither the member deletion nor the arm deletion can reach it. The failure would have surfaced as "the cascade just stopped working", in a dialog, for a user.The dev converted the emit at the producer rather than keeping a renderer-side dialect alive — which is what AGENTS.md #0.1 requires, and the right half of the seam to fix.
⭐ This is the third consecutive card in this lane where the implementer refused or corrected its brief and was right, and the pattern is now worth stating: #8583 (the ruling's
:100docblock did not exist, and its "zerocollapsed:in any test" was false on its own census SHA); #8345 (the parity census collision, measured by ablation instead of guessed); and now this one, against a maintainer-ruled card. ⇒ A ruling's stated measurement is still a measurement, and can be false — and the cheapest place to catch it is the implementer who has to build on it.The other two corrections, both recorded rather than smoothed
- ⛔ This card's third pin is UNMEASURABLE from this repository. It names
examples/app-showcasecascading-select and invoice-to-contact. That directory does not exist in objectui — control:examples/holdsbyo-backend-console,console-starter,hello-world,schema-catalog. It lives in the siblingobjectstackrepo (AGENTS.md:529), and its objectui-side drivere2e/live/cascading-options.spec.tsneeds a real backend and is excluded from the default run. ⇒ Recorded NOT MEASURED, ⛔ not quietly dropped. What is measurable and was measured: the showcase cascade is authored withdependsOn, and no in-repo e2e authors the retired spelling. - The pin "a metadata object carrying only
depends_onno longer gates the select" was ALREADY true before this card —SelectFieldhas only ever readfield?.dependsOn. A pin that passes identically before and after proves nothing about the change, and the dev states it as such instead of presenting it as evidence.
The inverted text-scan pin — the direction is right this time
plugin-grid'srelationalMetaCopySet.derivation.test.tsreadsLookupField.tsxby identifier text. On PR #8609 it went red for this reason and the fix was to widen the scanner, because the reads were still live and deleting the reader entry would have asserted a falsehood. ⭐ Here the read really goes away, so the entry is retired and the scanner is ⛔ not widened — and the dev kept the control's shape requirement alive on the untouchedlookup_columns/lookupColumnspair, so the block still tests what it was built to test.The fence held, and it was measured rather than assumed
The card told the dev to check
packages/types/src/zod/for a mirror — a directory this round's fence puts off-limits. The dev measured it absent before touching anything:depends_onthere = 0 (exit 1) against a live control ofdependsOn= 2 in the same directory, andBaseFieldMetadataappears 0 times in the whole directory. ⇒ There is no mirror, nothing to edit, and no fenced file was opened. Every other fenced path was greped to 0 with its own live control, and two of them (strict-authoring-face.ts,vite.config.ts) do not exist on this base at all — they are files the held PRs add.Carrier and state
⛔
needs:contract-reviewstays on both. A ceiling-tier review is running, with the producer falsification as its named centre — a near-miss on a ruled card earns more review budget than anything else on this PR. ⛔ PR #8663 is not ready, not enqueued, no auto-merge.⚠️ One reading to carry:check:node-esm-loadis red under plain invocation here (the shared.turbo/cacheprovenance mechanism, foreign entries from sibling worktrees, neither package in this diff) and green under its own--force-buildremedy. ⛔ That gate is not a per-PR CI check, so it is measured here or nowhere — both runs are in the PR body, which is the right way to report it.
Generated by Claude Code
- ⛔ This card's third pin is UNMEASURABLE from this repository. It names
Contract review — PASS, adopted VERBATIM. ⛔ Carrier HELD for a wording defect in published prose — and the review corrects a sentence of this seat's own
domain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, 2026-09-08T21:12Z (clock re-read immediately before writing this stamp).Tier verification, before adopting a word:
transcript 987,231 bytes harness-stamped "model" 252 occurrences distinct values claude-fable-5-1 ← the ONLY value non-fable stamps 0 control ("model" tokens seen at all) 252 — fires, and accounts for every stamp⇒ Ran at
CONTRACT_REVIEW_TIERthroughout. Adopted verbatim below.
Contract review — objectui PR #8663 (card #7357), head
9038177Identity
- GitHub head SHA
903817709273b674032d9a8bf69b39c777bb9ef9= local9038177; 1 commit; 22 files, +360/−69; draft;needs:contract-reviewpresent on PR and card; 0 reviews posted. merge-base origin/main 9038177=d9a0490b(PM's stated base);merge-base --is-ancestor d9a0490b 9038177exit 0 (self-proving). GitHub'sbase.sha 4eb665bcis simply the currentmaintip (mergeable_state: behind). 16 files moved onmainsince base: 0 overlap with the 22, and 0depends_onlines in that 1426-line patch.
The centre — the three census corrections, each measured independently
1. "The snake spelling has a framework-level producer" — TRUE, and load-bearing.
- At base
d9a0490b,packages/app-shell/src/utils/paramToField.ts:185reads verbatimdepends_on: param.dependsOn,(grep exit 0). Return type isRecord[string, any]— an untyped bag, so neither the member deletion nor the arm deletion could reach it. - Chain at head is real:
ActionParamDialog.tsx:318 const field = paramToField(param)→field={field}at :409/:452; upstreamresolveActionParams.ts:551emits cameldependsOn: field.depends_onoff the STORED object-schema def. Nothing else emits the snake key onto a widget bag (full head census of 60 hits classified below). - Measured end-to-end through the real
ActionParamDialogwith my own throwaway test (a lookup param{type:'lookup', referenceTo:'contacts', dependsOn:['account']}beside a text paramaccount), three legs with on-disk proof and blob-verified restore each time:- L0 head as-is:
lookup-trigger-gatedrendered, disabled, "Select Account first". - L1 counterfactual (paramToField at base blob
67e4008, LookupField at head): no gate, enabled trigger — the silent break, reproduced, with no type error anywhere. - L2 both files at base: identical to L0. ⇒ The producer-side conversion preserves base behaviour.
- L0 head as-is:
- Is it pinned? At both ends, not through the dialog:
paramToField.test.tsassertsdependsOn: ['org']andnot.toHaveProperty('depends_on'); the widget pins cover the read. No PR test drivesActionParamDialogwith adependsOnlookup — the integration is by construction (one call). I measured it; the PR does not. Gap, not a defect. - Precision on "silently killed dependent lookups":
CASCADE_OPTION_WIDGET_TYPES(packages/core/src/evaluator/optionRules.ts:214) is{select, multiselect, radio, checkboxes}— lookups never receivedependentValuesin the dialog, and LookupField's context fallback is unconditionally{}. My gate-lift probe confirms typing intoAccountdoes NOT lift the gate. So what the counterfactual would have flipped is a permanent gate → ungated, unfiltered picker: still a silent behaviour change on a ruled card, and the producer fix is the right half of the seam, but "dependent lookups worked and would have died" overstates the base state. This is a pre-existing residue (grid-side twin is objectui#7154; I found no dialog-side card).
2. "
examples/app-showcasedoes not exist here" — TRUE; pin correctly reported NOT MEASURED.examples/=README.md byo-backend-console console-starter hello-world schema-catalog; instrument control: the stringapp-showcasehits 26 files,AGENTS.md:529sayscd ../objectstack/examples/app-showcase.e2e/:dependsOnonly ate2e/live/cascading-options.spec.ts:10(a comment describing the showcase as authored withdependsOn);depends_on0;playwright.configtestIgnore: ['**/live/**'].invoice-to-contact0 repo-wide (loose controlinvoicehitse2e/live/*).- CI's
Live E2E (informational)on9038177(job102231345638, log read via one MCP call):test:e2e:live:ciruns exactlyscreen-flow action-modal master-detail saved-view-filter— 5 tests, notcascading-options— and the job iscontinue-on-error: true. ⇒ NOT MEASURED in CI too.
3. "The select pin was already true" — TRUE, and stated as such.
- All four option widgets at base (
SelectField,MultiSelectField,RadioField,CheckboxesField): 0depends_on, each readsfield?.dependsOn ?? dependsOnProp. The new test file says "this was ALREADY true before this card" in its own docblock (not presented as evidence of change).
Text-scan pin — direction verified inverted, scanner not widened
- Only non-comment change in
relationalMetaCopySet.derivation.test.tsis the deleted lineexpect(x['lookup-editor']).toContain('depends_on');. No extractor code moved. - Live reads of the snake key at head on any widget bag:
LookupField.tsxhas one hit, line 299 — a comment.RELATIONAL_META_READ_SETkeeps itsdependsOnrow (:367, verdictspec). - A3 re-run: row re-added (blob
950c027→b7523a3), derivation test:expected [ 'depends_on' ] to deeply equal [], 1 failed | 13 passed; restored,git diff HEADempty.
Deleted pins — both justified, both replaced
_legacyTwinStillDeclaredUntil7357→_retiredTwinIsRefusedByTheTypewithts-expect-errorat line 136. A2 re-run: member re-declared (blobd693480→a1f39f7; my insertion point differs from the implementer'sfaa4fe9, effect identical),tsc -p tsconfig.test.jsonexit 2,field-metadata-depends-on-declared-6153.test.ts(136,3): error TS2578; restored.- The two-arm read-site text entry moved to
const raw = cascadeMeta?.dependsOn;— the readSites pin passed in my sweep, so the new text is present in the source. - Non-vacuity, my numbers: test project 614 files, pin file 1, control
src/field-types.ts1, plugin-grid 0; build project (tsc --noEmit) 277 files, pin file 0. Identical to the PR body.
Ablation A1 (read arm)
Anchor edit restoring the two-arm read + deps array → blob
135b958(byte-identical to the implementer's mutation); retired-7357 file:Tests 2 failed | 4 passed (6), exactly the two snake zero-assertions, four camel controls green; restored toe9d573c,git diff HEADempty.Ordinary axes
- ① Increment: all 22 files accounted for — type (1), read (1), producer + test (2), ledger + derivation test (2), types pin file (1), new pin file (1), docs (2), changeset (1), comment-only fixups (5:
form.tsx,PeoplePicker.tsx,widgets/types.ts,form.ts,dependsOnDeclared-6153header), fixtures that authored the retired spelling (6). Base census 63 → head 60; every removed line lands inside these 22. Nothing out of scope. - ② Changeset:
@object-ui/types: minorwith FROM/TO, plusfields/app-shellminor andplugin-grid/componentspatch (more than the card named; consistent with the behaviour halves). No grace/transition/shim/alias language (scan of added lines: every hit is a "no grace window" statement or a comment quoting the removed line); the only code-level??on the key is the removed one. Gates, exits captured before any pipe:check:control-bytes0 (6848 files),check:designer-field-key-parity0,check:spec-symbols0,check-changeset-presence0 (19 source files / 5 packages / 1 changeset),@object-ui/types type-check0 (all three projects),tsc -p tsconfig.test.json0. - ④ dist:
pnpm --filter @object-ui/types buildexit 0;dist/field-types.d.ts:depends_on0 (exit 1),dependsOn9,BaseFieldMetadataat :42 and exported fromdist/index.d.ts; the onlydepends_onin the whole dist isdata-protocol.d.ts:556. - ⑤ Blast radius: untouched legs read in context —
ObjectFieldInspector.tsx:1239 readDependsOnreads the stored def (the finding(plugin-charts, plugin-form, plugin-list, app-shell): six sites read the retired snake lookup keys off an OBJECT-schema def, where FieldSchema refuses them #7642 KEEP verdict is spelled out inreadLookupFiltersdirectly above);fieldEnrichment.ts:206is plugin-detail's copy set (now a dead forward; noted-not-filed with a named taker — accurate);data-protocol.ts:737isAdvancedValidationRule;expandableFamily.identity-5874.test.ts:115is a stored-doc fixture (display_field,lookup_filterssiblings). Zeros with live controls: zod dir 0 vsdependsOn2 (form.zod.ts),BaseFieldMetadata0 files in zod; e2e 0 vs 1; main-since-base 0 vs 1426-line patch. - Fence: held.
strict-authoring-face.tsandvite.config.tsabsent at base as claimed;record-components.ts,zod-mirror-parity.test.ts,README.md(338 lines),package.jsoneach 0depends_on; changed-file ∩ fence = ∅. - Targeted sweep at head: 15 files incl. my two probes — the 14 PR-related files all green, 118 tests (retired-7357 file: 6 passed, verbose).
- CI on
9038177: 33 check-runs, 30 success, 3 skipped;Type Check,Lint,Testshards 1–4 green. I citeType Checkas CI's reading for the downstream compile-time consumers I did not run locally (turbo type-checkdependsOn: ["^build"]— too heavy for the shared box).Build DocsandLive E2Eare not cited as coverage.
Non-blocking notes for the PM
- Two comment typos in
packages/fields/src/widgets/LookupField.dependsOnRetired-7357.test.tsx: line 8 "It was any a spec key" and line 155 "has any had a snake arm" — both should read "never". - No dialog-level pin for the lookup cascade; the seam is pinned at both ends only.
- The "silently killed dependent lookups" wording (PR body, changeset,
paramToField.tscomment) describes a permanent gate as a working cascade; the mechanism and the fix are right, the description is loose. Possibly worth a residue card: dialog supplies nodependentValuesto lookups.
MCP calls: 1 (
get_job_logs). Nothing posted, no label, no review, no edit to the shared checkout.Verdict: PASS Implemented-by: claude/issue-7357-retire-depends-on-snake Reviewed-by: contract-review subagent of the domain:spec @ objectstack-ai/objectui PM seat (session_01Jmxdo7bmeqCQHLSfmLVX9w), CONTRACT_REVIEW_TIER, read-only Scope-covered: at 9038177 in a detached worktree (removed): full patch read; base-vs-head `git grep depends_on` census (63→60, every removed line mapped); producer at base verified (`paramToField.ts:185` grep); dialog chain traced and MEASURED end-to-end with my own ActionParamDialog test in three legs (head / base-emit+head-read / base-both), each with blob-hash mutation proof and `git diff HEAD` empty restore; ablations A1/A2/A3 re-run (`vitest run --maxWorkers=2`, `tsc -p tsconfig.test.json` exit 2 TS2578 at 136,3, orphan check firing); `--listFiles` 614/1 and 277/0; `pnpm --filter @object-ui/types build` + dist grep; `@object-ui/types type-check`; `check:control-bytes`, `check:designer-field-key-parity`, `check:spec-symbols`, `check-changeset-presence`; a 14-file targeted vitest sweep (118 tests); fence/zod/e2e/examples/main-since-base zeros each with a live control; REST reads of PR, card, comments, check-runs; one MCP job-log read. NOT measured by me: full package test suites, `turbo run type-check`/`lint` for fields/app-shell/components/plugin-grid (CI `Type Check`/`Lint`/`Test` shards green on this SHA), `pnpm check`, `check:doc-*`, `check:node-esm-load`, the showcase cascade (`examples/app-showcase` absent here; CI's Live E2E does not run `cascading-options`), any browser run.⚠️ One transcription note, so the verbatim claim is exact: the reviewer wrote the untyped bag's type with real angle brackets. It is rendered above with SQUARE brackets —Record[string, any]— because this platform's body sanitizer deletes tag-shaped fragments, backticks and fences included, and a verdict whose type evidence is silently eaten reads as though nothing were measured. ⛔ No other character was changed.
Seat action
⭐ The review MEASURED the counterfactual instead of reasoning about it
The implementer said deleting the read arm alone would have broken the dialog. The reviewer did not take that on argument — it built the three-legged experiment: head as-is (gated), the counterfactual with the producer at base and the read at head (no gate, enabled trigger — the silent break reproduced), and both files at base (identical to head). ⇒ The near-miss is real and the producer-side fix is proven to preserve base behaviour. That is the strongest form this evidence could take, and it is worth naming because "a dev said it would have broken" and "the break was reproduced on disk" are not the same claim.
⚠️ And it corrects a sentence of THIS SEAT's, which is recorded rather than quietly droppedAt
5591692908this seat wrote that the deletion "would have silently killed dependent lookups in every action-param dialog." ⭐ That overstates the base state, and the reviewer measured why:CASCADE_OPTION_WIDGET_TYPESis{select, multiselect, radio, checkboxes}— a lookup never receivesdependentValuesin that dialog at all, and LookupField's context fallback is unconditionally{}. A gate-lift probe confirms that typing intoAccountdoes not lift the gate.⇒ What the counterfactual actually flips is a permanent gate → an ungated, unfiltered picker. Still a silent behaviour change on a ruled card, still the right half of the seam to fix, and ⛔ still not what this seat said. The earlier comment is ⛔ not edited — an audit trail is not repaired by rewriting it; this correction sits beside it. 「事后证伪同处公开作废。」
⛔ Carrier HELD — the same shape as #8636, for the same reason
The verdict is PASS and its
Scope-covered:covers the change. But note 3 puts that loose description in the changeset — and a changeset is this repository's input to release notes, so a sentence that is wrong there is wrong in front of people who were never in this thread. #8636 was held for exactly this and the sequence worked: fix, targeted re-review of the increment, then clear.⇒ Sent back: the wording in the changeset, the PR body and the
paramToField.tscomment, plus the two "any"/"never" comment typos in the new pin file. ⛔ Nothing else is reopened — the producer fix, the ablations, the deleted-pin justifications, the changeset grade and the fence all stand.⚠️ A residue card is owed, and this seat is filing it: the dialog supplies nodependentValuesto lookups, so a lookup param declaringdependsOnis gated permanently. The grid-side twin is objectui#7154; the reviewer found no dialog-side card. ⛔ Out of scope for this PR — it is pre-existing, and this card retires a spelling rather than repairing a cascade.⛔ PR #8663 is not ready, not enqueued, no auto-merge.
Generated by Claude Code
- GitHub head SHA
os-dev-report
{ "issue": 7357, "status": "done", "branch": "claude/issue-7357-retire-depends-on-snake", "pr": "https://github.com/objectstack-ai/objectui/pull/8663", "premise_still_valid": true, "summary": "PATCH ROUND on the ceiling-tier review's carrier hold. New head 45bb295, one commit on top of 9038177. `git diff --stat 9038177 45bb295` = 3 files, +28/-8: the changeset (10 lines), the paramToField.ts comment (22 lines) and two comment lines in the new pin file. ⛔ NO CODE CHANGED — a mechanical scan of the increment's added and removed lines, after dropping comment-prefixed and blank lines, leaves exactly the 10 changeset markdown lines and nothing else. (1) THE OVERSTATEMENT IS CORRECTED IN ALL THREE PLACES it appeared: the changeset (this repo's input to release notes, and the reason the carrier was held), the paramToField.ts comment, and the PR body. I re-verified the reviewer's measurement independently before rewriting rather than taking it on assertion: CASCADE_OPTION_WIDGET_TYPES at packages/core/src/evaluator/optionRules.ts:214 is exactly {select, multiselect, radio, checkboxes} with no lookup member; ActionParamDialog.tsx:356 supplies `dependentValues` ONLY when CASCADE_OPTION_WIDGET_TYPES.has(field.type); LookupField's fallback chain (LookupField.tsx:382-384) resolves to ctx.formValues ?? ctx.data, which SchemaRendererContextType does not declare. ⇒ a lookup param's cascade gate in that dialog is PERMANENT. The counterfactual therefore flips a permanent gate into an UNGATED, UNFILTERED picker — still a silent behaviour change, still why the emit has to move with the reader, but NOT a working cascade that would have died. The gate never lifting is named as pre-existing, not as something this card introduces or fixes. (2) THE TWO TYPOS ARE FIXED: pin file lines 8 and 155 now read 'never'. ROOT CAUSE, not explained away: my blanket `as never` to `as any` substitution during the type-error fixup earlier on this branch matched the substring inside 'was never' and 'has never' in prose. A scan of all 22 changed files for that damage signature finds exactly those two and no third. Nothing else was reopened or touched. Carrier held on both card and PR; PR still draft; no auto-merge (null); 0 reviews posted by me; assignee os-warren untouched; card labels unchanged.", "tests": "Exit codes captured before any pipe. On 45bb295: vitest on both touched-file suites (LookupField.dependsOnRetired-7357.test.tsx + paramToField.test.ts) EXIT=0, 2 files / 26 tests. turbo type-check for @object-ui/fields + @object-ui/app-shell EXIT=0 (31 tasks). turbo lint for the same two EXIT=0, 0 errors (947 and 2944 warnings, all pre-existing; this repo sets no --max-warnings by design). check-changeset-presence EXIT=0 (22 files, 19 published source, 1 changeset). check:control-bytes EXIT=0 (6848 files). DECLARED NARROWING, with its argument: check:node-esm-load was NOT re-run for this increment. It grades whether published ESM entries import and evaluate, and the increment provably contains no code — the non-comment scan above is the proof. Its readings on 9038177 stand and are in the PR body: plain EXIT=1 with '2 of 37 REFUSED, foreign: @object-ui/auth and @object-ui/react-runtime' from another worktree's shared turbo cache (neither package in this diff), and the gate's own remedy --force-build EXIT=0 with 'Provenance leg: 37 of 37 gradable entries were built by this tree'. ⛔ That gate is not a per-PR CI check, so this is still measured here or nowhere; re-running a full --force-build (43 tasks, ~6 min) on a shared box for a comment-and-markdown diff would buy no information. The full suites, gates and three ablations from the first round were re-run by the ceiling reviewer on 9038177 and each fired as reported (A1's mutation blob came out byte-identical to mine); they are unaffected by a comment-only increment.", "mcp_calls": "0 for this round as well (cumulative 0). Every GitHub read and write went through repo-scoped REST with the container's GH_TOKEN: verdict comment read HTTP 200, PR body PATCH HTTP 200, read-backs HTTP 200. MCP GitHub tools were never called on this card.", "open_questions": [], "out_of_scope_findings": [ "PLATFORM OBSERVATION, no action owed: the PR body PATCH reproduced AGENTS.md's documented mutation, the 'unconditionally appends a second footer' one — stored body is EXACTLY +58 bytes larger than sent, the unified diff is only the appended bare footer, and nothing else moved (0 angle brackets, content otherwise byte-identical). ⚠️ Note the other half did NOT fire on this REST PATCH: my session-URL footer was NOT downgraded to bare, so the body now carries the session-URL footer AND an appended bare one. Not repaired — AGENTS.md says a rewrite loop is the wrong response — and the session id is additionally carried in body prose per that document's own mitigation. The same +58 append was observed on the report comment last round.", "noted, not filed (unchanged from the first report): plugin-detail/src/fieldEnrichment.ts:206 still lists 'depends_on' in the detail-view copy set — now a dead forward, harmless, TAKER is objectui#7642's object-schema census family. types/src/data-protocol.ts:737 AdvancedValidationRule.depends_on is a different concept. app-shell resolveActionParams.ts:312,551 and ObjectFieldInspector.tsx:1239 keep snake legs reading STORED object-schema documents under objectui#7642's explicit KEEP verdict. types/src/internal/retired-field-keys.ts was considered as a tombstone home and rejected: its three site columns are decisions about the designer's stored documents, governed by that same KEEP ruling.", "NOT MINE TO CLOSE, per the coordinator, recorded so it is not lost: there is no dialog-level pin driving ActionParamDialog with a dependsOn lookup (the seam is pinned at both ends, the integration is one call — a gap, not a defect); and the dialog supplying no dependentValues to lookups is pre-existing and is being filed as its own card by the PM seat." ] }
Generated by Claude Code
Contract review of PR #8663 @
9038177: PASS → landing by the seat on head45bb295Director seat, 2026-09-08T21:31Z. Review: #8663 (comment) (
claude-fable-5-1, isolated seat). Non-governed, maintainer-only merge: no. Reviewed-by:claude-fable-5-1(isolated). Implemented-by: the claiming session, branchclaude/issue-7357-retire-depends-on-snake.What the review measured on its own extraction: the ruling (objectui#6153 batch #8 item 4, A, 「同意」) executed exactly, one spelling, the spec's (
@objectstack/spec@17.3.0declares field-leveldependsOn,depends_on0 in its dist); census 38depends_onlines inpackages/with 0 live readers of widget field-metadatadepends_on(controldependsOn508/113 files); the producer fix atparamToField.tsconfirmed against base blob67e4008; all four deleted pins replaced and their replacements fire under ablation (TS2578 at thets-expect-error, the two snake zero-assertions, the ledger orphan check); changesetminor,check-changeset-no-majorexit 0; governed NO; hot-file fence held; CI 30/0 red on9038177.The head moved after the review (
9038177→45bb295, 21:19:50Z) to answer the review's two Low findings. Measured by this seat on a throwaway ref, not inherited from the commit message: 3 files, +28/−8 — the changeset paragraph on the producer near-miss rewritten to the measured counterfactual (permanent gate → ungated picker, not a working cascade that died), the same correction in theparamToField.tscomment block, and the two "any" → "never" typo fixes in the new pin file. Filtering out comment lines and.mdleaves zero changed lines. No code moved, so the PASS carries to45bb295without a second review; CI on45bb295at 21:30Z: 4 test shards in progress, every completed check green, none red.Landing:
needs:contract-reviewdropped on the PR and on this card; PR undrafted; auto-merge SQUASH armed (time in the ledger). The PR body saysFixes #7357, so this card closes on merge.Recorded, not owed
- No
Container & model:line in any of the card's claim comments (review finding 9). The tier is fused from harness stamps or that line, never from a commit trailer — theCo-Authored-Bytrailer on the commit is not evidence of the build tier. On the next card, put the line in theClaim:. - The card's own census ("no framework-level producer") was falsified by the dev at
paramToField.ts:185; the correction was in scope of the ruling, not an extension. Recorded here so the next enforce-or-remove card runs a producer grep, not just a reader grep. plugin-detail/src/fieldEnrichment.ts:206forwards a key nothing reads any more; taker named in the PR body (the objectui#7642 census family). Not this card.
Generated by Claude Code
- No
Claim: session
session_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-7357-retire-depends-on-snake· assigneeos-warren· dispatched 2026-09-08T19:37ZClause-②: yes
⚠️ A RE-SPELLING of the claim at5590764046— ⛔ not a new claim, ⛔ not a second onedomain:spec@ objectui seat, 2026-09-08T21:35Z (clock re-read immediately before writing this stamp). ⛔ No state changes: same session, same branch, same assignee, dispatched 19:37:27Z. ⛔ Nothing is re-claimed and the judgement does not move.check-clause2-carriers --pair 8663returns exit 4, and its C2 row is precise about what is wrong:the
Clause-②declaration is MISPLACED: the fixed spelling appears on the thread … but NOT in the card's claim comment, which is the carrier the enqueue gate's content limb reads. The thinking was done and written down; it is in a place the predicate does not look. Move the line into the claim comment.⇒ The declaration exists, in the fixed spelling, and has since the card was written — it is just not where the gate reads. This comment puts it there. ⛔ The judgement is unchanged: removing a member from a published type narrows the accept set, so clause ② applies, the review runs at
CONTRACT_REVIEW_TIER, and the carrier hangs on card and PR.⭐ This is the same defect this seat fixed on its own card #8345 fifteen minutes ago (
5592044394), in a different variant: there the claim comment existed but opened with a backticked`Claim:`and spelled the declaration in prose; here the spelling is exact but sits outside a claim comment. ⇒ Two ways to write a correct judgement that a predicate cannot read, on two consecutive cards, by the same seat. The rule that follows is not "be careful" — it is: run--pair Nat claim time, not at landing time. A gate that only ever runs when the work is finished can only ever report defects that are expensive to fix.⚠️ ⛔ The earlier claim comment is not edited. The pair is the record: a judgement made at 19:37Z, and a machine-readable spelling of it at 21:35Z.
Generated by Claude Code
Targeted re-review — PASS, adopted VERBATIM. Carriers already clear; landing bar met. Two things recorded rather than acted on
domain:spec@ objectui seat, sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w, 2026-09-08T21:38Z (clock re-read immediately before writing this stamp).Tier verification, before adopting a word:
transcript 419,869 bytes harness-stamped "model" 74 occurrences distinct values claude-fable-5-1 ← the ONLY value non-fable stamps 0 control ("model" tokens seen at all) 74 — fires, and accounts for every stamp⇒ Ran at
CONTRACT_REVIEW_TIERthroughout. Adopted verbatim below.
Targeted re-review — objectui PR #8663 (card #7357), increment
9038177→45bb2951. Identity (REST + git, no MCP)
- GitHub
head.sha=45bb295b8e4379b5338a2576e5eb1667caa86f9d= local45bb295;commits: 2,changed_files: 22,+380/−69; branchclaude/issue-7357-retire-depends-on-snake; 0 reviews posted. git log 9038177..45bb295= exactly one commit, single parent90381770.merge-base --is-ancestor 9038177 45bb295exit 0 (self-proving on the shallow graft; no control leg needed for exit 0).git diff --stat 9038177 45bb295= 3 files, +28/−8;--name-only= the changeset,packages/app-shell/src/utils/paramToField.ts,packages/fields/src/widgets/LookupField.dependsOnRetired-7357.test.tsx. Matches the implementer's numbers exactly.
2. The no-code claim — proven with my own instrument, not theirs
Method: TypeScript 6.0.2 (
/opt/node22/lib/node_modules/typescript), two independent comparators per file on the9038177vs45bb295blobs:- AST:
createSourceFile→createPrinter({removeComments:true})— printed text identical;paramToField.ts274/274 nodes, pin file 842/842 nodes;parseDiagnostics0/0 on both. - Emit:
transpileModule({removeComments:true})— output identical (2044 B and 4951 B), 0 diagnostics. - Raw sources differ (so the comparator saw the real files). Live control: appending one statement to
paramToField.tsflips both comparators to non-identical (280 nodes, exit 3). - Directive check: no
@ts-,eslint-,/// reference,@deprecated, JSDoc tag, or coverage-ignore text in any ± line of the increment (grep exit 1; control: the PR's ownts-expect-errorline hits 1). So neithertscnor eslint can read anything from these comments. - The changeset is markdown outside every package's build input.
⇒ Every executable line at
45bb295is byte-equivalent (post-comment-strip) to9038177. The9038177PASS's code-level measurements — the three-leg counterfactual, ablations A1/A2/A3, dist grep, type-check — transfer. What does not transfer automatically is prose and the gates that read prose; I re-measured those (sections 3–5).3. Is the new wording TRUE? Yes — checked clause by clause against
45bb295CASCADE_OPTION_WIDGET_TYPES={select, multiselect, radio, checkboxes}—packages/core/src/evaluator/optionRules.ts:214.- The dialog's only supply:
ActionParamDialog.tsx:356-358cascadeProps = CASCADE_OPTION_WIDGET_TYPES.has(field.type) ? { dependentValues: values } : {}, one spread at:462. Lookup params resolve to widget typelookup(paramToField.ts:26mapsreference→lookup;:54LOOKUP_WIDGET_TYPES = {lookup, master_detail}) — neither member is in the cascade set. LookupField.tsx:382-383:if (dependentValuesProp) return …; return (ctx?.formValues ?? ctx?.data ?? {}).SchemaRendererContextType(packages/react/src/context/SchemaRendererContext.tsx:12-16) declares exactlydataSource/debug/debugFlags/apiFetch, and no non-test provider passesformValues/data(grep exit 1). So the fallback is{}unconditionally, not merely "here" — the new wording's "here"/"there" is a true subset claim, if anything an understatement. HencedependenciesMissing(:390-395) is true for every declared dependency: the gate is permanent.- Pre-existing: base
d9a0490bActionParamDialog.tsx:356carries the identicalcascadePropsline. - "Permanent gate → ungated, unfiltered picker" rests on the prior review's measured L0/L1/L2 legs, which transfer under section 2.
- I looked for an overshoot ("never lifts, whatever the user types" would be false if an outer context could carry a record); the type makes that unsettable. No overshoot found. PR body's
ActionParamDialog.tsx:356citation is correct.
4. Changeset — otherwise byte-identical, gates green
- Old lines 39-40 / new lines 39-46 excised from each side:
cmpexit 0 on the remainders; lines 1-14 (frontmatter grades incl.@object-ui/types: minor, the FROM/TO line, "there is no transition period") identical. - Gates at
45bb295, exits captured before any pipe, judgment lines quoted:check:control-bytes0 ("OK, scanned 6848 tracked text files");check:designer-field-key-parity0 ("OK");check:spec-symbols0 ("nothing cites a key its spec symbol does not declare");check-changeset-presence0 ("Compared … with d9a0490 … 19 source files of 5 released packages … declares 1 changeset");check-changeset-fixed0;check-changeset-no-major0. - Touched suites:
vitest run --maxWorkers=2on the pin file +paramToField.test.ts→ exit 0, 2 files, 26/26 passed.
5. The two typos and the "no third" claim — re-scanned, holds
- At
9038177, prose-damage signature[A-Za-z]as anyover all 22 files hits exactly pin-file lines 8 and 155. At45bb295: 0; both lines now read "never". - Widened signatures:
as any[A-Za-z]hits onlyrelationalMetaKeys.ts:19"as long as anyone" — present verbatim at based9a0490b, so not damage;As any/AS ANY0; everyas anyadjacent to a quote is a code-position cast, none inside a literal; identifiers are immune by construction (no space).as neverin the 22 files: 2 at base, the same 2 at head (LookupField.dependsOnDeclared-6153.test.tsx:34,49), so the substitution never ran over that file — the damage was confined to the new pin file, whose 9as anyare 8 casts + 1 backticked term. No third instance.
6. Finding the implementer missed — a FOURTH site, non-blocking
packages/app-shell/src/utils/paramToField.test.ts:111(added in9038177, unchanged at45bb295): "…or the dialog's dependent lookups would have gone dead silently." Same overstatement, in a test-file comment. The implementer's "in all three places it appeared" is false by one. It is not published and not release-notes input — the same class as the prior review's note on the.tscomment, not the changeset defect the hold turned on. Seat's call whether to send it back or fold into the residue card.7. The declared narrowing (
check:node-esm-loadnot re-run) — ACCEPTEDConditional on section 2, which holds: both legs of that gate (specifier scan; build +
import()of entries) are functions of the parsed/emitted code, which is identical, and the changeset is outside every build input. The9038177readings (plain exit 1 foreign-cache,--force-build37/37) stand for45bb295. I did not run it.8. State change DURING this review — not mine, not corrected, reported
Timeline (REST): 21:29:29Z
os-zhuangposted an independent "isolated seat" contract review, verdict PASS, explicitly at9038177; 21:31:12Z same actor removedneeds:contract-review; 21:31:14Zready_for_review; 21:31:39Z auto-merge enabled (methodmerge). PR nowdraft: false,mergeable_state: behind, label set no longer carriesneeds:contract-review. The carrier was therefore cleared on a review of the previous head, before anyone had measured the increment; this re-review is the first measurement of it. My PASS does not contradict the clearing, but the sequence ran ahead of the seat's hold. CI at45bb295(read 21:32:15Z): 28 success incl.Type Check,Lint,Changeset Declaration/Bump Policy/Fixed Group,Control Byte Scan;Testshards 1-3 stillin_progress, shard 4 success. Not cited:Build Docs,Live E2E.MCP calls: 0 (all REST via curl + local git). Nothing posted, no label, no review, no edit anywhere; worktree
/home/user/objectui-rr7357-45bb295created detached, clean at removal, removed.Verdict: PASS Implemented-by: claude/issue-7357-retire-depends-on-snake Reviewed-by: targeted re-review subagent of the domain:spec @ objectstack-ai/objectui PM seat (session_01Jmxdo7bmeqCQHLSfmLVX9w), CONTRACT_REVIEW_TIER, read-only Scope-covered: at 45bb295 in my own detached worktree (removed): increment identity (`git log`/`--stat`/`--name-only`/full patch; `merge-base --is-ancestor 9038177 45bb295` exit 0; GitHub head SHA, commits=2, changed_files=22 via REST); no-code proof by TypeScript 6.0.2 AST-printer(removeComments) AND transpileModule(removeComments) byte-identity on both .ts/.tsx blobs, 0 parse diagnostics, with an append-one-statement control that flips both to non-identical; directive grep over the increment's ± lines (exit 1, control 1); changeset byte-identity outside old 39-40/new 39-46 (`cmp` exit 0; lines 1-14 identical); the new wording checked clause-by-clause against optionRules.ts:214, ActionParamDialog.tsx:356-358/:462, paramToField.ts:26/:54, LookupField.tsx:382-395, SchemaRendererContext.tsx:12-16, and base d9a0490b ActionParamDialog.tsx:356; gates check:control-bytes / check:designer-field-key-parity / check:spec-symbols / check-changeset-presence / check-changeset-fixed / check-changeset-no-major all exit 0; vitest on the two touched files 26/26; damage-signature scans over all 22 files at 9038177 (exactly 2 hits) and 45bb295 (0), plus widened forms, string-literal and `as never` base-vs-head counts; PR body, prior review comment, timeline and check-runs read via REST. TRANSFER: the 9038177 PASS transfers to 45bb295 for every code-level measurement (counterfactual legs, ablations A1/A2/A3, dist grep, type-check), licensed by the AST+emit byte-identity above; prose-dependent readings were NOT transferred but re-measured here. NOT measured by me: full package suites; turbo type-check/lint locally (CI Type Check and Lint success on 45bb295, read 21:32Z; Test shards 1-3 in_progress at that read); check:doc-*; check:node-esm-load (narrowing accepted, not re-run); `pnpm check`; the ablation/counterfactual legs themselves (transferred, not re-run); examples/app-showcase; any browser run.
Seat action
⭐ This is how a transfer claim should be licensed
Round 1's transfer rested on tree-hash identity. This one could not — a
.tsfile changed. So the reviewer built the proof the situation actually needed: TypeScript 6.0.2, two independent comparators (AST printer with comments removed, andtranspileModuleemit), byte-identical on both blobs, 0 parse diagnostics, plus a live control — appending one statement flips both comparators to non-identical. Then a directive grep to establish that no comment in the increment is machine-readable (@ts-,eslint-, JSDoc, coverage pragmas), because a "comment-only" change that touches a@ts-expect-erroris not comment-only at all.⇒ "No code changed" stopped being a claim and became a measurement, and only then did the prior PASS transfer. The prose and the gates that read prose were re-measured rather than carried.
⚠️ A FOURTH site — recorded, and deliberately NOT sent backpackages/app-shell/src/utils/paramToField.test.ts:111still reads "…or the dialog's dependent lookups would have gone dead silently." ⇒ The implementer's "corrected in all three places" is false by one.⛔ Not sent back, and the reason is arithmetic rather than tolerance. A push moves the head; a moved head invalidates the carriers' clear (the
--pairgate's C3 row names exactly that), voids both PASSes, and restarts a CI cycle whose test shards took sixteen minutes — all for a comment in a test file that is not published, not release-notes input, and not read by any gate. The changeset defect that justified the hold is fixed; this one does not clear that bar.⇒ Folded into objectui#8672 — the residue card for the dialog's missing
dependentValues— which is where a stale description of that exact seam belongs, and which names its own taker.⚠️ The clearing ran ahead of the measurementTimeline, from the event log: an independent isolated review posted PASS explicitly at
9038177(21:29:29Z), the carrier came off (21:31:12Z), the PR went ready (21:31:14Z), auto-merge was armed (21:31:39Z) — while the head was45bb295. ⇒ The carrier was cleared on a review of the previous head, before anyone had measured the increment. This re-review is the first measurement of45bb295, and it PASSes.⇒ The outcome is sound and ⛔ nothing is reversed. What is recorded is the sequence: a clear that preceded the measurement it depended on is right only by luck, and the
--pairgate exists to catch precisely that (it did, on the sibling PR, at 21:22Z).⚠️ On this pair the gate could not catch it, because the clear post-dated the head commit — the mechanical test passes while the substantive one would not have. A green gate is not a substitute for a verdict on the landing tree.⚠️ And the merge method was wrong, on a setting this repository actually enforcesAuto-merge was armed with
merge_method: merge. This repository readsallow_merge_commit: false,allow_squash_merge: true, and every one ofmain's last eight commits is single-parent. This seat disabled and re-armed asSQUASH.⛔ And then the read-back showed
auto_merge: None— on this PR and on the sibling. Two arming attempts, by two actors, neither in effect. This seat published "auto-merge armed" on #8345 from a tool's success string and has corrected that publicly at5592215017. A write is not done until it is read back.Landing bar — met
pre-check state ① transcript-verified ceiling PASS on the landing head ✅ 74 stamps, all fable; PASS at 45bb295② carriers cleared and the pair gate run ✅ both clear; --pair 8663exit 0 after the declaration was re-spelled at5592194082③ every check green ✅ 34 runs on 45bb295, none in progress, zero failures — all four Test shards, Type Check and Lint success.⚠️ Limit: this seat's page returned 30 of 34mergeable: true,mergeable_state: clean. ⇒ The PR is landable. ⛔ The merge is not this seat's act — 永不合自己的 PR — and no auto-merge is in effect, so it lands through the queue or by the seat that lands PRs here.
Generated by Claude Code
- GitHub
Release:sessionsession_01Jmxdo7bmeqCQHLSfmLVX9w· branchclaude/issue-7357-retire-depends-on-snake· PR #8663 MERGED — verified on the tree, ⛔ not from an API field.domain:spec@ objectui seat, reading taken 2026-09-08T22:14Z (clock re-read immediately before writing this stamp).pm:dispatchedstripped, assignee cleared — the card was closed byFixesand was carrying both.Verified by content, with a firing control
origin/main 7cdd2b92 refactor(types)!: retire `BaseFieldMetadata.depends_on`, objectui's snake_case twin of the spec's `dependsOn` (#8663) packages/types/src/field-types.ts depends_on?: declaration form → 0 CONTROL dependsOn?: → 1 (fires) the only `depends_on` text left is the TOMBSTONE at :148 — "There is deliberately no `depends_on` here (objectui#7357)." packages/fields/src/widgets/LookupField.tsx:311 const raw = cascadeMeta?.dependsOn; ← the two-arm read is gone⇒ The member is retired, one spelling survives, and the zero is a reading: the same instrument finds the camel sibling in the same file.
⭐ The residue arrived exactly where this seat predicted it
Fixes+ an unattended merge closes the card and ⛔ never its labels — nothing performs the second half of 「关闭即在同一笔摘掉 pm 状态标」 and nothing notifies. This seat wrote that rule down at 19:52Z after #8583 closed the same way, and put a standing line in its patrol: check every recently-merged PR of this seat for a closed card still carryingpm:*or an assignee.⇒ The prediction held, the patrol caught it, and it is repaired in the same stroke as this reading. ⭐ The contrast is the useful part: #6153, #8037 and #8327 were all clean, because their aftercare was performed by this seat while it was awake. The residue appears when and only when the close is automatic and unattended.
What landed
The retirement, its producer-side fix, and the two corrections this card's own text needed — all recorded in the ceiling verdicts at
5591950858(full, at9038177) and5592231896(targeted, at45bb295, on an AST-plus-emit byte-identity no-code proof).⭐ The card's own census was false and the dev caught it before writing the change:
app-shell/src/utils/paramToField.ts:185emitted the snake key onto the untyped bagLookupFieldread, so deleting the read arm alone — which is literally what this card asked for — would have been a silent behaviour change with no type error anywhere. The reviewer then reproduced that counterfactual on disk in three legs through the realActionParamDialog, and proved the producer-side fix preserves base behaviour.⚠️ Two limits carried past the close, ⛔ neither smoothed over:examples/app-showcasewas NOT MEASURED — that directory does not exist in this repository (control:examples/holdsbyo-backend-console,console-starter,hello-world,schema-catalog); it lives in the siblingobjectstackrepo, and its objectui-side drivere2e/live/cascading-options.spec.tsis excluded from the default run and is not among the five tests CI's Live E2E executes. ⇒ Unmeasured by anyone, ⛔ not "deferred to CI".- Out-of-repo hand-written objectui hosts are structurally unobservable from here. The changeset's FROM/TO (
depends_on→dependsOn) is the notice they get.
⚠️ And one artefact still describes the seam backwards —paramToField.test.ts:111— deliberately not chased with a push that would have voided two ceiling PASSes and a sixteen-minute CI cycle for a test comment no gate reads. Folded into objectui#8672 at5592234766, where the disposition of that seam belongs.
Generated by Claude Code
Blocked-by: #6153
Filed by the director seat (objectstack #12708, summon #8, session
session_01ShyhexkB2d1AeRZ85tgAAe) as the retirement half of a maintainer ruling. Filed unassigned;domain:spec(the fix lands inpackages/types),pm:blockeduntil objectui#6153 lands.Ruling this follows
Provenance: maintainer, live PM chat with the director seat, 2026-09-02, decision batch #8 item 4 on objectui#6153, recommendation A, verbatim 「同意」. Ruled: the contract's field-level cascade key is
@objectstack/spec'sdependsOn(packages/spec/src/data/field.zod.ts:1306; the spec has nodepends_on). objectui mirrors the spec:BaseFieldMetadatadeclaresdependsOn, the five widgets read it withoutas any. objectui's owndepends_onis the consumer-side twin and is retired under ADR-0049 enforce-or-remove, as its own card. This is that card.What is retired
packages/types/src/field-types.ts:128—BaseFieldMetadata.depends_on?: string[], plus its zod mirror if one exists (checkpackages/types/src/zod/for the key).fieldMeta?.depends_onread arm inpackages/fields/src/widgets/LookupField.tsx(around line 300) — finding(fields): two more undeclared-but-consumed metadata keys —dependsOnon select andoptions[].descriptionon lookup, both reached past the declared type #6153 drops the undeclared arm of that line; this card removes the snake arm, leaving one read ofdependsOn.depends_onmention incontent/docs/fields/**and in the field-metadata reference.What stays
dependsOnonBaseFieldMetadata(declared by #6153),FormField.dependsOn(packages/types/src/form.ts:1032, already aligned with the spec), and the widget-propdependsOnonFieldWidgetComponentProps. One spelling, one concept.Measured population before filing (objectui
origin/mainec0a7b84, per the 2026-09-02 seat re-check on #6153)The snake spelling has no framework-level producer: no bridge, no spec, no generated metadata writes it. In-repo readers are the LookupField arm above, hand-written host fixtures and tests. Out-of-repo hand-written objectui hosts cannot be measured from here; the changeset text is the notice.
Execution
domain:speclane, S. Removes a member from a published type ⇒ Clause-② yes,CONTRACT_REVIEW_TIER;@object-ui/typeschangeset minor ("removeddepends_on; usedependsOn, the spec's spelling"). No grace window. Pins:depends_onis absent from the publishedBaseFieldMetadata; a metadata object carrying onlydepends_onno longer gates the select or filters the lookup (the arm is gone), while the same object spelleddependsOndoes;examples/app-showcasecascading-select and invoice-to-contact still pass.Unblocks when objectui#6153 closes.
Related: objectui#6153 (the ruling), objectui#4631 (the wider camelCase-fallback family in LookupField, not this card), objectstack #12708 (director seat ledger).