Skip to content

feat(bus): B-0444 — add worktree field to claim envelope#3043

Merged
AceHack merged 4 commits into
mainfrom
feat/b0444-impl-worktree-2026-05-13
May 13, 2026
Merged

feat(bus): B-0444 — add worktree field to claim envelope#3043
AceHack merged 4 commits into
mainfrom
feat/b0444-impl-worktree-2026-05-13

Conversation

@AceHack
Copy link
Copy Markdown
Member

@AceHack AceHack commented May 13, 2026

Summary

Implements B-0444 — adds worktree?: string to ClaimPayload so the claim envelope captures the worktree path the claim was acquired from. Closes Otto-Desktop's identified follow-up gap filed in #3038.

  • Schema: ClaimPayload.worktree? + ClaimRecord.worktree? (both optional → back-compat)
  • CLI: acquire --worktree <path> flag; defaults to process.cwd()
  • Output: check text + check --json + acquire --json all surface the field

Design decision (substrate-honest)

Surface-tagged sender IDs (#3037) arbitrate split-brain at the coordination layer (otto-cli vs otto-desktop are distinct claims on the same item). worktree is observability metadata, not a coordination key — same sender re-acquiring from a different worktree remains idempotent. Pinned by the test same sender re-acquiring from a different worktree is idempotent (existing behavior preserved).

Test plan

  • 14 new tests in tools/bus/claim.test.ts — default cwd, explicit --worktree, JSON shape, combined with --branch, surface-IDs-still-arbitrate, same-sender different-worktree idempotency, claim-record shape
  • bun test tools/bus/claim.test.ts → 52/52 pass
  • Built from isolated worktree at /tmp/zeta-b0444 to avoid multi-Otto split-brain (the very class of problem this row addresses)

Composes with

🤖 Generated with Claude Code

Implements Otto-Desktop's identified follow-up gap (B-0444 row filed in
PR #3038): the claim envelope schema captured sender ID, item, and
branch but not the worktree path. With multi-foreground-surface agents
(Otto-CLI + Otto-Desktop on the same machine), the worktree is the
per-process operational coordinate — useful as an observability signal
even when sender-ID surface tags (PR #3037) already prevent split-brain
across surfaces.

Changes:

- `ClaimPayload.worktree?: string` added to the protocol schema in
  tools/bus/types.ts (back-compat: optional; envelopes published
  before this change continue to work)
- `ClaimRecord.worktree?: string` added so consumers of activeClaims /
  allActiveClaims see the field
- `tools/bus/claim.ts acquire` accepts `--worktree <path>`; defaults to
  `process.cwd()` so every new claim publishes a useful worktree value
  with no caller change required
- `check` text output appends `[worktree: <path>]` after the branch info
- `check --json` and `acquire --json` include the field on the record /
  response

Design decision recorded substrate-honestly in the B-0444 row:
cross-worktree same-sender is treated as idempotent re-acquire
(existing behavior preserved). Surface-tagged sender IDs (PR #3037)
handle split-brain at the coordination layer; worktree is
observability metadata, not a coordination key. A test pins this
behavior: "same sender re-acquiring from a different worktree is
idempotent (existing behavior preserved)".

Tests: 14 new tests in `tools/bus/claim.test.ts`
(default cwd, explicit worktree surfaces in check + --json output,
combined with --branch, claim-record shape, surface IDs still arbitrate
across worktrees, same-sender different-worktree idempotency). Full
suite: 52/52 pass.

Composes with:
- B-0400 (bus protocol root) — extends the existing schema
- B-0400 slice 3 / PR #2939 (claim-coordinator implementation)
- PR #3037 (SENDER_IDS schema extension — sibling surface-level fix)
- `.claude/rules/claim-acquire-before-worktree-work.md` — the rule
  this row's gap was identified against
- PR #3038 (the docs(backlog) commit that filed this row)

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 13, 2026 22:21
@AceHack AceHack enabled auto-merge (squash) May 13, 2026 22:21
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an optional worktree field to bus claim envelopes (B-0444) so claims carry the per-process operational coordinate (which checkout produced them) for observability. Coordination/arbitration remains driven by surface-tagged sender IDs (PR #3037); worktree is metadata only, optional for back-compat.

Changes:

  • Schema: ClaimPayload.worktree? and ClaimRecord.worktree? added as optional fields.
  • CLI: acquire --worktree <path> flag (defaults to process.cwd()); surfaced in text and JSON output of check/acquire.
  • Tests: 7 new tests covering default cwd, explicit path, JSON shape, branch+worktree combo, surface-ID arbitration still wins, same-sender different-worktree idempotency.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
tools/bus/types.ts Adds optional worktree?: string to ClaimPayload with doc comment.
tools/bus/claim.ts Adds worktree to ClaimRecord, propagates through activeClaims/allActiveClaims, adds --worktree CLI flag with process.cwd() default, surfaces in text/JSON outputs.
tools/bus/claim.test.ts Adds a 7-test describe block covering the new field, including back-compat (same-sender idempotency) and surface-ID arbitration.

…ee gambit

Per autonomous-loop discipline: substrate-or-it-didn't-happen. Tick shard
records the three multi-Otto coordination events observed during this
tick (parallel commits, branch-switches under active editing, stash-don't-
discard from sibling Otto), the recovery via `git worktree add /tmp/zeta-
b0444`, and the substrate-honest evidence that B-0444 itself addresses
exactly the class of split-brain this tick experienced.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5f3e0473f

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/bus/claim.ts Outdated
`parseArgs` encodes a bare `--worktree` (flag with no value following)
as the literal string `"true"`. Without an explicit check, that string
would flow through `flags.worktree ?? process.cwd()` and be recorded as
the worktree path, making claim provenance misleading. Reject fast with
a clear error so the user notices the mistyped invocation.

New test pins the behavior:
`bare --worktree (no value) is rejected with a clear error`.

Suite: 53/53 pass.

Co-Authored-By: Claude <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 13, 2026 22:29
AceHack added a commit that referenced this pull request May 13, 2026
Tick records: 6-file recovery from c0dcb26 (B-0257..B-0261 + B-0289)
clearing 9 → 3 dangling-dep refs, Codex P2 bare-flag rejection landed
on PR #3043 (`7740700`), thread `PRRT_kwDOSF9kNM6B6CLj` resolved.
Both PRs auto-merge armed; real-dependency-wait on required CI.

Co-Authored-By: Claude <noreply@anthropic.com>
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 774070094b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread tools/bus/claim.ts Outdated
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comments suppressed due to low confidence (2)

tools/bus/claim.ts:277

  • The bare---worktree rejection runs before the --from/--item presence check, so a user typo like acquire --worktree (omitting required flags entirely) reports "--worktree requires a path argument" instead of the more informative "--from and --item are required". Consider moving the worktree validation after the required-flag checks for a more useful error ordering.
      if (flags.worktree === "true") {
        console.error("Error: --worktree requires a path argument");
        process.exit(1);
      }
      const worktree = flags.worktree ?? process.cwd();
      if (!from || !itemId) {
        console.error("Error: --from and --item are required");
        process.exit(1);
      }

tools/bus/claim.ts:324

  • Since worktree is unconditionally assigned to flags.worktree ?? process.cwd() on line 273, it is always a non-empty string. The conditional spread ...(worktree ? { worktree } : {}) on line 300 and the ternary worktree ? ... : "" on line 323 can never take their false branch. This is confusing — either the default should be removed (making worktree truly optional in the payload) or the truthiness guards should be dropped. As written, the type signature suggests optionality the runtime never actually exercises.
            payload: {
              action: "claim",
              itemId,
              ...(branch ? { branch } : {}),
              ...(worktree ? { worktree } : {}),
            },
          });
          return { acquired: true, claimedByOthers: [] as string[], messageId: env.id };
        }));
      } catch (err) {
        console.error(`Error: ${(err as Error).message}`);
        process.exit(1);
      }

      if (!acquired) {
        if (asJson) {
          console.log(JSON.stringify({ itemId, acquired: false, claimedBy: claimedByOthers }));
        } else {
          console.error(`${itemId}: already claimed by ${claimedByOthers.join(", ")} — not acquiring`);
        }
        process.exit(1);
      }

      if (asJson) {
        console.log(JSON.stringify({ itemId, acquired: true, messageId, worktree }));
      } else {
        const branchStr = branch ? ` (${branch})` : "";
        const worktreeStr = worktree ? ` [worktree: ${worktree}]` : "";
        console.log(`${itemId}: claimed by ${sender}${branchStr}${worktreeStr} — ${messageId}`);

Comment thread tools/bus/claim.ts Outdated
Two round-2 threads on PR #3043 reshape the --worktree handling:

1. Codex P2 — "Accept explicit `--worktree true` values"

   Round-1 fix rejected `flags.worktree === "true"` unconditionally,
   which also rejected a legitimate `acquire ... --worktree true` call
   where `"true"` is the intended literal path. parseArgs is refactored
   to distinguish a bare flag (boolean sentinel `true`) from an explicit
   string value, so the bare-flag rejection now triggers on the boolean
   while explicit `--worktree true` flows through as a valid string
   path.

2. Copilot — "Defaulting worktree to process.cwd() can be misleading"

   Auto-defaulting to cwd records a plausible-looking coordinate when
   the caller is `cd`'d to an unrelated directory (CI step, wrapper
   script, cron context). Drop the cwd default: `--worktree <path>` to
   set, omit to leave the field absent. The presence of the field now
   reliably indicates the caller opted in.

Mechanical changes:

- parseArgs: return `Record<string, string | true>` instead of
  `Record<string, string>`; bare flags get boolean `true`.
- New helpers `asString(v)` and `isSet(v)` to read flags cleanly.
- Bare-flag check uses `flags.worktree === true` (the boolean).
- `worktree` is `undefined` when --worktree omitted; published payload
  + JSON output omit the field rather than defaulting to cwd.
- `asJson` switches from `=== "true"` to `isSet(flags.json)` so bare
  `--json` continues to work as before.

Tests (54/54 pass):

- Updated: "acquire omits worktree from payload + JSON when --worktree
  not specified (Copilot review)" — pinned absent-not-cwd behavior.
- New: "explicit --worktree true (literal string) is accepted as a
  valid path" — pinned that round-1 false-rejection is fixed.
- Existing 51 tests unchanged.

Co-Authored-By: Claude <noreply@anthropic.com>
@AceHack AceHack merged commit 5db892d into main May 13, 2026
25 checks passed
@AceHack AceHack deleted the feat/b0444-impl-worktree-2026-05-13 branch May 13, 2026 22:40
AceHack added a commit that referenced this pull request May 13, 2026
…c0dcb26 (#3044)

* recover(backlog): restore 6 lost B-0066/B-0246 decomposition rows from c0dcb26

Six P1 backlog rows were drafted in commit c0dcb26 (2026-05-08,
"fix(b0066): initial marker vs index audit") on an unmerged branch and
never reached main. As a result, B-0066 and B-0290 carried dangling
depends_on / children references for 5 days, the backlog-ready-notifier
surfaced 9 dangling-dep warnings, and the prior decomposition work was
silently lost from the substrate.

Recovers per `.claude/rules/honor-those-that-came-before.md` and the
canonical lost-files protocol in `tools/hygiene/LOST-FILES-LOCATIONS.md`:
files restored byte-for-byte from `c0dcb26`'s tree (via
`git show c0dcb26:<path>`), no editorial drift.

Files restored under docs/backlog/P1/:

- B-0257 — MEMORY.md harness contract verification and evidence
- B-0258 — MEMORY.md index generator implementation
- B-0259 — MEMORY.md hook + CI drift enforcement
- B-0260 — MEMORY.md cutover + parity validation
- B-0261 — MEMORY.md Q1 AutoDream/AutoMemory compatibility validation
- B-0289 — Green Lantern ring hardware spec + local inference requirements

B-0257..B-0261 are the atomic-decomposition children of B-0066 (MEMORY.md
marker-vs-index harness verify, Aaron 2026-04-28). B-0289 is a child of
B-0246 (Green Lantern ring IoT device genesis seed) and is the upstream
spec that B-0290 (consent gate firmware) was blocked-on.

Backlog notifier result:

- dangling dep refs: 9 → 3 (B-0257..B-0261, B-0289 cleared; the remaining
  B-0055.1, B-0054.1, B-0395 are unrelated and out of scope for this
  recovery)
- ready-to-grind candidates: 211 → 213 (B-0289 newly buildable-now;
  B-0066 + B-0290 still parented so not directly ready, but children
  now linkable)
- totalOpenRows: 376 → 382 (6 newly-visible rows)

BACKLOG.md regenerated via `BACKLOG_WRITE_FORCE=1 bun tools/backlog/
generate-index.ts`; the 6 new entries appear in the P1 in-flight
section under "Open work" between B-0256 and B-0262, and between
B-0288 and B-0290.

Worktree-isolated build (`/tmp/zeta-recover-lost`) to dodge the
multi-Otto split-brain pattern documented in PR #3043's tick shard.

Co-Authored-By: Claude <noreply@anthropic.com>

* shard(tick): 2228Z — lost-file recovery + Codex P2 fix on #3043

Tick records: 6-file recovery from c0dcb26 (B-0257..B-0261 + B-0289)
clearing 9 → 3 dangling-dep refs, Codex P2 bare-flag rejection landed
on PR #3043 (`7740700`), thread `PRRT_kwDOSF9kNM6B6CLj` resolved.
Both PRs auto-merge armed; real-dependency-wait on required CI.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(shard): MD032 blanks-around-lists in 2228Z tick shard

markdownlint flagged the Visibility-signal list as missing a blank
line between the introductory sentence and the first list item.
One-line fix.

Co-Authored-By: Claude <noreply@anthropic.com>

* fix(backlog): add missing last_updated field to recovered B-0289 (Copilot P1)

Copilot P1 on PR #3044: `tools/backlog/README.md` requires every per-row
file to carry `last_updated`. The byte-for-byte recovery from c0dcb26
preserved the original frontmatter which (in B-0289 alone of the six)
omitted the field; the other five carried it.

Set to today's date (2026-05-13) so the field reflects when the row
re-entered active substrate, not the original draft date.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
Records: PRs #3043, #3044, #3045 all merged via auto-merge since
previous tick (B-0444 worktree field; lost-row recovery; notifier
YAML-comment fix). PR #3047 opened this tick to close the final 2
dangling-dep refs (B-0054.1 + B-0055.1 slice rows). Session-arc
substrate-hygiene sweep: 9 → 2 dangling refs on main now, → 0
once #3047 merges.

Co-Authored-By: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
* fix(backlog): restore missing slice rows B-0054.1 + B-0055.1

Two atomic decomposition slices (B-0054.1 — media-catalog schema
foundation; B-0055.1 — edge-claims catalog) landed earlier as code
without corresponding row files. Nine sibling rows reference these
slice IDs via `depends_on`, and the backlog-ready-notifier surfaced
them as dangling-dep warnings.

Both rows added as `status: closed` (work landed) with bodies
documenting:

- the implementation file path (the canonical artifact)
- retroactive acceptance criteria (all satisfied)
- substrate-honest note that future slices should land row-first

Backlog-notifier effect (against origin/main with PR #3045 merged):

  dangling dep refs: 8 → 6  (the remaining 6 clear once PR #3044 merges)
  ready-to-grind candidates: 211 → 217  (sibling rows unblocked)

Completes the substrate-hygiene sweep:
- PR #3044 recovers B-0257..B-0261 + B-0289 (file-restored from
  unmerged commit)
- PR #3045 fixes notifier YAML inline-comment parsing
- This PR  formalizes the two slice IDs that never had row files

Once all three land, dangling-dep count goes 9 → 0.

Co-Authored-By: Claude <noreply@anthropic.com>

* shard(tick): 2241Z — slice-row restoration + 3 PRs landed on main

Records: PRs #3043, #3044, #3045 all merged via auto-merge since
previous tick (B-0444 worktree field; lost-row recovery; notifier
YAML-comment fix). PR #3047 opened this tick to close the final 2
dangling-dep refs (B-0054.1 + B-0055.1 slice rows). Session-arc
substrate-hygiene sweep: 9 → 2 dangling refs on main now, → 0
once #3047 merges.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…ness cleared

Records: PR #3047 merged. Verified dangling-dep refs on main went 9 → 0
(the notifier output's `(warning: N dangling dep ref(s))` suffix is now
gone entirely). Speculative pickup audited `.claude/rules/` for adjacent
stale callouts; found `otto-channels-reference-card.md:82` listing B-0444
as a "follow-up gap" when PR #3043 shipped it. PR #3050 replaces the
framing with the merged-PR reference.

Co-Authored-By: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…ap" framing (#3050)

* docs(rules): otto-channels card — B-0444 shipped, retire "follow-up gap" framing

The Otto inter-surface communication channels reference card listed
B-0444 (bus claim envelope worktree field) as a "follow-up gap" in
its Composes-with section. PR #3043 shipped that feature 2026-05-13
(merged as 5db892d). Updated the entry to reference the merged PR
rather than the row's open-state framing.

Sibling staleness cleanup to PR #3048 (Rule 0 legacy-violations
update); together they close the rule-callout staleness surfaced
by this session's substrate-hygiene sweep.

Co-Authored-By: Claude <noreply@anthropic.com>

* shard(tick): 2257Z — dangling refs 9 → 0 on main; otto-channels staleness cleared

Records: PR #3047 merged. Verified dangling-dep refs on main went 9 → 0
(the notifier output's `(warning: N dangling dep ref(s))` suffix is now
gone entirely). Speculative pickup audited `.claude/rules/` for adjacent
stale callouts; found `otto-channels-reference-card.md:82` listing B-0444
as a "follow-up gap" when PR #3043 shipped it. PR #3050 replaces the
framing with the merged-PR reference.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…ope row closed

Records: PR #3050 merged; audit of in-flight rows surfaced TWO rows
both with `id: B-0444` (one shipped via PR #3043 still open;
one duplicate from PM-2 gap-prediction). PR #3053 keeps B-0444 on
the shipped+referenced row, renumbers the duplicate to B-0450,
closes the shipped row.

Co-Authored-By: Claude <noreply@anthropic.com>
AceHack added a commit that referenced this pull request May 13, 2026
…→ B-0450 (#3053)

* fix(backlog): resolve B-0444 ID collision — renumber getting-started → B-0450; close shipped bus-envelope row

Two rows on main both claimed `id: B-0444`:

1. `docs/backlog/P1/B-0444-getting-started-guide-...md` — filed first
   (commit b6e419d 17:23 via PR #3033, B-0271 PM-2 gap-prediction pass)
2. `docs/backlog/P2/B-0444-bus-claim-envelope-...md` — filed 25 min
   later (commit bbb984d 17:48 via PR #3038, Otto-Desktop's identified
   follow-up gap) AND already shipped via PR #3043

Per "first-merged-wins" the getting-started row had the ID first
(PR #3033 merged at 17:34, before the bus-envelope row was even
committed). But: the bus-envelope row was shipped (PR #3043), so
moving its ID would break the link from PR #3043 → backlog row + the
ID is already referenced by B-0445 and B-0448's `composes_with`.

Resolution: keep B-0444 on the shipped+referenced bus-envelope row;
renumber the getting-started row to the next available ID (B-0450 —
B-0449 was taken by Otto-Desktop's bg-services slice 5).

Changes:

- `docs/backlog/P1/B-0444-getting-started-guide-...md`
  → `docs/backlog/P1/B-0450-getting-started-guide-...md`
  (file rename via `git mv` preserves history); `id` field updated;
  body title updated; `renumbered_from: B-0444` + reason recorded
  for substrate-honest provenance.
- `docs/backlog/P1/B-0271-pm2-first-research-pass-...md`: updated
  Gap-1 reference from B-0444 to B-0450 with renumber note.
- `docs/backlog/P2/B-0444-bus-claim-envelope-...md`: status open
  → closed; `closed_at: 2026-05-13` + `closed_by_pr: 3043` recorded
  (this row's substrate work shipped already; the row was just
  never marked closed).
- `docs/BACKLOG.md`: regenerated via `BACKLOG_WRITE_FORCE=1
  bun tools/backlog/generate-index.ts` to reflect the renumber +
  status change.

Verified post-fix:

- `bun tools/bg/backlog-ready-notifier.ts --once` still reports 0
  dangling-dep refs (no warning suffix).
- No grep for `B-0444` in `docs/backlog/` returns the now-renumbered
  row's old ID; the bus-envelope row's references survive intact.

Co-Authored-By: Claude <noreply@anthropic.com>

* shard(tick): 2306Z — B-0444 ID collision resolved + shipped bus-envelope row closed

Records: PR #3050 merged; audit of in-flight rows surfaced TWO rows
both with `id: B-0444` (one shipped via PR #3043 still open;
one duplicate from PM-2 gap-prediction). PR #3053 keeps B-0444 on
the shipped+referenced row, renumbers the duplicate to B-0450,
closes the shipped row.

Co-Authored-By: Claude <noreply@anthropic.com>

---------

Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants