Skip to content

feat(arch): gate adapter containment and prove restart rebuilds from checkpoints - #962

Closed
chughtapan wants to merge 4 commits into
impl/register-tool-and-cli-deletionfrom
impl/architecture-rules-and-restart
Closed

feat(arch): gate adapter containment and prove restart rebuilds from checkpoints#962
chughtapan wants to merge 4 commits into
impl/register-tool-and-cli-deletionfrom
impl/architecture-rules-and-restart

Conversation

@chughtapan

@chughtapan chughtapan commented Aug 5, 2026

Copy link
Copy Markdown
Owner

Step 13, complete: the architecture rules and the restart proof.

Architecture rules

Before this, exactly one rule was enforced: "clean-slate cannot import production." Three now hold the shape the cutover produced.

Adapter containment. The two adapter packages may reach @moltzap/client only through its published adapter-facing subpaths, and no shipped source may import daemon-side machinery by name (MoltZapService, MoltZapChannelCore, MoltZapAgentClient, ChannelService, acquireMoltzapd, runMoltzapd).

The subpath rule alone was insufficient, as the plan predicted: channel-base/index.ts re-exported MoltZapChannelCore and ChannelService, so an adapter could take the long way round through a legal subpath. That barrel now exports the seven presentation names its consumers actually use. Both classes have zero consumers outside packages/client.

assertExportMap + assertBinMap pin the client's seven export subpaths and its single moltzapd bin.

Rules scan shipped sources only — test scaffolding legitimately drives a peer agent and registers fixtures, and none of it reaches a user. Two real files hit the rule on the first run and this is why; the exclusion is stated in the script, not silently allowlisted.

Three conformance configs deleted. client, openclaw-channel, nanoclaw-channel each ran passWithNoTests: true against a directory with zero files — four CI steps green for nothing. The server's suite is real and untouched.

Non-vacuity probe — five for five

Probe Result
adapter imports MoltZapService FAIL at context-log.ts:1
adapter deep-imports @moltzap/client/dist/moltzapd.js FAIL, both rules fire
re-add the moltzap bin key FAIL — expected moltzapd, got moltzap, moltzapd
add a ./smuggle export subpath FAIL with the set diff
delete every adapter source FAILwould pass vacuously

Restart proof

Written against what the record actually says, not a paraphrase. 20260801-harness-client-owns-runtime-context:

The client stores stable per-conversation presentation checkpoints locally. After restart it uses search and history reads to rebuild context from those positions. […] This boundary presents context at most once during normal operation. […] History reads rebuild context only and never recreate reply authority.

Verified against the implementation first: projectTurn sets reply: turn.reply from the live MCP _meta route while reconstructHarnessContext produces only context, and sourceConversationIds excludes the target conversation. Both guarantees hold structurally; nothing tested either.

Three client lifetimes against one slot, each spawning its own daemon, only the checkpoint directory surviving between them:

  1. cold — cross-conversation context carries the source content
  2. warm — carries the new content, not the content lifetime 1 already presented, and its bound reply still lands in the live conversation
  3. checkpoints deleted — carries everything again

Probes

Probe Result
drainConversationRead ignores prior checkpoints FAIL — expected […] to not include 'source content before the restart'
harnessClientForProfile uses layerMemory instead of layerFileSystem FAIL — same assertion

The second probe matters: it is the one that distinguishes durable across process restart from within one lifetime. My first attempt at it silently no-op'd — the assert checked that a substring existed rather than that the replacement applied, so the test "passed" against unmodified code. Re-run with a post-condition on the edit, it fails correctly.

Two corrections

Ledger row 5 is withdrawn. It claimed check-architecture-boundaries.js is stale for naming @moltzap/v2-endpoint while "v2 renamed to -harness". Verified: v2 renamed endpointharness on its own branch and updated its own copy of the script. Main still has v2/endpoint, docs/architecture/components.md on main agrees, and main's table matches main's tree. Expected pre-cutover divergence per 20260729-v2-authority-lives-with-v2, not staleness.

The plan's metric was a proxy. It asked for "strictly fewer messagesRead calls than a cold run." The ADR's actual claim is at-most-once presentation; with pagination a smaller delta can still be one call, so the test measures content presented into the turn. Stronger and directly observable.

Authority gap, stated plainly

docs/architecture/harness-implementation-slate.md does not exist on the main line, and checkpoint appears in no main-resident ADR. The record this test pins is v2-owned, and it says so itself: "Production adoption is main-owned." So the test pins behavior the production implementation was built to, with no main-resident record binding it. That is step 5's hole, not something this PR can close.

Verification

  • nx run workspace:precommit — exit 0
  • node scripts/check-architecture-boundaries.js — OK, 470 v1 sources, 12 adapter sources, 6 v2 packages, 64 v2 sources
  • simulator integration suite — 3/3 passing
  • knip surfaced EnrichedSender as newly dead once the barrel narrowed; made internal, since channel-core.ts still uses it
  • OpenClaw case flaked once under concurrent build load (20s inner bound, 33–63s case runtime); not a regression, filed as harness-adapters integration: OpenClaw reply wait is flaky under load #964

@chughtapan chughtapan changed the title feat(arch): gate adapter containment, the client export map, and the bin map feat(arch): gate adapter containment and prove restart rebuilds from checkpoints Aug 5, 2026
…bin map

Only "clean-slate cannot import production" was enforced. Three rules now
hold the shape the cutover produced: adapters may reach @moltzap/client only
through its published adapter-facing subpaths, no source may import daemon-side
machinery by name, and the client's export and bin maps are pinned.

The subpath rule alone was insufficient — channel-base re-exported
MoltZapChannelCore and ChannelService, so any adapter could have taken the
long way round. That barrel now exports the seven presentation names its
consumers actually use; both classes have no consumer outside packages/client.

The rules scan shipped sources only. Test scaffolding legitimately drives a
peer agent and registers fixtures, and none of it reaches a user.

The three client-side conformance configs matched no tests and ran
passWithNoTests, so four CI steps reported green for nothing. Deleted rather
than left standing in for an unmet criterion; the deferral is recorded in #926.
The socket support module went with the Unix RPC plane; its JSDoc header
outlived it and now labels the wrong export.
`20260801-harness-client-owns-runtime-context` states the client "stores
stable per-conversation presentation checkpoints locally. After restart it
uses search and history reads to rebuild context from those positions",
"presents context at most once during normal operation", and that "history
reads rebuild context only and never recreate reply authority". Nothing
tested any of it.

Three client lifetimes against one slot. Each spawns its own daemon; only
the checkpoint directory survives between them. The second lifetime must not
re-present the content the first already carried, and its bound reply must
still land in the live conversation. Deleting the checkpoint directory
restores the full re-read, so the narrowing is the checkpoints and not a
recency window.

A turn's reply is bound to the MCP client that produced it, so the reply
round trip runs inside the lifetime that owns that client rather than
escaping its scope.
@chughtapan
chughtapan force-pushed the impl/architecture-rules-and-restart branch from b8d15a6 to 79a50a6 Compare August 5, 2026 21:30
@chughtapan

Copy link
Copy Markdown
Owner Author

Superseded by #974, which collapses this stack into one PR against main. This branch's commits are all contained in it — nothing is lost, and this thread stays readable. Closing to leave one place to review.

@chughtapan chughtapan closed this Aug 6, 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