Skip to content

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

@hotlong

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)

Mechanism — each step measured

  1. The 17.1 artifact authors form-view entries as {"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).
  2. The CURRENT contract binds form-field predicates to the record. root and declares the fail-mode: "a bare identifier is unbound and faults open" (packages/spec/src/ui/view.zod.ts:1981).
  3. So on 17.2 the predicate faults open → the conditionally-hidden field renders → its unconditional required: true (authored to be gated by visibility) blocks every create.
  4. hotcrm's own sources were later migrated to record.-root spellings (src/views/lead.view.ts:10,36,90 on 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 in packages/spec: a versioned conversion rewriting bare-identifier form-view predicate roots to the record. root for artifacts whose declared floor predates the root move. The rail for applying it already exists: PR #12843's applyArtifactForwardConversions replays 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 like features.*.

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.

Activity

  1. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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 beside applyArtifactForwardConversions in packages/metadata-core, wiring in packages/metadata — Part of this card, not Fixes.

    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-graded pm:on-hold carrying 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.

  2. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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 beside applyArtifactForwardConversions in 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, zero packages/spec / packages/metadata-protocol changes
    Serial constraints cleared: none — PR #12843 (same surface) is MERGED; no in-flight claims on packages/metadata* found.

  3. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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, off origin/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; zero packages/spec / packages/metadata-protocol edits. Scope A (the expression-rewriting ADR-0087 conversion) is deferred and NOT implemented here.
    PR will say Part of #12915 — this card stays open for scope A.

    Generated by Claude Code

  4. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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

  5. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    ACCEPT — scope C delivered on PR #12989, reviewed against the tree and a live boot, not the report.

    • PR shape: draft, base main, first line Part of #12915 (no closing keyword anywhere — the card stays open for scope A), 7 files all inside the declared surface (metadata-core policy + tests + export, metadata fixture + door tests + wiring, changeset). Zero packages/spec / packages/metadata-protocol edits.
    • 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 lastIndex state), 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-undeclared only), and that verdict is set by the floor comparison BEFORE any conversion applies — window state, not conversion count. authored-current and runtime-version-unknown return 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 + the visibleOn alias (field entries + sections); requiredWhen/readonlyWhen are 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 main locally, 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 to pm:on-hold carrying scope A's restart condition.

  6. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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

  7. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    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 #12915 first 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 FIELD visibleWhen binds current_user — re-measure the three stale text sites #12930), sections keep the strict base; BOUND_FORM_VIEW_PREDICATE_ROOTS keeps 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 visibleWhen binds current_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.

  8. hotlong commented on Aug 28, 2026

    @hotlong
    ContributorAuthor

    Closing — scope C delivered and landed; scope A archived here, not planned, by maintainer ruling.

    Scope C — DONE, on main

    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:

    1. 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;
    2. The expression-rewriting conversion (A proper) — only if rebuild is infeasible for some artifact class;
    3. 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.

  9. added a commit that references this issue on Sep 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions