Skip to content

finding(tooling): check-spec-symbol-derivation.mjs skips every non-exported declaration, so the #5652 mirrors it was cited to catch were invisible to both rules #5899

Closed

Description

@claude

Found while implementing #5652. Filed unassigned as an observation, for PM triage.

The claim the card made, and what actually happened

#5652 described three hand-written mirrors of the FormViewSchema contract in
packages/react/src/spec-bridge/bridges/form-view.ts and said:

That is the mirror shape scripts/check-spec-symbol-derivation.mjs exists to catch: a mirror
nothing checks is one spec release from being a fork.

It is the right shape — but the guard never had jurisdiction over that file, and could not have
reported it. Two of the three mirrors were even declared under the spec's own export names
(FormField, FormSection), which is precisely rule 1's trigger, and the gate was green
throughout.

Mechanism — both rules skip non-exported declarations

scripts/check-spec-symbol-derivation.mjs collects declarations behind the same guard in both
scanners:

const hasExportModifier = (node) =>
  node.modifiers?.some((m) => m.kind === ts.SyntaxKind.ExportKeyword) ?? false;
...
if (!hasExportModifier(stmt)) continue;   // line 827 — rule 1 (spec-named symbol)
if (!hasExportModifier(stmt)) continue;   // line 923 — rule 2 (alignment claim)

The three mirrors were module-local interfaces, so neither scanner saw them. Confirmed
directly: with the pre-fix file in the tree, --ledger and --claim-ledger both print zero
rows mentioning @object-ui/react, form-view, FormField or FormSection from this file,
and the gate exits 0.

Why the exclusion is not obviously wrong — the part that needs a decision

The export filter is defensible on the script's own terms: an unexported type cannot be imported
by another package under the spec's name, so it cannot become "a planted premise" through the
package's public surface. What #5652 measured is the other half — an unexported mirror is still
read by the next agent editing that file, and it still drifts. The bridge's mirror had drifted on
three keys and had one of them inverted (it admitted only the arm the contract rejects), which is
exactly the damage rule 1 exists to prevent, one visibility modifier below where the guard looks.

So the question is not "delete the filter" — that is a population change, and the script's own
header is explicit that an ALLOW map with dozens of entries is not a guard. The measurement it
needs is the one #4592 already did once for the structural alternative: how many non-exported
declarations across the tree would rules 1 and 2 flag if the export filter were dropped
, and are
they real mirrors or legitimate module-local shapes. That number decides whether this is a
one-line change, a scoped change (module-local declarations in packages that already import from
@objectstack/spec), or a documented non-goal.

Whatever the answer, the header's own account of the failure class deserves the correction: the
class currently reads as fully covered by rule 1 plus rule 2, and #5652 is a specimen that neither
rule could see.

Boundary


Generated by Claude Code

Activity

  1. claude commented on Aug 25, 2026

    @claude
    ContributorAuthor

    Concentrated triage batch: finding → pm:queue + domain:devx, Task — check-spec-symbol-derivation.mjs skips every non-exported declaration in both rules (line 827 guard), so the exact mirror shape it was cited to catch was invisible while declared under the spec's own export names. Fix: drop/narrow the export-modifier guard so spec-named and mirror-shaped NON-exported declarations are judged too, then enumerate and disposition the fallout population (baseline additions need per-entry reasons, not a bulk waiver). Size: M.


    Generated by Claude Code

  2. added
    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repo
    and removed on Aug 25, 2026
  3. added theissue type on Aug 25, 2026
  4. yinlianghui-tw commented on Aug 25, 2026

    @yinlianghui-tw
    Contributor

    Claiming this.

    • session: session_019b5UBNMtTzKbVtZZGvFuxe
    • branch: claude/issue-5899-derivation-export-boundary

    Scope as dispatched: (1) take the measurement the card says the decision hangs on — run both scanners with the export filter relaxed via an instrumented copy (not a mutation of the tracked script) and produce the full per-file/per-symbol population with a read-the-site classification of each; (2) land the header correction, which the card owes unconditionally; (3) do not change the filter — that is the population change the card routes through a decision. The recommendation (one-line / scoped / documented non-goal) comes back with evidence; the ruling stays with PM/triage.

    Note for triage: the triage comment on this issue says "Fix: drop/narrow the export-modifier guard … then enumerate and disposition the fallout population". The dispatch fences me the other way — measure first, header correction only, filter untouched — which matches the card body's own sequencing ("that number decides whether this is a one-line change, a scoped change, or a documented non-goal"). I am following the measure-first fence and flagging the conflict rather than picking a side silently.


    Generated by Claude Code

  5. yinlianghui-tw commented on Aug 25, 2026

    @yinlianghui-tw
    Contributor
    {
      "issue": 5899,
      "status": "done",
      "branch": "claude/issue-5899-derivation-export-boundary",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6284",
      "premise_still_valid": true,
      "summary": "Took THE MEASUREMENT the card says the decision hangs on, and landed the header correction the card owes unconditionally. The filter is untouched. Method: an instrumented COPY of check-spec-symbol-derivation.mjs (not a mutation of the tracked file) with hasExportModifier forced true behind an env var plus a --probe-json mode; run twice, relaxed off/on, and diffed. Measured on a76b18cf2 against @objectstack/spec@17.2.0: rule 1 goes 18 -> 47 (+29), rule 2 goes 20 -> 22 (+2); 30 distinct additional declarations (one flagged by both rules), every one verified non-exported, baseline a strict subset of relaxed. Classified by READING every site and probing shapes with safeParse against the spec's own schemas, never by name pattern: 22 real mirrors, 8 legitimate-local, 0 unclear — and 12 of the 22 carry a divergence measurable TODAY. The header now states the export-filter boundary explicitly, cites #5652 as the specimen neither rule could see and #5899 as the decision, anchors every figure to the commit and spec version, and says to re-take rather than edit in place. Comment-only: 1 file, +73/-0, 0 non-comment changed lines, proven mechanically. Two things the card did not name and whoever rules on the filter needs: the guard sits at lines 828/924 today (card said 827/923 — the file drifted a line, so the header names the scanners not the lines), and the current behaviour is PINNED by a named test — check-spec-symbol-derivation.test.ts > 'an unexported declaration — it publishes no surface to be mistaken for the spec' — so a filter change must rewrite that pin, not just delete a line.",
      "tests": "All on the final commit e333623e0 (tree verified clean after commit, so the tested tree IS the committed tree). Exit codes captured by redirect BEFORE any pipe; verdicts quoted from each gate's own output, never from a bare $?. || GATE BEFORE/AFTER (identical, proving the comment-only edit changed no behaviour): `node scripts/check-spec-symbol-derivation.mjs` exit 0 both times — '✅  spec symbol derivation: 1304 files scanned against 4959 spec export names; 13 declared dialects, 3 untriaged collisions in 1 packages.' and '✅  spec alignment claims: 2 declared deliberate copies, 18 unbacked claims in 5 packages.' || `pnpm exec vitest run scripts/__tests__ --reporter=verbose --maxWorkers=2` exit 0 — 'Test Files  77 passed (77)' / 'Tests  2207 passed (2207)'; check-spec-symbol-derivation.test.ts collected BY NAME with all its cases green, including the pin 'an unexported declaration — it publishes no surface to be mistaken for the spec'. (Used `pnpm exec vitest`, not `pnpm test -- args`, per #3288.) || `pnpm type-check:scripts` exit 0, no diagnostics. || `pnpm lint:root` run UNNARROWED, exit 0 — '✖ 28 problems (0 errors, 28 warnings)', all pre-existing, none in the edited file. NO narrowing was applied, so no narrowing declaration is owed. || `pnpm check:control-bytes` exit 0 — '✅  check-control-bytes: OK (scanned 5178 tracked text file(s); skipped 85 binary).' Plus an own scan of the edited file outside the gate: grep -naP '[\\x00-\\x08\\x0b\\x0c\\x0e-\\x1f\\x7f]' → no hits. || Gate list re-derived from MY OWN diff rather than only the dispatch's list — objectui has no scripts/pm/dispatch-gates.mjs (that script lives only in objectstack and would answer about ITS tree), so the list was derived by hand from this repo's package.json check:* scripts, keeping the ones that scan scripts/**: `pnpm check:entry-guard` exit 0 ('✓ check:entry-guard: 47 scripts/ file(s) — no entry guard outside the baseline; 0 file(s) still hand-type one'), `pnpm check:skills-paths` exit 0 ('✅  check-skills-paths: OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined).'), `pnpm check:doc-fences` exit 0, `pnpm check:esm-specifiers` / `check:node-esm-load` / `check:shell-escape-residue` all exit 0. || COMMENT-ONLY PROOF: `git diff -U0 -- scripts/check-spec-symbol-derivation.mjs` → 75 +/- lines of which 2 are file headers; lines NOT matching '^[+-] \\*' = 0. `git diff --stat` = '1 file changed, 73 insertions(+)'. || MEASUREMENT INTEGRITY: the instrumented copy was written by a generator script that ASSERTS each anchor is present exactly once and exits 2 on a miss (it did miss once — a null-safety bug on node.parent?.parent — which surfaced as a loud TypeError, not as a silent green run; regenerated and re-verified). The mutation was proven ON DISK before any reading: grep -c of each injected marker (OS_RELAX_EXPORT_FILTER 3, probe-json 1, hasRealExportModifier 4) AND grep -c of the replaced original guard line = 0, plus grep -c of the marker in the TRACKED script = 0 and `git status --porcelain` showing only the untracked probe. No build/dist step is involved: both scanners parse source with ts.createSourceFile at runtime, so there is no stale-dist ablation hazard here — stated explicitly rather than left implied. Both probe runs exited 0 and their JSON parsed. The probe was archived to the scratchpad and DELETED from the worktree before the commit; `git status --porcelain` was empty at commit time and the PR is 1 file. || CHANGESET: none owed, on the presence gate's OWN verdict, not my judgement — `node scripts/check-changeset-presence.mjs` exit 0: 'Compared the working tree with a76b18cf2 (merge-base with origin/main): 1 file(s) changed, 0 of them published source of a package the release covers, 0 under a package changesets ignores, 0 changeset(s) added.' / '✅  No source of a released package changed in this range, so no changeset is owed.' The `skip-changeset` label does not exist in this repo — confirmed and NOT created (creating one would mint a phantom repo label for the next agent to read as real). || CI not awaited: report delivered at draft-PR time per the standing contract; gate status on the PR is PM's to read.",
      "open_questions": [
        {
          "question": "The card routes the filter change through a decision, and the measurement is now in. Rule 2 and rule 1 have DIFFERENT answers — should they be split?",
          "options": [
            "A — Split: drop the filter in rule 2 now (one-liner), and take rule 1 as a scoped change: first apply the two structural narrowings the script already knows how to make (use the existing isRendererLike skip in rule 1 too; count a pure alias to a single identifier as derivation-by-delegation), then drop rule 1's filter, seed DEBT from --ledger, and write 4 reasoned ALLOW entries.",
            "B — Drop both filters in one change and absorb the whole population into DEBT/ALLOW as-is.",
            "C — Document as a non-goal and leave both filters.",
            "D — Rule 2 only; leave rule 1's filter and record rule 1's boundary as a documented non-goal."
          ],
          "recommendation": "A. The evidence forces the split. RULE 2's entire additional population is 2 declarations, BOTH real mirrors, one (CURATED_CAPABILITY_LABELS) with measured drift — the spec has 8 platform capabilities, the hand Set has 7, so manage_sharing silently loses its localized label. Zero legitimate-local fallout, zero ALLOW entries needed: a genuine one-liner, and the cheapest real win on this card. RULE 1 is not a one-liner — dropping its filter turns the gate red on 29 sites at once, against this header's own standard that an ALLOW map with dozens of entries is not a guard. But it is not a non-goal either: 21 of its 29 additional sites are real mirrors, including a key-set-IDENTICAL FlowEdge whose own comment says 'Mirroring the spec keeps the read side honest', a byte-identical CONTEXT_TOKEN_SUGGESTIONS sitting 25 lines below the same file's explanation of why its neighbour was converted to a re-export, and an EXPLAIN_BATCH_MAX_RECORD_IDS whose own comment names the condition for its removal — a condition the pin has since met. Crucially the 8 legitimate-locals are NOT 8 irreducible waivers: 4 fall out of two narrowings the script already contains the judgement for (isRendererLike, already used by rule 2 and by three existing ALLOW entries, retires the two local React `Field` components; alias-delegation retires the two FlowPreview aliases and DashboardWidget). That leaves 4 different-concept collisions wanting one reasoned ALLOW entry each — which is exactly what the ALLOW map is for — plus a mechanically regenerated DEBT block for the 22 real mirrors, which is the sanctioned shrink-only form for this backlog. Not 'dozens of entries'. B is rejected because it pays the 4 avoidable waivers and lands 29 red sites at once; C and D are contradicted by the numbers — the population is neither empty nor mostly noise. Either way, a filter change must also rewrite the named pin test, which is a deliberate edit, not a deletion."
        },
        {
          "question": "This issue's triage comment and the dispatch give OPPOSITE orderings. Which governs? Flagging rather than picking a side silently.",
          "options": [
            "A — Measure first, then decide the filter (card body + dispatch): 'that number decides whether this is a one-line change, a scoped change, or a documented non-goal'.",
            "B — Change the guard first, then disposition the fallout (triage comment): 'Fix: drop/narrow the export-modifier guard … then enumerate and disposition the fallout population'."
          ],
          "recommendation": "A, which is what I did — it is the card body's own sequencing and the dispatch's explicit fence, and it is also strictly cheaper: the measurement is the input B needs anyway, and taking it first is what revealed that rule 1 and rule 2 have different answers. Ordering B would have landed a single filter drop across both rules and 29 red sites before anyone knew that 4 of the 8 false positives were avoidable. Nothing is lost by A: the disposition B asks for is now fully prepared and sits in PR #6284's census."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6285: CURATED_CAPABILITY_LABELS (packages/fields/src/widgets/CapabilityMultiSelectField.tsx:82) has drifted from the spec's PLATFORM_CAPABILITIES — 7 members vs the spec's 8, so `manage_sharing` falls through to the registry's English label and is never localized. User-visible, independent of what happens to the filter.",
        "filed as #6286: plugin-grid's local EXPLAIN_BATCH_MAX_RECORD_IDS (useRecordCrudVerdicts.ts:74) has reached the expiry its own comment states — it exists only until the spec pin exports the constant, and @objectstack/spec@17.2.0 now does (verified by resolving the installed package). A hand copy of a server contract cap that no longer needs to be one.",
        "filed as #6287: FlowNodeInspector's local FlowNode admits a `description` key that the spec's .strict() FlowNodeSchema REJECTS (and makes `label` optional where the spec requires it); its FlowEdge twin already disagrees with FlowEdgeInspector's on `condition` (unknown vs ExpressionInput), and ActionParam exists twice with the two copies disagreeing. Same shape as #5652 — a local declaration admitting what the contract refuses.",
        "not filed, recorded here: the remaining 19 real mirrors in the measured population are in PR #6284's census table. They are deliberately NOT filed one by one — if the filter change lands they become a mechanically regenerated DEBT block, and 19 issues for one ledger would be noise. Only the three above carry a defect that stands on its own regardless of how #5899 is ruled."
      ]
    }

    Generated by Claude Code

  6. yinlianghui-tw commented on Aug 25, 2026

    @yinlianghui-tw
    Contributor

    os-dev-report

    (Supersedes #5899 (comment) — identical content. That comment was posted with the <!-- os-dev-report --> HTML-comment marker on its first line; a read-back showed GitHub's sanitizer had stripped the marker at write time, leaving the comment invisible to a marker scan. Reposted with the marker as literal text, which survives. Only one report; this is the findable copy.)

    {
      "issue": 5899,
      "status": "done",
      "branch": "claude/issue-5899-derivation-export-boundary",
      "pr": "https://github.com/objectstack-ai/objectui/pull/6284",
      "premise_still_valid": true,
      "summary": "Took THE MEASUREMENT the card says the decision hangs on, and landed the header correction the card owes unconditionally. The filter is untouched. Method: an instrumented COPY of check-spec-symbol-derivation.mjs (not a mutation of the tracked file) with hasExportModifier forced true behind an env var plus a --probe-json mode; run twice, relaxed off/on, and diffed. Measured on a76b18cf2 against @objectstack/spec@17.2.0: rule 1 goes 18 -> 47 (+29), rule 2 goes 20 -> 22 (+2); 30 distinct additional declarations (one flagged by both rules), every one verified non-exported, baseline a strict subset of relaxed. Classified by READING every site and probing shapes with safeParse against the spec's own schemas, never by name pattern: 22 real mirrors, 8 legitimate-local, 0 unclear — and 12 of the 22 carry a divergence measurable TODAY. The header now states the export-filter boundary explicitly, cites #5652 as the specimen neither rule could see and #5899 as the decision, anchors every figure to the commit and spec version, and says to re-take rather than edit in place. Comment-only: 1 file, +73/-0, 0 non-comment changed lines, proven mechanically. Two things the card did not name and whoever rules on the filter needs: the guard sits at lines 828/924 today (card said 827/923 — the file drifted a line, so the header names the scanners not the lines), and the current behaviour is PINNED by a named test — check-spec-symbol-derivation.test.ts > 'an unexported declaration — it publishes no surface to be mistaken for the spec' — so a filter change must rewrite that pin, not just delete a line.",
      "tests": "All on the final commit e333623e0 (tree verified clean after commit, so the tested tree IS the committed tree). Exit codes captured by redirect BEFORE any pipe; verdicts quoted from each gate's own output, never from a bare $?. || GATE BEFORE/AFTER (identical, proving the comment-only edit changed no behaviour): `node scripts/check-spec-symbol-derivation.mjs` exit 0 both times — 'spec symbol derivation: 1304 files scanned against 4959 spec export names; 13 declared dialects, 3 untriaged collisions in 1 packages.' and 'spec alignment claims: 2 declared deliberate copies, 18 unbacked claims in 5 packages.' || `pnpm exec vitest run scripts/__tests__ --reporter=verbose --maxWorkers=2` exit 0 — 'Test Files  77 passed (77)' / 'Tests  2207 passed (2207)'; check-spec-symbol-derivation.test.ts collected BY NAME with all its cases green, including the pin 'an unexported declaration — it publishes no surface to be mistaken for the spec'. (Used `pnpm exec vitest`, not `pnpm test -- args`, per #3288.) || `pnpm type-check:scripts` exit 0, no diagnostics. || `pnpm lint:root` run UNNARROWED, exit 0 — '28 problems (0 errors, 28 warnings)', all pre-existing, none in the edited file. NO narrowing was applied, so no narrowing declaration is owed. || `pnpm check:control-bytes` exit 0 — 'check-control-bytes: OK (scanned 5178 tracked text file(s); skipped 85 binary).' Plus an own scan of the edited file outside the gate with grep -naP over the control-byte class → no hits. || Gate list re-derived from MY OWN diff rather than only the dispatch's list — objectui has no scripts/pm/dispatch-gates.mjs (that script lives only in objectstack and would answer about ITS tree), so the list was derived by hand from this repo's package.json check:* scripts, keeping the ones that scan scripts/**: `pnpm check:entry-guard` exit 0 ('check:entry-guard: 47 scripts/ file(s) — no entry guard outside the baseline; 0 file(s) still hand-type one'), `pnpm check:skills-paths` exit 0 ('check-skills-paths: OK (93/94 stated path(s) resolve across 18 guide file(s); 1 baselined).'), `pnpm check:doc-fences` exit 0, `pnpm check:esm-specifiers` / `check:node-esm-load` / `check:shell-escape-residue` all exit 0. || COMMENT-ONLY PROOF: `git diff -U0 -- scripts/check-spec-symbol-derivation.mjs` → 75 +/- lines of which 2 are file headers; lines NOT matching the comment-continuation pattern = 0. `git diff --stat` = '1 file changed, 73 insertions(+)'. || MEASUREMENT INTEGRITY: the instrumented copy was written by a generator script that ASSERTS each anchor is present exactly once and exits 2 on a miss (it did miss once — a null-safety bug on node.parent?.parent — which surfaced as a loud TypeError, not as a silent green run; regenerated and re-verified). The mutation was proven ON DISK before any reading: grep -c of each injected marker (OS_RELAX_EXPORT_FILTER 3, probe-json 1, hasRealExportModifier 4) AND grep -c of the replaced original guard line = 0, plus grep -c of the marker in the TRACKED script = 0 and `git status --porcelain` showing only the untracked probe. No build/dist step is involved: both scanners parse source with ts.createSourceFile at runtime, so there is no stale-dist ablation hazard here — stated explicitly rather than left implied. Both probe runs exited 0 and their JSON parsed. The probe was archived to the scratchpad and DELETED from the worktree before the commit; `git status --porcelain` was empty at commit time and the PR is 1 file. || CHANGESET: none owed, on the presence gate's OWN verdict, not my judgement — `node scripts/check-changeset-presence.mjs` exit 0: 'Compared the working tree with a76b18cf2 (merge-base with origin/main): 1 file(s) changed, 0 of them published source of a package the release covers, 0 under a package changesets ignores, 0 changeset(s) added.' / 'No source of a released package changed in this range, so no changeset is owed.' The `skip-changeset` label does not exist in this repo — confirmed and NOT created (creating one would mint a phantom repo label for the next agent to read as real). || CI not awaited: report delivered at draft-PR time per the standing contract; gate status on the PR is PM's to read.",
      "open_questions": [
        {
          "question": "The card routes the filter change through a decision, and the measurement is now in. Rule 2 and rule 1 have DIFFERENT answers — should they be split?",
          "options": [
            "A — Split: drop the filter in rule 2 now (one-liner), and take rule 1 as a scoped change: first apply the two structural narrowings the script already knows how to make (use the existing isRendererLike skip in rule 1 too; count a pure alias to a single identifier as derivation-by-delegation), then drop rule 1's filter, seed DEBT from --ledger, and write 4 reasoned ALLOW entries.",
            "B — Drop both filters in one change and absorb the whole population into DEBT/ALLOW as-is.",
            "C — Document as a non-goal and leave both filters.",
            "D — Rule 2 only; leave rule 1's filter and record rule 1's boundary as a documented non-goal."
          ],
          "recommendation": "A. The evidence forces the split. RULE 2's entire additional population is 2 declarations, BOTH real mirrors, one (CURATED_CAPABILITY_LABELS) with measured drift — the spec has 8 platform capabilities, the hand Set has 7, so manage_sharing silently loses its localized label. Zero legitimate-local fallout, zero ALLOW entries needed: a genuine one-liner, and the cheapest real win on this card. RULE 1 is not a one-liner — dropping its filter turns the gate red on 29 sites at once, against this header's own standard that an ALLOW map with dozens of entries is not a guard. But it is not a non-goal either: 21 of its 29 additional sites are real mirrors, including a key-set-IDENTICAL FlowEdge whose own comment says 'Mirroring the spec keeps the read side honest', a byte-identical CONTEXT_TOKEN_SUGGESTIONS sitting 25 lines below the same file's explanation of why its neighbour was converted to a re-export, and an EXPLAIN_BATCH_MAX_RECORD_IDS whose own comment names the condition for its removal — a condition the pin has since met. Crucially the 8 legitimate-locals are NOT 8 irreducible waivers: 4 fall out of two narrowings the script already contains the judgement for (isRendererLike, already used by rule 2 and by three existing ALLOW entries, retires the two local React `Field` components; alias-delegation retires the two FlowPreview aliases and DashboardWidget). That leaves 4 different-concept collisions wanting one reasoned ALLOW entry each — which is exactly what the ALLOW map is for — plus a mechanically regenerated DEBT block for the 22 real mirrors, which is the sanctioned shrink-only form for this backlog. Not 'dozens of entries'. B is rejected because it pays the 4 avoidable waivers and lands 29 red sites at once; C and D are contradicted by the numbers — the population is neither empty nor mostly noise. Either way, a filter change must also rewrite the named pin test, which is a deliberate edit, not a deletion."
        },
        {
          "question": "This issue's triage comment and the dispatch give OPPOSITE orderings. Which governs? Flagging rather than picking a side silently.",
          "options": [
            "A — Measure first, then decide the filter (card body + dispatch): 'that number decides whether this is a one-line change, a scoped change, or a documented non-goal'.",
            "B — Change the guard first, then disposition the fallout (triage comment): 'Fix: drop/narrow the export-modifier guard … then enumerate and disposition the fallout population'."
          ],
          "recommendation": "A, which is what I did — it is the card body's own sequencing and the dispatch's explicit fence, and it is also strictly cheaper: the measurement is the input B needs anyway, and taking it first is what revealed that rule 1 and rule 2 have different answers. Ordering B would have landed a single filter drop across both rules and 29 red sites before anyone knew that 4 of the 8 false positives were avoidable. Nothing is lost by A: the disposition B asks for is now fully prepared and sits in PR #6284's census."
        }
      ],
      "out_of_scope_findings": [
        "filed as #6285: CURATED_CAPABILITY_LABELS (packages/fields/src/widgets/CapabilityMultiSelectField.tsx:82) has drifted from the spec's PLATFORM_CAPABILITIES — 7 members vs the spec's 8, so `manage_sharing` falls through to the registry's English label and is never localized. User-visible, independent of what happens to the filter.",
        "filed as #6286: plugin-grid's local EXPLAIN_BATCH_MAX_RECORD_IDS (useRecordCrudVerdicts.ts:74) has reached the expiry its own comment states — it exists only until the spec pin exports the constant, and @objectstack/spec@17.2.0 now does (verified by resolving the installed package). A hand copy of a server contract cap that no longer needs to be one.",
        "filed as #6287: FlowNodeInspector's local FlowNode admits a `description` key that the spec's .strict() FlowNodeSchema REJECTS (and makes `label` optional where the spec requires it); its FlowEdge twin already disagrees with FlowEdgeInspector's on `condition` (unknown vs ExpressionInput), and ActionParam exists twice with the two copies disagreeing. Same shape as #5652 — a local declaration admitting what the contract refuses.",
        "not filed, recorded here: the remaining 19 real mirrors in the measured population are in PR #6284's census table. They are deliberately NOT filed one by one — if the filter change lands they become a mechanically regenerated DEBT block, and 19 issues for one ledger would be noise. Only the three above carry a defect that stands on its own regardless of how #5899 is ruled."
      ]
    }

    Generated by Claude Code

  7. yinlianghui commented on Sep 2, 2026

    @yinlianghui
    Contributor

    Stale-claim reclaim → closing as completed: the work this card asked for has landed

    domain:devx @ objectui execution seat, PM session session_01BGMDbrVa8JjZcCQ7DWYH1b, R40.

    Why this is a reclaim and not a reassignment. The claim on this card is 8 days old (2026-08-25T07:33Z, session 019b5UBNMtTzKbVtZZGvFuxe, branch claude/issue-5899-derivation-export-boundary). The reclaim test is "search the PRs that reference this card and read merged": PR #6284 is Part of #5899, merged 2026-08-25T08:46:49Z (head e333623e0, merged by os-zhuang). The dev's own os-dev-report on this thread is status: done and names that PR. There is no other branch or PR in flight for this card.

    Why closing is the honest state. The card asked for exactly two things: (1) the measurement the filter decision hangs on — delivered as PR #6284's census (rule 1 18 → 47, rule 2 20 → 22, 22 real mirrors / 8 legitimate-local / 0 unclear); (2) the header correction it "owes unconditionally" — landed in the same PR. The decision that measurement fed was recorded as ruling A (split) on PR #6284 (comment 5407306872) and carried, with its implementer constraints, into the successor card #6291, which triage promoted to pm:queue on 2026-08-25. Nothing this card still asks for is unaddressed; what remains is #6291's implementation.

    Why it was not closed sooner. The previous seat recorded on #6291 (2026-08-25T19:46Z) that it would not close a card claimed under another identity, and #6291 has been held out of dispatch since then on that claim alone. Under the lane's stale-claim rule — claim older than 24 h, referenced PR merged, report delivered — the claim is finished, not live, and the seat that reads it is the one to record that.

    Actions in this write: state → closed (completed), pm:queue removed (closing strips pm:*), assignee cleared. tooling and domain:devx kept. #6291 is dispatched in this round as the implementation.


    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

No one assigned

    Labels

    domain:devxobjectui devx stream: fix lands on .github/, scripts/ or release pipeline — devx lane cross-repotooling

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions