Skip to content

composeStacks re-merges bound standalone actions that defineStack already copied into their objects — every bound action appears twice in the composed object's actions #14847

Description

@claude

Found while implementing #14662 (the cross-stack action-key check in composeStacks); out of scope there and recorded here instead. Measured on main @ f3ae441fa, packages/spec/src/stack.zod.ts, two defineStack outputs composed — the shape examples/app-multi-package composes.

What was measured

defineStack ends with mergeActionsIntoObjects(data): every standalone action carrying objectName is COPIED into that object's actions on the way out, and the standalone stays in stack.actions. composeStacks concatenates actions across its inputs and ends with mergeActionsIntoObjects(composed) again — so each bound standalone action is appended to its object a SECOND time, beside the copy the input's own build already put there:

a = defineStack({ manifest: { id: 'com.example.a', ... }, objects: [a_item], actions: [{ name: 'dup_x', objectName: 'a_item', ... }] })
a.objects[a_item].actions            = ["dup_x/BOUND"]                    // one copy: the build's own merge
composeStacks([a, b]).objects[a_item].actions = ["dup_x/BOUND", "dup_x/BOUND"] // two copies of one declaration

Same with three stacks under objectConflict: 'merge' / 'override' (the surviving object's copy plus every concatenated standalone): three entries for two declarations. manifest: 'preserve' inherits it. examples/app-multi-package declares no action, so the shipped fixture does not show it.

Why it matters

The runtime note recorded in the header of packages/spec/src/stack-duplicate-action-key.test.ts (#14686): collectActionDeclarations pushes every embedded entry and dedupes only a standalone against an embedded one, MCP listActions lists both, and bare-name resolveActionByName refuses the ambiguity. A composed artifact therefore lists every bound action twice and cannot resolve it by bare name — and a composed artifact fed back into defineStack is refused by #14686's same-key rule (two embedded twins under one key), so composeStacks output is not round-trippable through the door that built its inputs.

os build does not hit the refusal today because compile.ts validates the lowered stack with ObjectStackDefinitionSchema.safeParse (schema only), not defineStack; the doubled entries reach dist/objectstack.json unremarked.

Suggested direction (for triage, not decided here)

Either mergeActionsIntoObjects becomes idempotent — skip a standalone whose copy is already embedded on the object (identity or structural equality; #14686 deliberately counts an identical hand-written twin as a duplicate, so the skip must be scoped to the build's own echo, e.g. by marking or by comparing against the standalone list rather than treating any equal pair as an echo) — or composeStacks composes the authored shape (standalone actions stripped of their build echo) before merging once. Which of the two is right is a contract question for domain:spec.

#14662's check deliberately counts DISTINCT stacks per key, not sites, precisely so this echo never reads as a collision; that decision is documented in collectComposedActionKeyCollisions and does not fix the doubling.

Filing unassigned for triage.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 3, 2026

    @huangyiirene
    Collaborator

    Triage — graded. bug · priority:p2 · pm:queue · domain:spec · needs:contract-review.

    Verified at origin/main — structurally, not just by the repro

    ✅ mergeActionsIntoObjects is called at stack.zod.ts:2342 and :2404 (the defineStack paths) and again at :2990 (composeStacks). ⇒ The double application is visible in the call graph, independent of the fixture.
    ✅ :1563 documents the semantics that make it double rather than idempotent — "mergeActionsIntoObjects APPENDS, so both survived into object.actions".
    ✅ compile.ts:318 validates the lowered stack with ObjectStackDefinitionSchema.safeParse, not defineStack. ⇒ The card's key consequence holds: os build does not hit #14686's refusal, and the doubled entries reach dist/objectstack.json unremarked.

    Why p2

    ⭐ The sharpest consequence is not the duplicate itself — it is that composeStacks output is not round-trippable through the door that built its inputs. Feed a composed artifact back into defineStack and #14686's same-key rule refuses it (two embedded twins under one key). A pipeline whose output its own input door rejects is a real defect, not cosmetic.

    Downstream, per the runtime note the card cites: MCP listActions lists every bound action twice, and bare-name resolveActionByName refuses the ambiguity. ⇒ User-visible on a composed app.

    ⛔ Not p1: examples/app-multi-package declares no action, so nothing shipped demonstrates it today.

    The fork is real — ⛔ do not pick it by shape

    The card names two directions and correctly declines to choose. ⚠️ Whichever is taken, the binding constraint is the one it identifies:

    #14686 deliberately counts an identical hand-written twin as a duplicate, so the skip must be scoped to the build's own echo

    ⇒ ⛔ A naive "skip structurally equal pairs" is wrong: it would silently swallow the hand-written twin #14686 exists to refuse. The skip has to distinguish the build's own echo from an author writing the same action twice — by marking, or by comparing against the standalone list, not by equality alone.

    needs:contract-review because the choice changes the shape of a published composed artifact, and because getting it wrong disarms a landed refusal.

    ⚠️ If your measurement says neither direction can be scoped safely, ⛔ do not weaken #14686's rule to make room — stop and report; that trade is a maintainer's.

    ✅ #14662's decision to count DISTINCT stacks per key rather than sites is correctly out of scope, and the card is right that it does not fix the doubling — it only stops the echo reading as a collision.

    ⚠️ Sequence with #14848 (same file, objectConflict: 'merge' dropping non-fields keys wholesale). Different defects; ⛔ not merged, ⛔ not parallel.


    Generated by Claude Code

  2. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Claim: PM loop, domain:spec seat (seat post #6017), R2 of this shift
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14847-compose-stacks-action-echo
    Worktree: objectstack-issue-14847
    Domain: domain:spec
    File surface: packages/spec/src/stack.zod.ts — mergeActionsIntoObjects (:2081) and/or composeStacks's final merge (:3151), whichever direction the measurement supports; ⛔ defineStack's same-key refusal (#14686) and collectComposedActionKeyCollisions (#14854) untouched, ⛔ objectConflict: 'merge''s key handling untouched (#14848, serial behind this card) · compose-stacks*.test.ts pins: a bound standalone appears ONCE in the composed object (two stacks; three under merge / override; manifest: 'preserve'), the composed output round-trips through defineStack, and — the binding constraint — an identical hand-written twin is STILL refused by #14686's rule · changeset @objectstack/spec patch (the build's output shape is corrected; accept set unchanged) · generated artifacts only if a describe moves. Stop on breach; explain in the report.
    Container & model: M, mode:subagent, model: fable (CONTRACT_REVIEW_TIER — the emitted shape of a published composed artifact changes, and scoping the skip against #14686's refusal is contract judgment). node scripts/pm/dispatch-gates.mjs --tier packages/spec/src/stack.zod.ts at 77a532d6 (17:00Z): "no path-derived mandate" + "Clause ② SUSPECT surface".
    Clause-②: yes (conservative: the emitted shape of composeStacks output changes for every composed artifact with bound actions; the accept set itself does not move — the delivered diff decides at the gate).
    Serial constraints cleared: fresh open-PR file scan at 17:00Z (27 open PRs) — none touches stack.zod.ts (PR #14975 on this lane is test-only); last commits 35dffeac (#14854, 2026-09-03T09:27Z) and 279431e7 (#14686) ⇒ same-day churn line carried; fold-or-serial for the queued siblings on the same file answered SERIAL — #14848 (different defect: merge drops non-fields keys; triage: "⛔ not merged, ⛔ not parallel") and #14873 (comments) wait behind this card; H17 index: no row.

    Decision re-read (all comments read, one — triage 5521543316): the fork (idempotent mergeActionsIntoObjects vs composing the authored shape) is real and ⛔ not to be picked by shape; the skip must distinguish the build's own echo from an author's hand-written twin (by marking, or by comparing against the standalone list — ⛔ never by equality alone); if neither direction can be scoped safely ⇒ stop and report, ⛔ never weaken #14686. Premise re-read on origin/main 77a532d6 (17:00Z): mergeActionsIntoObjects defined at :2081, called at :2344 and :2406 (defineStack) and :3151 (composeStacks); compile.ts validates with safeParse, not defineStack (card's consequence stands). Premise holds.


    Generated by Claude Code

  3. self-assigned this
    on Sep 3, 2026
  4. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Dispatch (R2 of this shift, 2026-09-03T17:03Z) — domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i, seat post #6017. mode:subagent, model: fable (CONTRACT_REVIEW_TIER), size M, Clause ② yes (conservative). The dev leaves its own Claim: comment below; the assignee stays the PM's.

    Dispatch text handed to the dev, in brief (triage 5521543316's binding constraint, the stop-and-report clause and the #14848 sequencing are quoted verbatim and marked non-renegotiable; #14686 / #14854 are landed rulings, untouchable):


    Generated by Claude Code

  5. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    Claim: os-dev subagent on #14847, dispatched by the domain:spec seat (seat post #6017; PM claim comment 5529219489).
    Session: session_0174WZTU6XcFcS7g2kykC53i (GitHub zhuangjianguo)
    Branch: claude/issue-14847-compose-stacks-action-echo (pushed empty — write-route probe green)
    Worktree: objectstack-issue-14847 (base 6392b9c2, current origin/main)
    Clause-②: yes

    File surface (complete):

    Generated by Claude Code


    Generated by Claude Code

  6. claude commented on Sep 3, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14847,
      "status": "done",
      "branch": "claude/issue-14847-compose-stacks-action-echo",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15022",
      "premise_still_valid": true,
      "summary": "The doubling is real and fixed: mergeActionsIntoObjects is now idempotent over its own output — a bound action the object already carries BY IDENTITY is not appended again — so composeStacks (which concatenates its inputs' actions and re-runs the merge over inputs defineStack already merged) carries each bound standalone once: measured on BASE 6392b9c2, two stacks gave [dup_x/BOUND, dup_x/BOUND] and three stacks under override/merge gave [emb3, b3, b1, b2, b3]; after: [dup_x/BOUND] and [emb3, b3, b1, b2]. Fork decided by measurement: identity is the discriminator under BOTH directions (H1 confirmed — a marker key is refused by the strict ActionSchema, and object identity survives build, mergeObjects and the concat; a strict parse yields fresh objects so a hand-written twin never shares identity and #14686's refusal, which runs before the merge, still fires — pinned); the idempotent-merge direction was taken as the smaller published-shape change: output = previous output minus the duplicates, nothing reordered, and collectComposedActionKeyCollisions (#14854) plus its message untouched, whereas stripping echoes before the walk would move #14854's landed message pin (it names the echo site) and stripping after it reorders the surviving stack's own bound action. H2 pinned (default, preserve incl. packages[] halves agreeing, three stacks override/merge). H3 FALSIFIED in its literal form by a landed ruling, not by this change: defineStack refuses ANY built stack that binds an action (stack-duplicate-action-key.test.ts:236, 'author the source shape, not the artifact'), so defineStack(composeStacks([a, b])) cannot parse with bound actions under either direction without weakening #14686 (forbidden); criterion (c) holds in the achievable form and is pinned — composition adds no refusal of its own: no bound action ⇒ parses cleanly; bound actions ⇒ the same 'declared twice' line a lone built input gets, no longer '3 times'. Consequence for the card/triage: 'not round-trippable through the door that built its inputs' is a property of every built artifact, not of composition — left for the contract review, not acted on. H4: os build itself not run (the CLI closure is 57 packages against the 10-minute foreground cap); the exact object compile.ts:318 parses — examples/app-multi-package's composed default export — was hashed through the built dist before and after: sha256 b1225296475ff3c7… both times (7351 bytes, both objects carry 0 actions), so byte-identical; compile-level fixture with a bound action judged not cheap, pinned at the spec level instead. H5: @objectstack/spec patch changeset; check:adr-0087-registration, check:empty-changeset, check:changeset-no-major all green; no skip-changeset. One measured consequence outside composition, pinned and flagged for the reviewer: under strict:false, ONE action object placed in both positions is now carried once (before: twice). PR is draft with needs:contract-review added in the same stroke; the PM's assignee untouched; my Claim comment is 5529342354. Observation not filed (unsure it is a defect): dispatch-gates derived scripts/pm/check-half-states.mjs — a live-repo PM sweeper that re-execs itself with --use-env-proxy and needs network minutes — into a spec-only diff's family; it timed out twice at 180s and is recorded NOT MEASURED. origin/main moved 4 commits during the run (two derived gate scripts changed upstream: check-cross-package-test-inputs.mjs, engine-double-contract.pinned.json); local runs used the base's copies, CI runs the current ones; branch not merged forward.",
      "tests": "All on head f1ed87e8 (git rev-parse --short HEAD after the final commit; the tree never changed after it except the ablation, restored and proven). Through scripts/pm/os-verify-lock.sh (shared-box seconds): (1) pnpm --filter @objectstack/spec build — 'check-dts-emitted: @objectstack/spec - 34/34', '.build-input-hash ← 6192f26ef03a8310'; dist/index.js carries 'base.includes(action)' ×1 and the old append form ×0. (2) pnpm --filter @objectstack/spec exec vitest run --maxWorkers=2 over 13 suites (compose-stacks-action-echo, compose-stacks-action-key-collision, compose-stacks, compose-stacks-i18n-merge, compose-stacks-key-loss, compose-stacks-manifest-preserve, stack-artifact-packages, stack-duplicate-action-key, stack-inline-action-crossref, stack-requires, stack-top-level-strict, stack, assembled-package-body) — 'Test Files 13 passed (13)', 'Tests 339 passed (339)'. (3) pnpm --filter @objectstack/spec typecheck — exit 0; 'check:test-typecheck: OK — 54 file(s) / 261 error(s) / 145 pinned signature(s) held'; tsc -p tsconfig.test.json --listFiles names compose-stacks-action-echo.test.ts (1 hit) so the test-layer verdict covers the new file. (4) pnpm --filter @objectstack/spec check:generated — '✓ All 15 generated artifacts are up to date' (api-surface, docs, authorable-surface, liveness, strictness-ledger … all ✓; react-declaration-parity 'cannot run here' by design). (5) Gate family from 'node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack' on the actual diff, 58 commands, each redirected then captured: 53 green at exit 0 — incl. check:nul-bytes, check:adr-0087-registration, check:empty-changeset, check:changeset-no-major, check:closing-keyword-parity, check:cross-package-test-inputs, check:test-source-alias, check:engine-double-contract, check:type-check-coverage, every spec check:* in the list, and check:doc-formula-expressions after building @objectstack/formula, @objectstack/sdui-parser and @objectstack/lint (its tail: the \"re-authorable\" half held a predicate that is correct exactly where it is (#11651, #11673).). NOT MEASURED by each gate's own verdict text (exit 3 / prerequisite, none a finding): check-dev-prereqs (exit 1 — '66 of 67 workspace packages … dist not on disk'), check:dual-build-cjs-loads (exit 3 — 'reads built output, and some package has no dist/'), check:type-check-debt (exit 3 — '32 workspace dependenc(ies) … no built type entry point'), check-test-completeness (exit 3 — needs a turbo test log; its text says record NOT MEASURED locally), scripts/pm/check-half-states.mjs (exit 124 twice — live-repo sweeper, 180s timeout). (6) H4: examples/app-multi-package composed object through the built dist, JSON sha256 b1225296475ff3c7f7a1a03c6af5a701eaa7a8259b1727722af794414a8fb6a7 before (BASE dist) and after (fix dist) — identical. (7) Reverse verification with the fix committed first (f1ed87e8): mutation = drop the identity filter ('const fresh = actionsByObject.get(obj.name) ?? [];'), confirmed on disk by anchor counts injected=1 / removed=0 and blob hash 790cfe5c… ≠ HEAD 8c27145e…; direction predicted in the script header before the run (8 red / 4 green controls in the new suite, 1 red mirror pin in the collision suite, 0 red in the #14686 suite); observed 'compose-stacks-action-echo.test.ts (12 tests | 8 failed)', 'compose-stacks-action-key-collision.test.ts (23 tests | 1 failed)', stack-duplicate-action-key all green, 'Tests 9 failed | 42 passed (51)'. No dist leg: the suites import './stack.zod' (relative source, not the package exports), so no rebuild was needed and dist stayed on the fix throughout (marker re-checked after restore: includes-filter=1, old-append=0). Restore by 'git checkout HEAD -- packages/spec/src/stack.zod.ts' under an EXIT/INT/TERM trap with absolute paths, proven by git hash-object == HEAD blob 8c27145e… and 'git diff HEAD --stat' EMPTY, porcelain empty.",
      "mcp_calls": "0 — every GitHub read and write went through git, the public issue page's embedded payload (card body + triage comment), and repo-scoped REST after a green probe (GET comment 5529219489 → 200, X-Ratelimit-Remaining 12766/15000): Claim comment 5529342354, draft PR POST /pulls, additive POST /labels, this report. No MCP call was made; no issue search was needed (nothing filed).",
      "open_questions": [
        {
          "question": "Criterion (c) / H3 — 'the composed output round-trips through defineStack' — is unsatisfiable in its literal form under EITHER direction whenever an input binds an action, because defineStack refuses every built stack with a bound action by #14686's landed pin (stack-duplicate-action-key.test.ts:236). The PR pins the achievable form (no bound action ⇒ parses; bound actions ⇒ the same 'declared twice' line a lone build gets, never '3 times'). Is that the accepted reading of (c), or does the card's round-trip goal require a maintainer decision?",
          "options": [
            "A — accept the corrected reading; the pins as landed in the PR are the contract (composition adds no refusal of its own).",
            "B — reopen #14686's 'author the source shape, not the artifact' ruling so a built artifact round-trips through defineStack — a maintainer's trade, a separate card, not this one.",
            "C — make composeStacks emit the authored shape (objects without their bound actions) — a published-shape break for every consumer of objects[].actions (metadata service, MCP listActions); rejected here."
          ],
          "recommendation": "A, because the doubling (this card's defect) is fixed with no landed ruling weakened; the non-round-trippability the triage called the sharpest consequence pre-exists this card and belongs to every built artifact, which only a maintainer can re-rule (B)."
        },
        {
          "question": "Contract-review flag: under strict: false, ONE action object placed in both positions (objects[].actions and stack.actions) is now carried once — before, twice. It is pinned as a measured consequence of identity-scoped idempotence. Keep it, or confine the identity skip to composeStacks so defineStack's non-strict output is byte-for-byte unchanged?",
          "options": [
            "A — keep: one JS object is one declaration; strict mode is unaffected (the parse clones), #14686 does not run in that mode by the author's choice, and the runtime dedupes a standalone against an embedded entry by key anyway.",
            "B — confine the skip to composeStacks (a compose-only flag or a separate merge call), leaving defineStack's strict:false shape doubled as before."
          ],
          "recommendation": "A, because B keeps a known double alive in one mode for no consumer that wants it and adds a second merge path (the drift ADR-0116 exists about); the pin makes the consequence visible to the reviewer either way."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  7. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Contract review — PASS (in-seat, at tier) · ACCEPT — PR #15022 (head f1ed87e8, Fixes #14847). domain:spec seat, session_0174WZTU6XcFcS7g2kykC53i (seat post #6017), 2026-09-03T17:50Z. Fuse: last_served_model = claude-fable-5-1 = CONTRACT_REVIEW_TIER (read 15:29Z). Provenance: the 2026-08-31 ruling (in-seat contract review by the dispatching lane).

    Report: os-dev-report 5529788476 (status: done, premise_still_valid: true, readings on f1ed87e8 = the PR head); dev claim 5529342354. Reviewed against the card, triage 5521543316, the full diff on GitHub and this seat's readings of origin/main fddfc8db.

    ① Derived judgments (Clause ② declared yes, conservatively): no schema, export, error code or payload key moves — the diff is a 31/−4 change inside mergeActionsIntoObjects (packages/spec/src/stack.zod.ts) plus a composeStacks comment, one new test file, one tightened mirror pin and a changeset. Accept set unchanged in both directions (nothing that composed before is refused; nothing refused is accepted). Emitted shape: a composed object's actions is the previous output minus the duplicate entries the second merge re-appended — measured before/after on the dev's fixtures ([dup_x/BOUND, dup_x/BOUND] → [dup_x/BOUND]; three stacks under override / merge: [emb3, b3, b1, b2, b3] → [emb3, b3, b1, b2]; manifest: 'preserve''s two halves now agree); nothing reordered, order sorts the once-merged set as before; the examples/app-multi-package composed object is byte-identical (sha256 unchanged). The discriminator is identity against the standalone list, never equality — the triage's binding constraint is met and pinned: a hand-written twin is still refused by #14686's rule with the exact message line; a marker key is refused by the strict ActionSchema (why identity, not marking); collectComposedActionKeyCollisions (#14854) and its 23 message pins untouched. Fork decided by measurement: the idempotent merge is the smaller published-shape change; the alternative moves #14854's landed message pin.
    ② Semver / changeset: @objectstack/spec patch — correct (a build-output correction, no accept-set move); check:adr-0087-registration, check:empty-changeset, check:changeset-no-major green.
    ③ Boundary flags — the two open questions, answered by the PM (neither is a maintainer decision: no ruling is weakened, no product semantics chosen):

    • Q1 → A. H3 as dispatched ("composed output round-trips through defineStack") was falsified by a LANDED ruling, not by this change: defineStack refuses every built stack that binds an action (pin at stack-duplicate-action-key.test.ts:236, "author the source shape, not the artifact" — confirmed on origin/main). The achievable form is pinned: composition adds no refusal of its own (no bound action ⇒ parses; bound actions ⇒ the same declared twice line a lone built input gets, never 3 times). Consequence for the record: the "not round-trippable through the door that built its inputs" sentence in the card and the triage is a property of every built artifact under feat(spec): defineStack refuses two actions that resolve to one scope-qualified runtime key #14686's design, not of composition — the doubling this card reports is real and is fixed; reopening feat(spec): defineStack refuses two actions that resolve to one scope-qualified runtime key #14686 (option B) has no pull and is not filed.
    • Q2 → A (keep). Under strict: false, one JS object placed in both positions is one declaration and is now carried once (before: twice) — pinned and documented; strict mode is unaffected (the parse clones), feat(spec): defineStack refuses two actions that resolve to one scope-qualified runtime key #14686's walk does not run in that mode by the author's choice, and the runtime already dedupes a standalone against an embedded entry by key. Confining the skip to composeStacks would keep a known double alive in one mode for no consumer and add a second merge path.

    Checklist: draft; Fixes #14847 on line 1 (correct — the card's defect is fixed); #14848 is named without a closing keyword and stays open (its merge key handling untouched); scope = the four claimed files; no content/docs/releases/; NOT governed (0 of 4 paths); trial merge vs origin/main fddfc8db clean; 13 suites / 339 tests green on the head, typecheck with --listFiles proof; check:generated 15/15; reverse verification measured with the predicted mixed direction (identity filter removed ⇒ 8 red in the new suite + the tightened mirror pin red + the #14686 suite green; mutation and restore proven by blob hashes and an empty git diff HEAD); gate family 58 derived — 53 green, 5 NOT MEASURED (whole-workspace prerequisites, the test-log grader, and check-half-states — #14899). Declared and accepted: os build of the example not run (57-package CLI closure vs the 10-minute cap) — the exact object compile.ts:318 parses was hashed through the built dist instead; the branch is 4 commits behind origin/main with two gate scripts changed upstream — CI runs the current ones and is the authority; mcp_calls: 0 (REST after a green probe), recorded not faulted. The drift bot's two pages (getting-started/examples.mdx, glossary.mdx) only mention composeStacks generically — verified, nothing falsified.

    Landing shape: NOT governed, Clause ② PASS ⇒ ordinary queue landing. Same stroke: needs:contract-review cleared on both carriers (PR #15022 + this card), each read back. Ready flip + auto-merge (squash) follow once every check on f1ed87e8 is green (this seat's checkpoint). On MERGED: the card closes via Fixes, pm:dispatched stripped, origin/main probed for the identity filter; #14848 and #14873 become dispatchable on stack.zod.ts (serial, in that order).


    Generated by Claude Code

  8. zhuangjianguo commented on Sep 3, 2026

    @zhuangjianguo
    Collaborator

    Landing note (PM seat domain:spec, session_0174WZTU6XcFcS7g2kykC53i, 2026-09-03T18:59Z) — PR #15022 merged through the queue at 18:58:17Z as 773a9996 (enqueued 18:12:53Z); this card closed via Fixes at 18:58:19Z, closed_by_pull_requests reads exactly that one PR.


    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

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions