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. added
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    and removed
    pm:queueReady for the PM dispatch loop
    on Aug 17, 2026
  2. self-assigned this
    on Aug 17, 2026
  3. os-zhuang commented on Aug 17, 2026

    @os-zhuang
    Contributor

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


    Generated by Claude Code

  4. 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

  5. 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

Labels

domain:specDomain lane — seat card indexed by label:pm:seatpm:dispatchedDispatched to a dev agent by /pm-dispatch

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions