Repository navigation
17.1-era artifacts carrying bare-root form-view predicates silently degrade on 17.2: unbound root faults open, conditionally-hidden fields render and their required: true dead-ends console record creation — no ADR-0087 conversion covers the predicate-root move #12915
Description
Activity
Maintainer ruling recorded (2026-08-28, live PM chat) — scope C approved, A deferred with an explicit start line.
Verbatim, untranslated: 「同意C」 — in response to the recommendation "C 现在做,A 立卡定死启动线" (C = loud versioned detection at the artifact door; A = the full ADR-0087 predicate-root conversion). Follow-up clarification asked and answered in the same chat: the loud warning lands at service startup, server-side, operator-facing (the same channel as the #12843 conversion summaries) — NOT in the frontend. Both SaaS shapes are covered by the same funnel: single-DB multi-org boots the artifact once (one notice, read by the operator who alone can rebuild); per-tenant-DB kernels each pass the same door at their own boot. A console/end-user surface is explicitly out of scope — the person at the form cannot act on "your artifact is stale".
Scope C (this card, dispatching now): at the framework artifact door, inside the same versioned window #12843 built (declared floor below the running spec), detect form-view predicates whose root identifier is not in the bound vocabulary (
record/previous/parent/data), and surface ONE deduped operator warning per artifact naming the affected views, the fault-open consequence, and the rebuild prescription. No behaviour change: no refusal, no rewrite, no spec/contract change. Landing: policy helper besideapplyArtifactForwardConversionsinpackages/metadata-core, wiring inpackages/metadata—Part ofthis card, notFixes.Scope A (deferred, stays recorded here): the expression-rewriting conversion (bare root →
record.root, field-name-aware) in the spec conversions registry. Start line ruled: must land before cloud serves customer-built artifacts on centrally-upgraded runtimes (the objectstack-ai/cloud#1688 era — cloud's composed door consuming the conversion policy is the last rail before customer artifacts ride it). Prerequisite premise to pin before design: the exact historical contract status of bare-root spellings (contract, or a then-evaluator accident). After C merges, this card is re-gradedpm:on-holdcarrying that restart condition rather than closed.Routing note: labels set under the maintainer direct-dispatch channel (standing authorization 2026-08-10); landing surface
packages/metadata*⇒domain:engine.Claim: PM direct-dispatch (maintainer ruling 「同意C」, this thread)
Session:local_6f4b2700-a5fd-43ae-98a2-65e25e1dfe95
Branch:claude/issue-12915-bare-root-predicate-notice
Worktree: dev-managed per os-dev protocol
Domain:domain:engine
File surface:packages/metadata-core/src/**,packages/metadata/src/**,.changeset/(stop on breach; explain in the report). Producer-side: detection policy lives besideapplyArtifactForwardConversionsin metadata-core; wiring at the door in metadata.
Container & model: M,mode:subagent,model: opus(dispatch-gates this stroke: no path-derived mandate — PM judgment tier; floor sonnet · default opus)
Clause-②: no — operator-facing boot notice only; no accept/reject behaviour change, no public-surface widening, zeropackages/spec/packages/metadata-protocolchanges
Serial constraints cleared: none — PR #12843 (same surface) is MERGED; no in-flight claims onpackages/metadata*found.Claim: os-dev (scope C only, per the maintainer ruling recorded in this thread)
Session:66492893-3158-450a-acfd-7cb62f526646
Branch:claude/issue-12915-bare-root-predicate-notice
Worktree:objectstack-issue-12915(dedicated, offorigin/main@ 196a6c7)
Scope: loud deduped operator-facing boot notice at the framework artifact door for pre-current-era artifacts carrying form-view predicates with an unbound root identifier. No refusal, no rewrite, no behaviour change; zeropackages/spec/packages/metadata-protocoledits. Scope A (the expression-rewriting ADR-0087 conversion) is deferred and NOT implemented here.
PR will sayPart of #12915— this card stays open for scope A.Generated by Claude Code
os-dev-report
{ "issue": 12915, "status": "done", "branch": "claude/issue-12915-bare-root-predicate-notice", "pr": "https://github.com/objectstack-ai/objectstack/pull/12989", "premise_still_valid": true, "summary": "Scope C implemented as ruled: a loud, deduped, operator-facing boot notice at the framework artifact door for pre-current-era artifacts carrying form-view predicates whose root identifier is not in the bound vocabulary. New policy detectUnboundFormViewPredicateRoots (+ BOUND_FORM_VIEW_PREDICATE_ROOTS) in packages/metadata-core/src/form-predicate-root-policy.ts, exported for a composed door to reuse; wiring in packages/metadata/src/plugin.ts _convertArtifactForward, reusing the existing artifactConversionWarned dedupe set. No refusal, no rewrite, no behaviour change; zero packages/spec and packages/metadata-protocol edits; scope A not touched. Card premise verified on the tree and holds. Window posture verified in code rather than assumed: the notice reads the conversion pass's own verdict, firing only on converted-forward and converted-undeclared (undeclared IS grandfathered as old data at rest), never on authored-current or runtime-version-unknown. ONE PM mechanism assumption refuted: the form-view predicate key set is visibleWhen plus the deprecated visibleOn alias on both field entries and sections, NOT the briefed visibleWhen/requiredWhen/readonlyWhen/visibleOn -- requiredWhen and readonlyWhen are declared on the OBJECT field schema (packages/spec/src/data/field.zod.ts), a different surface with its own scope, and are out of scope C. Per-option visibleWhen deliberately excluded (its evaluator also binds current_user). A real CEL parse was measured as unreachable: packages/formula would break metadata-core's spec-only dependency edge and spec exposes no parse, so a conservative tokenizer is used -- the same shape the spec's own enforced checkFormViewPredicateFeaturesRoot already takes for this exact question. History note: the branch carries four wip commit subjects because collapsing them after the first push would have required a force-push (banned); the tree was proven byte-identical before returning to the pushed commit, so nothing was lost.", "tests": "Union re-run on final commit 8ee7e0793 (working tree clean, branch pushed). SUITES: pnpm --filter @objectstack/metadata-core --filter @objectstack/metadata test exit 0 -- 'Test Files 15 passed (15) / Tests 255 passed (255)' and 'Test Files 37 passed (37) / Tests 672 passed (672)'. 28 of those are new (21 policy unit pins + 7 door pins). TYPE GATES: pnpm --filter @objectstack/metadata-core typecheck exit 0, and tsc -p tsconfig.test.json --listFiles confirms it really reads both new metadata-core files (1 hit each) -- NOT a green over source nothing read. packages/metadata declares no typecheck script (DEBT ledger entry, dts build is its type gate): pnpm --filter @objectstack/metadata build exit 0 with 'check-dts-emitted: 4/4', and its ledger entry holds at exactly its recorded 89. RATCHET: node scripts/check-type-check-coverage.mjs --re-measure under NODE_OPTIONS=--max-old-space-size=4096 -- 'OK -- 31 ledger entr(ies) re-measured in 116.9s, 1570 raw tsc error(s) total, none above its recorded number' plus 'surplus: none -- every entry sits exactly at its measurement'. GATES: families derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (script-derived change set, no hand-built path list); all 21 path-matched families and all 6 convention-triggered ones exit 0 -- including check:cross-package-test-inputs, check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:stack-collection-maps, check:durability-log-level, check:test-source-alias, check:type-source-resolution, check:published-files, check:undeclared-dep-imports, check:nul-bytes. check:dispatcher-error-vocabulary exit 0 (run explicitly per the dispatch caveat; no new SCREAMING_SNAKE code literal was introduced). LINT: full pnpm lint (eslint . --no-inline-config) exit 0 -- no narrowing claimed on that axis. NOT MEASURED (one): a bare node scripts/pm/check-half-states.mjs exited 3 with 'PREREQUISITE NOT MET -- the anonymous API rate limit (60 req/h) is exhausted for this egress IP' -- a transport refusal, read as NOT MEASURED, not as a red gate; the form CI runs (check:pm-half-states = --self-test) exits 0. ABLATION: packages/metadata/vitest.config.ts carries NO alias for @objectstack/metadata-core, so the door suite resolves it through dist/ -- both legs rebuilt and both proved what reached dist. Predicted direction declared before running (the 2 notice-asserting pins go red, the 5 silence pins stay green); observed exactly '2 failed | 5 passed', both failures 'expected [] to have a length of 1'. Mutation confirmed ON DISK before the rebuild by counting the injected marker (1) and the anchor (still 1), then in dist by scripts/ablation-dist-preflight.mjs ('marker present in 2 built files'). Restore leg: git checkout HEAD -- PATH, git diff HEAD empty, restored blob byte-identical to the HEAD blob 0d828bb8f3fe8d36ca2e3bb6b4885e5ab522e991, rebuild, preflight --absent clean ('marker absent from all 18 built files'), suite green again 7/7. A FIRST ablation attempt was VOID and is reported rather than silently retried: the marker was a comment, esbuild stripped it, it reached only the sourcemap, and the preflight refused to count it -- no conclusion was drawn from that run.", "mcp_calls": "0 -- GitHub was reached entirely over the repo-scoped REST channel (gh api / gh pr create), probed green at task start; no MCP GitHub call was made.", "open_questions": [], "out_of_scope_findings": [ "NOT filed as a new card, deliberately, and recorded here instead because this card stays open and already owns the deferred surface: the OBJECT field schema's requiredWhen / readonlyWhen (packages/spec/src/data/field.zod.ts) bind their own record + parent scope, so a pre-move artifact could in principle carry bare roots there with the same fault-open shape. I did NOT measure that surface's fault behaviour or look for a real artifact carrying it, so filing would have minted an unverified card; it is a scope question for the deferred scope A design rather than a confirmed defect. PM: fold it into scope A's premise-pinning step, or ask for it to be measured separately." ] }Generated by Claude Code
ACCEPT — scope C delivered on PR #12989, reviewed against the tree and a live boot, not the report.
- PR shape: draft, base
main, first linePart of #12915(no closing keyword anywhere — the card stays open for scope A), 7 files all inside the declared surface (metadata-corepolicy + tests + export,metadatafixture + door tests + wiring, changeset). Zeropackages/spec/packages/metadata-protocoledits. - Detector spot-read: string literals stripped before the scan, comprehension macros make the predicate opaque (skipped whole), call targets and CEL reserved words excluded, per-call regex (no
lastIndexstate), union root vocabulary sourced from the contract prose. Precision resolves toward silence, as dispatched. - Window gating verified at the source: the notice reads the conversion pass's own
verdict(converted-forward/converted-undeclaredonly), and that verdict is set by the floor comparison BEFORE any conversion applies — window state, not conversion count.authored-currentandruntime-version-unknownreturn early. The two features cannot disagree about which artifacts are old. - Dev refuted one dispatch assumption with evidence, accepted: the form-view predicate key set is
visibleWhen+ thevisibleOnalias (field entries + sections);requiredWhen/readonlyWhenare OBJECT-field-schema surface with its own scope — recorded below for scope A's premise-pinning, not expanded here. - Live acceptance on the real deployment: merged the branch onto current
mainlocally, rebuilt the two packages, re-booted the real 2026-08-05 17.1-built hotcrm artifact — ONE deduped line: "carries 47 form-view predicate(s) whose root identifier is NOT bound …'status','disqualification_reason','duplicate_of_type','rating'… across 1 view(s): crm_lead (first at views[9].form.sections[1].fields[5].visibleWhen) … rebuild the artifact with current tooling ('os build')". Boot completes, no behaviour change, conversion summaries unchanged beside it. - Union per the report: 255+672 package tests (28 new pins), typecheck/dts gates, capped debt re-measure, full lint, 21 path + 6 convention gate families — all exit 0; ablation ran both legs with dist preflights and a VOIDED first attempt honestly reported.
Landing next: full-green check on
8ee7e0793→ ready → merge queue. After MERGED this card re-grades topm:on-holdcarrying scope A's restart condition.- PR shape: draft, base
os-dev-report
{ "issue": 12915, "status": "done", "round": "patch round on scope C (PR #12989 merged as b745157e); this is a NEW PR on a NEW branch", "branch": "claude/issue-12915-current-user-vocabulary", "pr": "https://github.com/objectstack-ai/objectstack/pull/13005", "base": "main @ feab4bff9", "head": "330e4b85b", "files_changed": 6, "premise_still_valid": true, "premise_notes": "The hand-off premise was verified against current origin/main rather than taken on trust, and it HOLDS. Fresh-read FormFieldSchema.visibleWhen (the '## current_user DOES bind here' block plus its describe string) and FormSectionSchema.visibleWhen: a FIELD-level predicate resolves current_user and the ADR-0068 aliases user / ctx.user / os.user since objectui#6010, unbound only on the public /f/:slug route; a SECTION-level predicate keeps current_user UNBOUND and faults open. ADR-0068 itself confirms the alias spellings, so the ROOT identifiers are current_user / user / ctx / os. My merged module did carry the false claim and would have false-flagged a legitimate field-level current_user predicate.", "summary": "Split the form-view predicate root vocabulary per surface. BOUND_FORM_VIEW_PREDICATE_ROOTS keeps its exact name AND value (record/previous/parent/data) and is now documented as the shared base = the SECTION vocabulary; new FIELD_ONLY_BOUND_PREDICATE_ROOTS (current_user/user/ctx/os) and BOUND_FORM_FIELD_PREDICATE_ROOTS (base + field-only) express the field surface. The traversal already knew which slot it was scanning, so the split is a parameter rather than a second scanner: scanPredicateSlot takes the surface and picks the vocabulary; UnboundFormPredicateRoot gained a `surface` field. unboundRootsInCelSource takes the vocabulary as an OPTIONAL second argument whose default is unchanged (the stricter base), so no existing caller changes behaviour -- and the strict default means a forgetful caller gets a findable false positive rather than a silent miss. Stale contract quotes corrected in three places (module header, vocabulary docblock, the door method docblock), each citing #12930 and objectui#6010 and recording the mid-flight drift so the next reader knows why a merged PR needed a same-day correction. Warn-line wording changed: printing one flat list would either understate the field vocabulary (reading as 'your legitimate current_user predicate is broken') or quote a section rule at an operator with no section findings, so it now prints bound roots PER SURFACE and only for the surfaces the findings implicate. Two limits of the field binding deliberately do NOT change the detector's answer, and the module states why: it is a rendering rule not authorization (an authoring hazard, not version drift), and the /f/:slug unboundness is equally true of a freshly built CURRENT artifact, so it says nothing about the artifact's era -- the only thing this notice claims to detect.", "tests": "Union re-run on final commit 330e4b85b (working tree clean, branch pushed). SUITES: pnpm --filter @objectstack/metadata-core --filter @objectstack/metadata test exit 0 -- 'Test Files 15 passed (15) / Tests 260 passed (260)' (was 255, +5) and 'Test Files 37 passed (37) / Tests 674 passed (674)' (was 672, +2). Every pre-existing pin unchanged and still green. NEW PINS: field-level predicates rooted at each of current_user / user / ctx / os produce ZERO notices, pinned at BOTH the policy level and the door level (the door pin drives all four through _parseAndRegisterArtifact on an old-floor artifact); section-level current_user still flagged; the same predicate judged differently per surface; findings tagged with the deciding surface; the warn line prints only the implicated surface's vocabulary (asserted positively AND negatively -- a field-only artifact must NOT print the section rule, and vice versa). The era fixture gained a legitimate field-level current_user predicate as a standing silent control, with the premise guard extended to assert it is there. TYPE GATES: pnpm --filter @objectstack/metadata-core typecheck exit 0, and tsc -p tsconfig.test.json --listFiles confirms it still reaches both changed metadata-core files (1 hit each). packages/metadata has no typecheck script (DEBT ledger entry, dts build is its type gate): pnpm --filter @objectstack/metadata build exit 0. RATCHET: node scripts/check-type-check-coverage.mjs --re-measure under NODE_OPTIONS=--max-old-space-size=4096 -- 'none above its recorded number' + 'surplus: none'. GATES: families re-derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack AND diffed against the previous round's derivation -- the family set is IDENTICAL in both directions, so no newly-implicated gate went unrun (main gained a family, 179 to 180, but it does not match these paths). All 21 path-matched plus the 6 convention-triggered families exit 0; check:dispatcher-error-vocabulary exit 0 (no new SCREAMING_SNAKE code literal). LINT: full pnpm lint exit 0. NOT MEASURED (one, unchanged): bare node scripts/pm/check-half-states.mjs exit 3, 'PREREQUISITE NOT MET -- the anonymous API rate limit (60 req/h) is exhausted for this egress IP' -- a transport refusal, not a red gate; the form CI runs (check:pm-half-states = --self-test) exits 0. ABLATION targeting exactly what this patch adds: collapse the FIELD vocabulary back onto the section base (reintroducing the defect), rebuild, confirm the new pins go red. packages/metadata/vitest.config.ts carries NO alias for @objectstack/metadata-core, so the door suite resolves it through dist/ -- both legs rebuilt, both proved what reached dist. Predicted direction declared before running (the field-level current_user silence pins go red); observed door '2 failed | 7 passed' and policy '4 failed | 22 passed', and EVERY failure is a per-surface-split pin by name -- no incidental collateral. Mutation confirmed ON DISK before the rebuild by counting the injected marker (1) AND the replaced anchor (0, i.e. the replacement really happened rather than an append), then in dist by scripts/ablation-dist-preflight.mjs ('marker present in 2 built files'). Restore leg: git checkout HEAD -- PATH, git diff HEAD empty, restored blob byte-identical to the HEAD blob dcc5963d525c5769dc05835a30564ab16d11d8ad, rebuild, preflight --absent clean, both suites green again at 9/9 and 26/26.", "deviations": [ "Warn-line text changed from a single flat 'bound roots: ...' to a per-surface rendering. This is a log-string change only (no API, no behaviour), taken under the coordinator's explicit 'your call on wording'; the door pins assert both the positive and the negative direction so the wording cannot silently drift back.", "unboundRootsInCelSource gained an OPTIONAL second parameter rather than a required one: the export shipped hours ago and a required parameter would be a breaking change to a published surface for no gain. Default is the unchanged strict base." ], "mcp_calls": "0 -- GitHub reached entirely over the repo-scoped REST channel (gh api / gh pr create); no MCP GitHub call in this round.", "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
ACCEPT (patch round) — PR #13005 reviewed against the tree; landing waits on my own full-green read of
330e4b8.- Shape: draft, base
main,Part of #12915first line, zero closing keywords, 6 files all inside the same declared surface, changeset present. - The per-surface split matches the corrected contract on current
origin/main:FIELD_ONLY_BOUND_PREDICATE_ROOTS = current_user/user/ctx/os(objectui#6010, re-measured by docs(spec,lint): a form FIELDvisibleWhenbindscurrent_user— re-measure the three stale text sites #12930), sections keep the strict base;BOUND_FORM_VIEW_PREDICATE_ROOTSkeeps its name and value so the shipped export is unchanged, and the new optional vocabulary parameter defaults to the STRICT base — a forgetful caller gets a findable false positive, never a silent miss. - Warn line now prints only the implicated surface's vocabulary — spot-read the wiring diff; the pins hold both the positive and negative direction of that rendering.
- The mid-flight drift (module authored against prose that docs(spec,lint): a form FIELD
visibleWhenbindscurrent_user— re-measure the three stale text sites #12930 corrected while the PR was in queue) is recorded in the module docs where the next reader will trip over it. - Report's union: 260+674 package tests (+7 pins), identical gate-family derivation diffed against the previous round (no newly-implicated family unrun), capped debt re-measure, full lint — all exit 0; ablation reintroduced the exact defect and every failing pin was a per-surface-split pin by name.
On green: ready → merge queue. After MERGED, this card closes not planned per the maintainer ruling (2026-08-28, live chat: 「那就挂A,然后直接not planned,留作备查。」) with the scope-A archive in the closing comment.
- Shape: draft, base
Closing — scope C delivered and landed; scope A archived here, not planned, by maintainer ruling.
Scope C — DONE, on
main- PR feat(metadata-core,metadata): warn when a pre-current-era artifact carries fault-open form-view predicates #12989 (merged
b745157e): the detection policy (detectUnboundFormViewPredicateRoots,packages/metadata-core) + the deduped operator boot notice at the framework artifact door, gated by the feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door #12843 versioned window's own verdict. Live acceptance on the real 2026-08-05 17.1-built hotcrm artifact: one line naming 47 predicates / 4 roots / 1 view with the fault-open consequence and theos buildprescription. - PR fix(metadata-core,metadata): split the form-view predicate root vocabulary per surface so a field-level current_user test is not false-flagged #13005 (merged
2852acce): same-day per-surface vocabulary correction — mid-flight, docs(spec,lint): a form FIELDvisibleWhenbindscurrent_user— re-measure the three stale text sites #12930 re-measured the contract prose this module was authored against (a form FIELDvisibleWhenbindscurrent_userand the ADR-0068 aliasesuser/ctx.user/os.usersince objectui#6010; a SECTION does not). Field slots now stay silent on thecurrent_userfamily; section slots keep the strict base; the warn line prints only the implicated surface's vocabulary. Both directions pinned.
Scope A — NOT PLANNED, archived for reference
Maintainer rulings, verbatim and untranslated (2026-08-28, live PM chat): 「同意C」, then 「那就挂A,然后直接not planned,留作备查。」
What A would be: an ADR-0087 conversion rewriting bare-identifier form-view predicate roots to the
record.root for artifacts whose declared floor predates the root move — riding the #12843 door automatically once registered.Why not now (four-axis, recorded): it solves exactly ONE era class, not the family; it is the highest-risk conversion type (expression surgery — done wrong it silently corrupts predicate semantics, worse than fault-open); C already removed the dangerous part (silence); today zero deployments carry an artifact that cannot simply be rebuilt.
The re-decision checkpoint (reopen this card then): when cloud begins serving customer-built artifacts on centrally-upgraded runtimes — the objectstack-ai/cloud#1688 era is the last rail before that. At that point weigh, in order of preference:
- Cloud-side rebuild-on-upgrade (pipeline policy: cloud holds project sources, so a platform upgrade re-runs the build) — zero conversion risk, likely makes A unnecessary;
- The expression-rewriting conversion (A proper) — only if rebuild is infeasible for some artifact class;
- Nothing beyond C's notice — if the affected class turns out empty.
Premises to pin before any A design (both recorded, neither measured):
- The exact historical contract status of bare roots (contract, or a then-evaluator accident) — determines whether a rewrite is a conversion or an amnesty;
- The OBJECT field schema's
requiredWhen/readonlyWhen(packages/spec/src/data/field.zod.ts) bind their own scope — whether pre-move artifacts carry bare roots THERE with the same fault-open shape was not measured in this card's work (noted by the scope-C dev, deliberately unfiled).
General-class note: the recurring pattern ("contract tightening degrades era-built artifacts") is guarded by the #12843 rail (any future conversion entry plugs in) and by review discipline on tightening PRs — not by scope A, which covers only this instance.
Closed not planned — the card IS the archive; reopening is free at the checkpoint above.
- PR feat(metadata-core,metadata): warn when a pre-current-era artifact carries fault-open form-view predicates #12989 (merged
- added a commit that references this issue
on Sep 28, 2026 - added a commit that references this issue
on Sep 29, 2026
Found during the post-merge browser dogfood of PR #12843 (2026-08-28, isolated-posture epic #12701 aftercare). Filed by PM session
local_6f4b2700-a5fd-43ae-98a2-65e25e1dfe95. Dedupe: searched requiredWhen/visibleWhen family in objectstack and objectui — the objectui conditional-rule cards (objectui#4161/#5627/#6261, all closed) fix evaluator plumbing, none covers the artifact-era root move; no objectstack card names a predicate-root conversion.Reproduction (real machine, real artifact)
main(contains feat(metadata): versioned ADR-0087 forward conversion at the artifact-ingestion door #12843's versioned forward-conversion door),os serve -p 4712withOS_ARTIFACT_URL=file://<hotcrm>/dist/objectstack.json— a REAL artifact built 2026-08-05 by released@objectstack/cli17.1.0 (hotcrm v2.2.2,engines.protocol ^17.0.0-rc.1).new) → submit is BLOCKED: 未通过原因 (disqualification_reason), 重复于 (duplicate_of_type), 重复的线索 (duplicate_of_lead) are rendered and flagged required./api/v1/data/crm_leadfrom the same session: 201 Created. Server-side validation is correct; the dead-end is client-side and metadata-era-induced.Mechanism — each step measured
{"field": "disqualification_reason", "required": true, "visibleWhen": {"dialect": "cel", "source": "status == \"unqualified\""}}— bare identifier root, the era's working spelling (hotcrm v2.2.2 ran fine on 17.1 with it).record.root and declares the fail-mode: "a bare identifier is unbound and faults open" (packages/spec/src/ui/view.zod.ts:1981).required: true(authored to be gated by visibility) blocks every create.record.-root spellings (src/views/lead.view.ts:10,36,90on current hotcrm main documents exactly this fail-open) — which is why only ALREADY-BUILT artifacts exhibit the degradation. This is the Artifacts built by released 17.x tooling are REFUSED by the 17.2 runtime: retired-key tombstones fire at artifact parse, and no artifact-ingestion door runs the ADR-0087 conversion that exists for exactly this #12772 shape one layer up: the artifact parses now, but a semantic surface the era legally authored is silently broken.Why this lands here (not objectui, not hotcrm)
The pinned console behaves per the current contract (fault open — and the loud-rejection precedent for unbound roots is the 2026-08-27
features.*ruling recorded on view.zod.ts). The era-compatibility fix surface is the ADR-0087 conversions registry inpackages/spec: a versioned conversion rewriting bare-identifier form-view predicate roots to therecord.root for artifacts whose declared floor predates the root move. The rail for applying it already exists: PR #12843'sapplyArtifactForwardConversionsreplays the registry (retired entries included) at every framework artifact door, keyed off the artifact's declared floor — a new registry entry plugs straight in, M2-return semantics preserved by the same versioned-window rule.Open design point for triage/spec seat: the rewrite needs a field-name-aware guard (only prefix identifiers that name fields of the view's object; leave
has(...)args and non-field identifiers alone), and a ruling on whether fault-open for still-unconverted spellings should become loud likefeatures.*.Impact
Any pre-move artifact using the visibility-gated-required authoring pattern dead-ends record creation in the console UI for those objects (API unaffected). For the single-DB SaaS posture this is tenant-facing: a tenant admin cannot create a lead through the UI on an artifact-deployed hotcrm of that era.