Skip to content

ADR-0049 enforce-or-remove: targetVariable on element:text_input / element:record_picker — a declarative hint with zero readers in any repo and no liveness-ledger entry #9198

Closed

Description

@os-project-manager

Filed by the triage seat (scheduled round 2026-08-17) as the enforce-or-remove channel for objectstack-ai/objectui#3834, whose grading history reached this route after its sibling card resolved by retirement.

The declared-but-unenforced shape

packages/spec declares targetVariable on two elements (packages/spec/src/ui/component.zod.ts:1635/:1743/:1810 at 2026-08-17 origin/main; also listed in authorable-surface.base.json and authorable-surface/ui.json). Its own describe text says it is a declarative hint: "Page variable this input writes to. Declarative hint; the live binding resolves via the variable whose source equals this component id (see PageVariableSchema)."

Measured (objectui#3834, re-verified 2026-08-17 against objectui origin/main):

  • Zero production readers in objectui — the only hits are the reverse-parity gate's exemption block (apps/console/src/__tests__/registry-inputs-spec-parity.test.ts:545-556, citing objectui#3834) and CHANGELOG lines. The live binding runs the other direction (usePageVariableBinding(schema?.id) reverse-looks-up PageVariableSchema.source), which works and matches the spec's own description.
  • No entry for it in any liveness ledger / ADR-0087 registry (grep across packages/spec, docs/adr, docs/audits — zero hits outside the schema and generated surface files).

Why this card, and why now

The hazard was written on objectui#3834: an author (human or AI) who reads the manifest will write targetVariable, skip the variable's source, and get an input that writes nothing, with zero diagnostics. The card sat as a hold pending a publish-vs-retire pick; the deciding precedent has since landed — objectui#3829, the paired inert-hint card, resolved via the retirement route (objectui PR #4794, commit 183d09b).

The work

Run the ADR-0049 enforce-or-remove flow for these two keys (both shapes identical — one disposition covers both):

  1. Establish the liveness verdict properly (the spec-property-retirement playbook's ledger discipline — the measurement above is a lead, not a ledger row).
  2. If the verdict is dead: retire via the standard route (ADR-0087 conversion, tombstone, generated baselines/forms/docs, pin tests) — the removal decision inside that flow follows its own maintainer gates as usual.
  3. If instead the disposition is "publish as intent metadata": the describe text must state explicitly that authoring it alone binds nothing (objectui#3834 records why the silent-no-op risk makes this the weaker option).
  4. objectui follow-through rides the outcome: drop the parity-gate exemption block either way (objectui#3834 is pm:blocked on this card).

Backlinks: objectui#3834 (origin, full analysis and grading history) · objectui#3829 → objectui PR #4794 (the sibling settled by retirement) · ADR-0049 · ADR-0087.

Activity

  1. self-assigned this
    on Aug 17, 2026
  2. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    Claim: PM seat (spec lane), round 12.


    Generated by Claude Code

  3. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

    PM review — ACCEPT (round 12, session session_01225pUjnCKWqxcc1PeqKFUq).

    PR #9221 verified; spot-checked on the branch head: retiredKey() tombstones on both elements, the two 18.ui__*__targetVariable retired-key entry files, the element-input-target-variable-removed D2 conversion in the regenerated registry, and element:filter untouched (grep-zero in the diff).

    Landing flow: flip ready + auto-merge at gate-green.


    Generated by Claude Code

  4. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor
    {
      "issue": 9198,
      "status": "done",
      "branch": "claude/issue-9198-targetvariable-enforce-or-remove",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/9221",
      "pr_number": 9221,
      "premise_still_valid": true,
      "ledger_verdict": "dead",
      "verdict_evidence_summary": "Re-measured against sources at retirement time: objectui origin/main 372d9f9b4 has zero production readers (only the parity-gate exemption block + CHANGELOG; live binding is usePageVariableBinding(schema?.id) reverse-lookup over PageVariableSchema.source); framework repo has only spec's own accept tests; cloud origin/main d8c3c44 has zero hits. No liveness-ledger row exists or is owed — element props schemas are outside the walked metadata-type shapes (the #5775 precedent likewise has no rows; check:liveness green both directions); the verdict is recorded in the retired-key entry files.",
      "route_taken": "retire",
      "semantic_entry": "not required — D2 conversion `element-input-target-variable-removed` (toMajor 18, retiredFromLoadPath, stripKeys) is the channel; page components are stack collection members, unlike the kernel/Manifest:loading family",
      "changeset": ".changeset/element-input-target-variable-retired.md — @objectstack/spec: minor (post-GA launch-window convention, #8586 precedent), FROM→TO + `adr-0087: registered element-input-target-variable-removed` marker",
      "files_touched": [".changeset/element-input-target-variable-retired.md", "content/docs/references/ui/component.mdx", "packages/spec/authorable-surface/ui.json", "packages/spec/src/conversions/registry.ts", "packages/spec/src/migrations/entries/retired-keys/18.ui__ElementRecordPickerProps__targetVariable.ts", "packages/spec/src/migrations/entries/retired-keys/18.ui__ElementTextInputProps__targetVariable.ts", "packages/spec/src/migrations/registry.ts", "packages/spec/src/ui/component.test.ts", "packages/spec/src/ui/component.zod.ts", "packages/spec/src/ui/page.test.ts"],
      "objectui_followthrough_owed": true,
      "tests_added": "2 refusal pins (prescription text, s-flag) + 2 accept-side controls (clean parse materializes nothing) in component.test.ts; conversion fixture (4 notices; nested container, slot, live variables[].source and element:filter controls) replayed by conversions.test.ts; stale page.test.ts fixture re-spelled (dropped #5775's displayField drift + targetVariable)",
      "tests": "All at final head e4204118b: spec test 407 files / 10832 passed; spec typecheck green; check:generated 13/13 ✓ (after --fix regenerated only gen:docs); check:liveness / empty-state / skill-examples / variant-docs PASS; check:adr-0087-registration + check:nul-bytes green; lint 2061 passed; dogfood expression-conformance 3 passed (dependency closure built first); dispatch-gates union all PASS incl. type-check-debt and dev-prereqs after full workspace build",
      "gates_run": {"spec_test": "pass", "spec_typecheck": "pass", "check_generated": "pass(13/13)", "liveness_audits": "pass", "adr_0087_registration": "pass", "nul_bytes": "pass", "dispatch_gates_union": "pass", "ci": "in_progress (draft PR just opened; PM reads conclusions)"},
      "reverse_verification": "Direction decided beforehand: red. Fix committed first; component.zod.ts restored to origin/main tree-only (git restore --source, no staging); exactly the 2 refusal pins went red, 183 others green; restored from the branch commit, 185/185 green, working tree clean",
      "deviations": ["dispatch-gates first run used a two-dot diff and picked up one disjoint docs-comment commit that landed on origin/main (#9199, plugin-security) — re-derived from the merge-base set; no merge needed, zero overlap", "spec-changes.json / upgrade guide deliberately unchanged: they carry published majors only (no major-18 entries exist for #8586 either); gates green"],
      "open_questions": [],
      "risks": ["conversions/registry.ts and migrations step-18 block are hot files — a sibling landing another major-18 conversion will conflict textually in CONVERSIONS_BY_MAJOR[18]/conversionIds; regen-merge discipline applies", "#9220's future disposition of element:filter will need to update this conversion's negative-control fixture"],
      "out_of_scope_findings": ["filed as #9220: element:filter declares targetVariable (and possibly the whole element) with no renderer or reader in any repo — element-grain verdict needed before per-key retirement"]
    }

    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions