Skip to content

fix(native): reconcile edited graphs that have a dependency cache - #637

Closed
roodboi wants to merge 3 commits into
nextfrom
claude/hack-1937-cache-graph-reconcile
Closed

roodboi wants to merge 3 commits into
nextfrom
claude/hack-1937-cache-graph-reconcile

Conversation

@roodboi

@roodboi roodboi commented Nov 7, 2026 •

Copy link
Copy Markdown
Contributor

Problem

An env-only Compose edit stranded a retained native run whose graph has a hack.dependencies.* cache. After up and Ctrl-C, changing one environment value on the app made up (and restart) refuse with graph_hostname_change, and only reverting the edit worked. Compose and v4 apply this edit on the next up.

Cause

Plan reconciliation (#614) treated every graph with a dependency cache as ineligible, so the edit fell through to the narrower hostname-change proof. That proof found no hostname change and refused with its own code, which named the wrong field. Even if reconciliation had run, the cache identity bound the whole Compose digest, so any edit anywhere would have selected a new, empty cache.

Final behavior

  • Cache graphs reconcile. plan_reconcile now selects graphs with dependency caches. A cache whose freshly resolved identity equals the recorded binding is kept, with its provenance fence. A changed identity selects that identity's shared cache exactly as a fresh admission does: it is adopted by labels or created, and the initializer installs into it, from a freshly published source snapshot (source-compatibility reports reconcile: true with no retained revision, and the CLI publishes the current snapshot). Only this run's reference to the old shared cache is released. Run-owned data keeps the existing reattach rules.
  • The cache identity covers only the cache's own inputs. It binds the initializer's reviewed service definition, its resolved command, entrypoint, environment and user, the cache declaration, image, lockfile and runtime-file contents, and the full mount layout of the cache volume: every service that mounts it, with its target, subpath and read-only flag. Any of those services can write the shared contents, so adding, removing or changing such a mount selects a fresh cache. Mounts stay as declared, as in Compose and v4, and are not forced read-only. The identity no longer binds the whole Compose digest, so any other edit to another service, such as an environment value, keeps the cache, as Compose keeps a named volume.
  • Sharing scope. A cache is shared across linked worktrees and branch instances only while every non-initializer consumer mounts it read-only. If any other service mounts it read-write, that writer can change the contents through inputs the identity does not hash (its image, command, environment or source), so the cache binds the plan namespace into its scope and is used only by this checkout and branch instance. Such graphs always re-prove the cache through reconciliation. Inside one instance, env-only edits still keep the cache.
  • Upgrade compatibility. A receipt recorded under the earlier whole-document identity keeps its exact cache while the review still resolves to it (config::retain_recorded_legacy_caches, used by strict replay, restart and reconciliation). It moves to the new identity once, on the first edit, instead of refusing. A receipt whose cache has a writable consumer never matches an earlier shared identity; it moves to its instance-scoped cache once.
  • Accurate refusals.
    • Run-owned data newly declared as a cache refuses with graph_plan_reconcile and names the service and the hack.dependencies.cache-volume field.
    • Graphs that reconciliation still does not select (live source, a carried cache scope, a pending source-device rebind or initializer cache release) refuse a non-hostname edit with graph_plan_change_unsupported instead of graph_hostname_change.
    • A hostname change mixed with other edits keeps graph_hostname_change and now names the routed service.
    • Errors carry a value-free, versioned plan_change diagnostic (service identifier plus a closed field). The CLI validates it and prints closed guidance: revert the edit, or run hack down --remove-data and then hack up. Native messages are still never printed.
  • A reserved dependency cache in a stopped receipt may already exist as another graph's shared cache. Restore selection, the generation fence and the CLI's retained-graph check accept either state for it, and still require a reserved run-owned volume to be absent.

Validation

  • Rust focused tests (plan_reconcile, dependency_cache, hostname_change): 33 pass at 592a9b1d, and the full lib suite passes 1,200/1,200 from a real-path target. Mutations that drop the layout or the instance scope from the identity fail their tests. New cases cover a writable consumer in worktree A not sharing with worktree B, a read-only consumer still sharing, an env edit inside one instance keeping the cache, and an earlier shared receipt with a writer moving to its instance cache. New cases cover the synthetic repro (app env edit keeps the cache and its provenance; the reconciled receipt still loads), initializer and lockfile changes selecting a fresh cache while keeping run data, a new read-write (or read-only) mount of the cache by another service changing the identity while an env edit to that service keeps it, reconciliation selecting a fresh cache when a writer service is added, run data refused as a cache (pure check and projection), dropping a cache declaration, a legacy identity kept on an unchanged review and replaced on edit, reserved shared caches allowed to exist, and the hostname refusal codes.
  • Full cargo +1.97.1 test: 1,352 pass. The remaining failures are environment-only: node_contract failed with SUN_LEN on the long checkout path and passes 20/20 from a short path. explicit_root_binding_requires_exact_executable failed through the /tmp symlink and passes from its real path. The loopback port-probe test failed twice during an earlier full run; on a recheck it passed 3/3 on clean origin/next and 3/3 on this branch (short-path checkouts, cargo +1.97.1), so it is transient and not caused by this PR.
  • cargo fmt --check and cargo clippy --all-targets -D warnings are clean.
  • TS focused tests (bounded runner, 4 files): 59 pass, including new cases for the cached-reconcile compatibility contract, the reserved-cache observation, plan-change parsing and guidance, and the end-to-end runtime-client message.
  • bun run --cwd packages/cli typecheck: pass, from a checkout outside /private/tmp. Biome is clean on the changed files; one pre-existing complexity warning remains in an untouched file. docs:check passes.
  • TLA+: new native-cache-reconcile model (2,208 distinct states) with four negative controls. They fail as intended: a whole-document identity fails UnrelatedEditKeepsCache, reattaching a stale cache fails CacheMatchesInputs, reusing run data as a cache fails NoDataAsCache, and keeping writable-consumer caches shared (the poisoning path) fails NoForeignWrites. Every maintained model still verifies.
  • gitleaks: protect on staged files and detect over origin/next..HEAD both report no leaks. Normal hooks ran.

Host proof (at afb4526e; real VM, isolated HOME and native home, synthetic fixture)

The candidate is this branch's hack-native and CLI in a copy of the 5.0.0-next.10 bundle, with unchanged guest binaries. The fixture is an alpine initializer that fills a cache volume, plus an app that prints its MARKER and the cache contents.

  1. up (MARKER=one), then Ctrl-C. Ready; the cache was created.
  2. Edit MARKER one to two, then hack restart on the stopped graph. It refuses with the existing pointer to hack up, not graph_hostname_change.
  3. up. Same run, marker=two, and the same cache volume: identical name, created_at and directory identity, and the initializer's first-run stamp is preserved.
  4. While running, edit MARKER two to three and hack restart. Same run, marker=three, and the same cache volume.
  5. Change the lockfile v1 to v2 (a cache input), then up. Same run, a new cache volume (new identity), installed=v2, and a new first-run stamp.

Remaining gaps

  • The upgrade path from a run admitted by 5.0.0-next.10 is covered only by Rust tests. On the host, next.10 refused the shared native home that the candidate had prepared (project_share), so I could not create a next.10 run there.
  • A failed reconciled up that refuses inside the foreground owner (not at the source-compatibility preflight) still prints the generic foreground startup envelope with the code. The new guidance appears on the preflight path, which runs the same pure checks first.
  • Caches whose identity changes are released by this run but not collected; cache-wide collection remains a separate retention policy.

Refs HACK-1937

🤖 Generated with Claude Code

An env-only Compose edit stranded a retained run whose graph has a
`hack.dependencies.*` cache: plan reconciliation skipped every cache graph,
so the edit fell through to the hostname-change proof and `up`/`restart`
refused with a misleading `graph_hostname_change`.

- Cache graphs are now eligible for reconciliation. A cache whose freshly
  resolved identity equals the recorded one is kept with its provenance; a
  changed identity selects that identity's shared cache exactly as a fresh
  admission does (adopt by labels or create, then the initializer runs) from a
  freshly published source snapshot, and only this run's reference to the old
  shared cache is released. Run-owned data is never reselected as a cache:
  declaring one refuses with `graph_plan_reconcile` and names the service and
  the `hack.dependencies.cache-volume` field.
- A cache identity now binds the initializer's own reviewed definition and its
  cache volume declaration instead of the whole Compose digest, so an edit to
  another service keeps the cache, as Compose keeps a named volume. Receipts
  recorded under the earlier whole-document identity keep their cache while the
  review still resolves to it, and move to the new identity on the first edit.
- Graphs that reconciliation still does not select (live source, a carried
  cache scope, a pending source-device rebind or initializer cache release)
  refuse a non-hostname edit with `graph_plan_change_unsupported` instead of
  `graph_hostname_change`; a hostname change mixed with other edits names the
  routed service. Errors carry a value-free `plan_change` diagnostic and the
  CLI prints closed guidance: revert, or `hack down --remove-data` then `up`.
- Adds the native-cache-reconcile TLA+ model (54 states) with negative
  controls for whole-document identities, stale cache reattachment and run
  data reused as a cache.

Refs HACK-1937

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

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@linear-code

linear-code Bot commented Nov 7, 2026

Copy link
Copy Markdown

HACK-1937

@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 7, 2026 •

Copy link
Copy Markdown
Codex Review Summary: ✅ Completed

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-07T02:49:15.138369Z 592a9b1 Manual request
🔒 Security Review ✅ Completed 2026-11-07T02:52:22.416232Z 592a9b1 Manual request

Security findings

Advisory findings (2)

ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: afb4526eb6

ℹ️ 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".

The v2 dependency-cache identity bound the mount layout only when a mount
used a subpath. Adding or changing another service that mounts the shared
cache volume read-write therefore kept the same identity, so a new writer
could change contents that other runs and instances reuse.

The current identity now always binds the full layout: every active
service that mounts the cache volume, with its target, subpath and
read-only flag. Adding, removing or changing a writer selects a fresh
cache; an environment-only edit to a service keeps it. Mounts stay as
declared, as in Compose and v4, rather than being forced read-only. The
legacy whole-document identity is unchanged, so earlier receipts still
match exactly.

Regression tests cover a new read-write mount, its access mode, an env
edit to that writer, a second initializer, and reconciliation selecting a
fresh cache when a writer service is added.

Refs HACK-1937

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

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Follow-ups at 139be6c3.

Cache identity now binds every mount of the cache volume (security review, MEDIUM). Before this, the v2 identity included the mount layout only when a mount used a subpath. Another service that mounted the shared cache read-write could therefore be added or changed under the same identity and alter contents that other runs reuse. The current identity now always binds every active service mount of the cache volume: service, target, subpath and read-only flag. Adding, removing or changing a writer selects a fresh cache; an env-only edit still keeps it. I did not force non-initializer mounts to read-only: Compose and v4 mount them as declared, and apps commonly write into these volumes, so the identity change is the safeguard. The legacy whole-document identity is unchanged.

New regression tests cover four cases:

  • a read-write mount added to another service changes the identity, and so does a read-only one;
  • an env edit to that writer keeps the identity;
  • a second initializer is part of the identity;
  • reconciliation with an added writer service selects a fresh cache.

A mutation that drops the layout fails both writer tests. Focused Rust tests: 31 pass. clippy, fmt and all TLA+ models (162 controls) pass, and gitleaks is clean.

Loopback port-probe test (provider::publication::port_probe_tests::loopback_probe_refuses_exact_and_wildcard_listeners_and_accepts_a_free_port): it passed 3/3 on clean origin/next (b844c76f) and 3/3 on this branch (afb4526e), from short-path checkouts with cargo +1.97.1. The two earlier failures (published_port_in_use right after dropping the listener) were transient, and this PR does not touch that code.

@codex review

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: afb4526eb6

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Chef's kiss.

Reviewed commit: 139be6c345

ℹ️ 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".

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

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛡️ Codex Security Review · Automatically triggered

Here are some automated security review suggestions for this pull request.

Reviewed commit: 139be6c345

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

…nstance

A service other than the initializer that mounts a dependency cache
read-write can change the cache contents through its own image, command,
environment or source, none of which the cache identity hashes. On a
contributor branch it could therefore poison a cache that a linked worktree
later reuses. Within one checkout the same author controls every input, so
the trust boundary is sharing across worktrees and branch instances.

- A cache with a writable non-initializer consumer binds the plan namespace
  (this checkout and branch instance) into its scope, so it is never shared
  across linked worktrees. Caches whose other consumers all mount read-only
  keep sharing; the initializer's definition, image and lockfiles stay in
  the identity.
- Such graphs always re-prove the cache through reconciliation, and no
  earlier whole-document identity is matched for them: a run created by an
  earlier build moves to its instance-scoped cache once instead of reusing
  the shared one. Env-only edits inside one instance still keep the cache.
- The native-cache-reconcile model adds writable consumers and foreign
  writes (2,208 states) with a `poison` negative control that keeps such
  caches shared and fails `NoForeignWrites`.

Refs HACK-1937

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

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. Hooray!

Reviewed commit: 592a9b1dcb

ℹ️ 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".

@chatgpt-codex-connector

Copy link
Copy Markdown

🛡️ Codex Security Review · Automatically triggered

Security review completed. No security issues were found in this pull request.

Reviewed commit: 592a9b1dcb

View security finding report

Only the user who started this review can view the report in Codex.

ℹ️ About Codex security reviews in GitHub

This is an experimental Codex feature. Security reviews are triggered when:

  • You comment "@codex security review"
  • A regular code review gets triggered (for example, "@codex review" or when a PR is opened), and you’re opted in so security review runs alongside code review

Once complete, Codex will leave suggestions, or a comment if no findings are found.

@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Host proof at 592a9b1d on a real VM. Setup: a fresh isolated HOME, Hack home and native home, with one VM. The candidate is this head's hack-native and CLI on the 5.0.0-next.10 bundle, with next.10's guest binaries unchanged. The fixture is synthetic: an alpine initializer fills the deps cache and stamps its first run, and an app prints MARKER plus the cache contents. The fixture is a git repository, and B is a linked worktree of it.

Worktree A, app mounts the cache read-write (an instance-scoped cache)

Step Result
1. up (MARKER=one), Ctrl-C marker=one installed=v1. Created cache hack-cache-v5-a6e11cf9…, scope c6ff95b6….
2. Edit MARKER to two, hack restart on the stopped graph Refuses with "Run hack up…", not graph_hostname_change.
3. up Same run, marker=two. Same cache a6e11cf9…: same created_at and directory identity, and the first-run stamp is kept.
4. While running, edit MARKER to three and hack restart Same run, marker=three, same cache a6e11cf9….
5. Change lock.txt from v1 to v2 (a cache input), up New cache hack-cache-v5-6f5f478d…, installed=v2, new first-run stamp, same run.

Linked worktree B: sharing follows the consumer mode

Step Result
B base instance (worktree.auto_branch=false), app read-write, up Its own cache hack-cache-v5-8fb69737…, scope 37ddf9a1… (A's read-write scope is c6ff95b6…). Created here, with its own first-run stamp. A's cache is not reused.
A and B with the app mounting the cache :ro (initializer unchanged). A up, then B up A creates the shared cache hack-cache-v5-edcd9d7c…, scope c934ff1b…. B gets the same name and scope with provenance origin adopted, and A's first-run stamp.
A up again with lock v1 after the lockfile change A reattaches its own earlier read-write cache a6e11cf9…, never B's.

Pre-existing behavior found during the proof. When B runs as its default branch instance wt-b, Hack's runtime metadata env (HACK_BRANCH and the branch-specific HACK_* values) is injected into every service, including the initializer. That makes B's initializer inputs differ from A's, so the caches stay separate even with a read-only consumer. The earlier whole-document identity bound these values too, so this PR doesn't change that. It does mean read-only cache sharing only applies between base instances of linked worktrees today. Excluding the generated metadata keys from the cache identity would be a separate follow-up.

One fixture mistake: an edit that switched the app mount to :ro also made the initializer's own mount read-only, so that start failed with "Read-only file system". I restored the initializer mount and reran the step; the results above are from the rerun.

Afterwards I stopped the VM and removed the native home, the fixtures, the candidate bundle and the VM home alias.

roodboi added a commit that referenced this pull request Nov 7, 2026
## What this is

Merge train batch 7 into `next`: three real-project trial fixes. Each
component PR's reviewed head is merged with `git merge --no-ff`, in this
order (#638 is stacked on #636). Integration conflicts are resolved in
the batch's merge commits, described below. Hosted CI on this branch is
the full-suite gate for the combined result.

| PR | Title | Linear | Head included | Review at that head |
| --- | --- | --- | --- | --- |
| #636 | fix(native): compile host aliases like execution and name
unbound aliases | HACK-1939 | `4d25fa5a` | Codex code + security: no
findings; 0 open threads |
| #638 | fix(native): run stock image processes in wrappers like Docker,
naming refusals | HACK-1939 | `08f2d8c3` | Codex code + security: no
findings; 0 open threads |
| #637 | fix(native): reconcile edited graphs that have a dependency
cache | HACK-1937 | `592a9b1d` | Codex code + security: no findings (the
security review confirms the cache-poisoning fix); 0 open threads;
real-VM proof passed |

## Integration changes

- **#636 with batches 1–6:**
- The restart preflight and native `up` pass both #608's
`dependencyOwners` and #636's `internalExtraHostSources`.
- #615 and #636 both added a "nothing was reserved" startup report. They
are now one `finalInspectionFailure`: #615's check plus #636's guards (a
settled failure, not a restore, no mapping attempted), with the combined
message "nothing was created and no cleanup is needed" followed by
#615's diagnostic.
- #636 matched `native_graph_unreserved`, but image projects inspect
with `graph inspect`, which only returns `graph_unreserved`. #636's
check therefore could never fire, so its tests now use
`graph_unreserved`.
- #636's ready summary and confirmed-stop line ride #611's progress
events, so `up` prints one ready line, `Project is ready (SERVICES; run
RUN). …`, with HTTPS URLs and published ports under it. Ctrl-C prints
one stopped line, which states that data was retained when a run mapping
was published.
- **#638 with #615 / #622:** the error type keeps the fixed-message type
and adds #638's process-refusal types. The unified startup report
appends #638's named process refusals after the startup diagnostic.
Doctor keeps the amd64 emulation and settings checks next to #638's
process-wrapper check.
- **#637 with #615 / #638:** failures keep the fixed message, service
and process refusals, and add #637's `planChange`. Its guidance replaces
"inspect owned state before retrying". The cache-poisoning fix holds in
the merged code:
  - `dependency_cache.rs` is #637's file unchanged;
- `plan_reconcile::ordinary_layout` still routes caches with a writable
consumer through reconciliation;
- no batch change mounts a cache volume outside
`instance_scoped_volumes`.
- **Size fix (`dbcf2bcc`):** the three error additions together pushed
`CandidateError` past clippy's large-error bound. `cause_code` is now a
boxed `str`, and process refusals and the plan change share one boxed,
flat-serialized `RefusalDiagnostics`. A test pins the size and the JSON
shape.
- **#640** (credential-helper EPIPE race fix) was folded into batch 6,
so it is not listed here.

## Validation on this branch (`458e0a93`)

- Clean CLI typecheck: pass. Biome on changed files: no errors.
- `RUSTUP_TOOLCHAIN=1.97.1 bun run check:local`: pass. All-features
clippy `-D warnings` (Zig 0.15.2): pass.
- Cargo `--lib`: 1375 pass. `--features native-config-plan`
`provider::graph`: 688 pass. Contracts: `project_contract` 66,
`compose_fields_contract` 10, `interpolation_budget_contract` 1,
`local_contract` 14.
- `bun run test:models` (TLA+ 1.7.4): exit 0, 177 controls verified.
- Focused bun tests, bounded runner, the changed test files: 260 pass, 0
fail.
- `docs:prepare` + `docs:check`, `privacy:check`, gitleaks over
`origin/next..HEAD`: pass.

Not run locally: the full bun suite, the Linux cargo jobs, docker-e2e
and native host qualification. Hosted CI covers the first three.

After this merges, each component PR is closed with a link to the merge
commit.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

<!-- codesmith:footer -->
---
<a
href="https://backend.blacksmith.sh/track/run-proof?expires=1796634294&installation_model_id=17053&pr_number=641&ref=codesmith_pr_footer&repository=hack-dance%2Fhack&return_to=https%3A%2F%2Fgithub.com%2Fhack-dance%2Fhack%2Fpull%2F641&variant=run_proof_on_this_pr&signature=daf0979b3cf0e8fb0d4344fa34460d87b7d75f22ce9fa22f3da3d8c483a575a9"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-light.svg"><img
alt="Run Proof on this PR"
src="https://pr-comments-assets.blacksmith.sh/codesmith/proof-run-proof-on-this-pr-dark-3x.png"
width="168" height="28"></picture></a>
<sup><a
href="https://app.blacksmith.sh/hack-dance/codesmith/hack/pr/641?autoLogin=true&ref=codesmith_pr_footer">Open
in [code]smith</a> · Need help on this PR? Tag
<code>@codesmith-bot</code> with what you need.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->

---------

Co-authored-by: Dimitri Kennedy <344842+roodboi@users.noreply.github.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@roodboi

roodboi commented Nov 7, 2026

Copy link
Copy Markdown
Contributor Author

Merged into next through the train batch PR #641 (squash commit 76460f8). This PR's reviewed head 592a9b1dcbb53aad32e13d89595ec5178fdef2ad was included unchanged. #641 describes the integration resolutions. Closing as merged by batch; the branch is kept.

🤖 Addressed by Claude Code

@roodboi roodboi closed this Nov 7, 2026
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.

1 participant