Repository navigation
[finding] objectName on an object-embedded action is inert AND unvalidated — the third arm PR for #7397 deliberately left open #7456
Description
Activity
Findings triage: held (
finding+domain:specboth stand — routing verified correct).Premise — filed today against
origin/main@d13ce33with a measured probe table (embeddedobjectName: 'probe_missing'ACCEPTED vs registered REJECTED); current main is8a9c079and #7397's PR (the first-two-arms mirror) has not merged, so the measured split is unchanged in kind. Not re-probed this round — the card is hours old and its filing seat measured it directly.Why held rather than promoted or escalated — same disposition logic as #7390: the residue is decision-shaped. Mirroring the registered
objectNamecheck onto the embedded position is an acceptance-surface change (stacks that validate today would be rejected), and the card's filing seat explicitly declined to take that as a side effect; but the key is also inert at that position (mergeActionsIntoObjectsreads onlyconfig.actions— never the reverse), so the alternative is retiring the key there, also an acceptance-surface change. Both directions aredomain:speccalls with zero user impact today — nothing consumes the key, nobody hits it. Escalating now would put a no-urgency contract fork in an already-loaded decision box.Promotion/escalation triggers recorded: ① #7397's PR merges (the natural moment — the third arm's fix would land in the same walk, and the claiming spec seat can carry the question to the maintainer with all three arms in view); ② any consumer of
obj.actions[].objectNameappears; ③ maintainer appetite for the embedded/registered parity question generally.Dedup — #7397 (arms 1–2, in flight) is the parent; this is its deliberately-severed third arm, cross-linked by both bodies. No other card owns the key.
本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Findings triage: HOLD.
Re-verified @
origin/main333769d: the registered-walk arm is live atpackages/spec/src/stack.zod.ts:1150(Action '…' references object '…' which is not defined in objects.) and no embedded-position mirror exists — the split verdict the card measured stands. Still inert at the embedded position (mergeActionsIntoObjectsreads only the top-level list). Held because the exit is the A-vs-B contract ruling the card lays out (existence check vs consistency check — different acceptance surfaces), which is adomain:specseat escalation when taken up, not a mechanical mirror.本评论来自分诊座位 Routine(#5474 试点),不构成认领。
Generated by Claude Code
Maintainer ruling — 2026-08-11. From the four-lens decision review (platform long-term coherence / measured business pull / AI-agent error-resistance / startup scope discipline); the maintainer accepted the recommendation set in full.
Ruling: deliberate defer — same disposition as #7219, into the spec/contract cadence. The A-vs-B evidence on this card is complete; rule it there where the neighboring contract decisions give it context.
findingstays.Recorded by the triage seat Routine (#5474 pilot) on the maintainer's direct instruction — ruling record, not a claim.
Generated by Claude Code
Findings triage round, 2026-08-11 (PM session, maintainer-directed: 「跑一轮集中定级」).
Graded: promote. The registered-action walk already refuses a dangling
objectName; the embedded arm accepts the identical defect silently. This inherits the ruling family #7397 implemented for the flow/modal arms (silent drop on one sibling branch joins the existing refusal set — the standing 元判据), so it does not need its own decision-inbox slot: mirror the third check onto embedded actions. S.finding→pm:queue(domain:spec).
Generated by Claude Code
Claim: PM loop round 1 (spec seat, takeover shift)
Session:session_01ECjShwqVRAhLy15mgatGjV
Branch:claude/issue-7456-embedded-objectname-retire
Worktree: cloud session (own clone) —objectstack-issue-7456
Domain:domain:spec
File surface:packages/spec/src/stack.zod.ts(embedded action walk region) +packages/spec/src/data/object.zod.ts(embeddedactions:array / ActionSchema usage region ONLY — disjoint from #7758'suserActionsregion) + regenerated authorable-surface/docs baselines. Stop on breach; explain in the report.
Container & model: M (position-scoped key retirement with contract judgment),mode:cloud,model: claude-opus-5
Serial constraints cleared: #7758 (in-flight, same fileobject.zod.ts,userActionsregion — regions disjoint; this card's PR lands at the os-regen relay tail and merges main after #7758 is in). #7428 (queued, same ActionSchema region) is serialized BEHIND this card per seat post #6017's hot-file queue. No other in-flight spec claim touchesstack.zod.ts.Direction: third-tier premised ruling, recorded in the dispatch prompt — Option C (retire
objectNameat the embedded position) conditional on measured premises; premise failure is a fork report, not a silent fallback to A or B.
Generated by Claude Code
Correction to the claim above — direction is Option A, not C. Re-reading the full thread after posting the claim (the race-check re-read doing its job on the PM, not just on racers): this card already carries a recorded seat disposition from the 2026-08-11T14:32Z findings-triage round (maintainer-directed), quoted verbatim:
mirror the third check onto embedded actions. S.
— which applies the standing 元判据 (a silently-dropped declaration joins the sibling branch's existing refusal set), and the maintainer's 02:57Z four-lens record framed the residual question as A-vs-B with 「deliberate defer … rule it there where the neighboring contract decisions give it context」. The grading round did rule it: A. My claim's C was a re-litigation of a decided question — withdrawn.
Dispatch therefore executes A (existence check, verbatim mirror of the registered walk's
objectName→ declared-object check, third arm beside #7397's flow/modal mirrors). Consequences for the claim block: size S (mechanical mirror, gate-determined),mode:subagent,model: sonnet. File surface unchanged. The B/C residue (a valid-but-contradictoryobjectNameunder a different owner is still blessed by A; retirement remains arguable under startup-focus) stays recorded on this card — A does not foreclose either, and neither ships without its own ruling.
Generated by Claude Code
{ "issue": 7456, "status": "done", "branch": "claude/issue-7456-embedded-objectname-retire", "pr": "https://github.com/objectstack-ai/objectstack/pull/7895", "premise_still_valid": true, "summary": "Implemented Option A (verbatim existence-check mirror) per the maintainer-confirmed 2026-08-11/12 ruling. `validateCrossReferences` in packages/spec/src/stack.zod.ts now checks `objectName` on config.objects[].actions[] the same way it already checks it on config.actions[] -- a dangling embedded objectName is refused with the same message wording, subject adjusted to the owning object (`Action '...' on object '...' references object '...' which is not defined in objects.`). objectName still gives no new runtime meaning at the embedded position (mergeActionsIntoObjects still reads only config.actions); this only makes a dangling value refused at authoring time. Options B (consistency check) and C (retirement) remain open per the ruling and are not exercised.", "tests": "Extended packages/spec/src/stack-inline-action-crossref.test.ts (the #7397 probe-table file, per the card's own testing convention) with 5 new cases in a new 'objectName -> object (#7456)' describe block. `pnpm --filter @objectstack/spec exec vitest run src/stack-inline-action-crossref.test.ts` -- 47/47 pass. `pnpm --filter @objectstack/spec test` -- 379 files / 9988 tests, all pass. `pnpm --filter @objectstack/spec typecheck` -- pass. Reverse verification (predicted red, confirmed): reverted stack.zod.ts alone, reran the file -- exactly the 2 new objectName assertions failed for the right reason (refusals() returned [] instead of the expected 1-element array); restored via `git apply` from a saved patch (no git stash), all 47 pass again. Named local gates all green: check:adr-anchors, check:changeset-gate-self-tests, check:doc-formula-expressions (had to build @objectstack/lint's deps first -- stale dist/ trap), check:docs-audit-scope, check:driver-conformance (40/40 cells), check:i18n (had to build @objectstack/cli first per the gate's documented prerequisite), check:merge-driver, check:release-body, check:spec-parsed-alias, check:stack-collection-maps, check:generated (13/13 artifacts up to date, no regen needed), check:authorable-surface (GREEN; baseRev lag vs upstream anchor is the documented informational-only state), check:nul-bytes (7215 files, clean). Full lint.yml farm not run locally per the local-verification-scope contract -- CI's job.", "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
ACCEPT — PR #7895 (draft), reviewed against GitHub per step 7.
- Exactly the ruled Option A: the embedded walk in
validateCrossReferencesgains the third arm (objectName→ declared object), wording identical to the registered rule fromreferencesonward with the subject adjusted — the same convention as [observation]config.objects[].actions[](object-embedded actions) may also bypass defineStack's cross-reference walk — unverified, same mechanism as #6889's row E #7397's flow/modal arms. The replaced code comment now records the ruling and what stays open (B/C), in the walk itself. - Tests extend the [observation]
config.objects[].actions[](object-embedded actions) may also bypass defineStack's cross-reference walk — unverified, same mechanism as #6889's row E #7397 probe-table file per its own convention: full-message pins (not baretoThrow), the k/l split closure, and an explicit "different declared object still accepted" case pinning that A is existence-only — B stays open and cannot be silently upgraded later without going red here first. - Reverse verification predicted red and hit it on exactly the 2 new load-bearing assertions, for the right reason. Corpus census: zero embedded-position
objectNamein shipped metadata, so the narrowing breaks nothing that builds today. Minor changeset present; no generated baselines touched; no unrelated files. - Note for the ledger: the fix landed entirely in
stack.zod.ts—object.zod.tsuntouched — so this PR needs no os-regen relay slot and lands independently once its gate jobs conclude green. The spec: guard theconfirmText+paramsPAIR at authoring time, once the 20 shipped sites have migrated toaction.description#7428 serialization note is loosened accordingly: its remaining same-file constraint is against feat(spec): userActions.create/import accept the edit/delete CEL predicate union #7758 only.
Generated by Claude Code
- Exactly the ruled Option A: the embedded walk in
- added a commit that references this issue
on Aug 17, 2026 - added a commit that references this issue
on Oct 9, 2026
Observation-class finding, surfaced and measured while implementing #7397 (the object-embedded modal/flow cross-reference arm). Filed unassigned per #4949 discipline — deliberately NOT fixed in that PR, because the fix requires a contract decision this seat should not take as a side effect.
What was measured
The registered action walk in
packages/spec/src/stack.zod.tsapplies three checks, not two: flow target, modal target, andobjectName→ declared object. #7397's PR mirrors the first two ontoconfig.objects[].actions[]. The third is left as-is, and it splits the same way the target arms did:Same action object, two authoring positions, opposite verdicts — the b/f, c/g, d/i pattern from #7397's probe table, one key over. Measured on
origin/main@d13ce33with both stacks built from the same helper and the same arguments.Why it is observation-class rather than a live defect
Nothing consumes the key at that position today, so no user hits it at runtime:
mergeActionsIntoObjects(stack.zod.ts) builds itsobjectName -> actions[]map only fromconfig.actions— the top-level list. It never readsobj.actions[].objectName. The merge direction is top-level to object, never the reverse.ActionSchema(data/object.zod.ts,actions: z.array(ActionSchema)), so the key is declared there purely because the shape is shared with the registered collection — not because anything reads it.So it is a declared-but-unconsumed key at this position (Prime Directive #12, "declared = enforced") plus an unvalidated dangling reference — inert drift, not a live break. That is why it carries the
findinglabel and nopm:queue.The contract question that blocks a mechanical fix
Mirroring the registered check verbatim is not obviously right, which is exactly why #7397's PR did not do it. Two readings, different acceptance surfaces:
objectNameon an embedded action must name a declared object, same as registered. Cheapest, consistent with the other two arms. But it acceptsobjectName: 'other_object'on an action embedded undertask— a contradiction the schema would then bless.objectNamemust equal the owning object's name (or be absent), because the action's owner is already unambiguous from its position. Stricter, and the only reading under which the key carries meaning at this position at all.A/B/C differ in what they refuse, so this is an acceptance-surface decision (
domain:specby the standing red line), and under the startup-focus principle option C deserves real weight: there is no measured business pull for authoringobjectNameat a position that already names the object.Anchors
packages/spec/src/stack.zod.ts— registeredobjectNamecheck (Validate action -> object references); the [observation]config.objects[].actions[](object-embedded actions) may also bypass defineStack's cross-reference walk — unverified, same mechanism as #6889's row E #7397 embedded walk immediately above the inline one, whose comment records this exclusion in code.packages/spec/src/data/object.zod.ts—actions: z.array(ActionSchema), the shared shape.config.objects[].actions[](object-embedded actions) may also bypass defineStack's cross-reference walk — unverified, same mechanism as #6889's row E #7397 / its PR — the modal/flow arms; Inline (page-element) actions bypassdefineStack's action cross-reference validation entirely — a danglingtype: 'modal'target builds clean #6889 / PR fix(spec): defineStack cross-reference validation reaches inline page-element actions (#6889) #7392 — the inline half. Neither covers this key:InlineActionSchemadoes not pickobjectNameat all, so the inline arm had nothing to decide.Dedup
Open-issue search for
objectName action cross-referenceandembedded action objectName defineStackreturns only #7397 itself. No open card covers this key.