detach: restoredValue is single-primary only (#443 finding B, option D) - #501
Conversation
…, option D) `DetachFromDiskResult.restoredValue` is a single scalar, but the `json_path` undo's `set` loop can restore more than one prior. Both restores land on disk correctly; only one is reported back, and nothing said which. Per the maintainer decision on issue #443 this lands as option D, documentation only: state in the contract docs that `restoredValue` is meaningful only for a single-primary record, and defer first-wins vs last-wins vs a plural `restoredValues: string[]` until a real multi-entry producer exists. Minting a "the first `set` entry is primary" convention today would promote a plugin-local assumption into the published kernel contract for a case nothing shipped can produce. No behaviour change, so no regression test: a test here would assert the very answer that was deliberately left open. Also corrects the stale "(codex)" tag on the CLI payload's `restored_value`: the `json` and `json_path` undos emit it too. Co-Authored-By: Claude <noreply@anthropic.com>
|
Verdict: clean. Round 1 of 2, head Does it implement the maintainer's decision?Yes, faithfully (reasoned, from reading every added line against the I checked the two ways this could have gone wrong: 1. It does not mint the primacy convention. Every one of the four statements says the
The diff adds no test file (executed: 2. It did no work on finding A. The diff touches neither LLP 0045 nor
So "no action" was the right call and the PR correctly took it. Consumer counts, verified on
|
| Check | Result |
|---|---|
npm test (PR head, 87f89d9) |
3037 tests / 3028 pass / 8 fail |
npm test (pristine origin/master, 73b4618) |
3037 tests / 3028 pass / 8 fail |
npm run typecheck |
clean, exit 0 |
@ref hygiene |
clean - no @ref added, none broken |
| House style | clean |
Executed. Both suites run in my own detached worktrees with node_modules symlinked from the
clone. Counts are identical on both sides, so this docs-only PR moves no test counts, and the
failure sets are identical by name: all 8 are the pre-existing test/core/leave-command.test.js
failures, confirmed against my own baseline, matching the eight the PR body lists.
Methodology note, so the numbers are reproducible: my first pass ran the two suites
concurrently and reported 3053 vs 3037 with divergent name lists. That was my artifact - the
logs contained interleaved TAP lines (findDesktop3pProjectsDirs is empty when # Subtest: install: org_key mode ...). Re-run serially, both sides are bit-identical at 3037/3028/8.
The delta was mine, not the PR's.
House style (executed, on added lines only): no U+2014 em dashes introduced in any of the four
files; no @typedef; no inline import('...') types; no new type-import specifiers; the sole
added ; is prose inside a JSDoc sentence, not code.
Sequencing
Executed. git merge-tree --write-tree of 87f89d9 against the heads of #456 (0bc6336),
#462 (4dcd246), #491 (1685288), #493 (1c5c620) and #495 (c829325) exits 0 for all five,
no conflicts. File sets are disjoint - the types.d.ts files those PRs touch are all different
files (ai-gateway/src/, codex/src/, claude/src/, src/core/cache/), none of this PR's four.
The body's §Sequencing does disclose the deferred @ref LLP 0109#restoredvalue-is-single-primary-only above the set loop in
src/core/config/client_detach_disk.js. I confirmed the rationale is genuine, not a
rationalisation: #495 really does hold both src/core/config/client_detach_disk.js and
llp/0045-client-attach.design.md. Leaving the annotation out rather than seeding a conflict into
the held queue is the correct call under the sequencing rule, and I am not filing it as a
finding. It should land as a follow-up once #495 merges.
Findings
None actionable. One observation recorded for the maintainer, deliberately not acted on:
- (nit, design, no change made)
hypaware-plugin-kernel-types.d.ts:207opens with "The
record'ssetlist is unordered". The sentence that follows it ("nothing in this contract
makes the first one primary") is the precise claim and is exactly right; "unordered" is a
slightly stronger summary of it. Two small tensions worth knowing about, neither of which I would
change here: core's shipped behaviour is order-sensitive today for the siblingremoved
(its first-wins guard atclient_detach_disk.js:506takes the first still-ours entry), and LLP
0109 §Open questions keeps "first-wins and a primacy convention" live, which a flat "unordered"
in the published contract would have to be walked back to adopt. This sits on top of the
deferred question the maintainer explicitly parked, so it is recorded rather than edited.
Non-blocking either way.
No design disagreement with option D itself, which is not this round's to revisit.
Documentation-only. Implements the maintainer decision recorded on issue #443
(comment):
finding A already resolved, no action; finding B is option D, document only.
Finding A: PR #441's "one consumer" claim - prose, and already fixed on
masterNo change in this PR. Verified on
origin/master(73b4618), not assumed:src/core/config/types.d.tsDetachFromDiskResult.warningalready says"Display only: do not parse or split this field" and names both consumers
(
action_attach.jsspandetail,hyp detachstdout +--json).llp/0045-client-attach.design.md§never-clobber-a-user-edit already says thesame in prose.
one".
The issue's own framing of finding A had a further error, caught by triage in the
thread: it listed
action_attach.js:346underrestoredValue. It is not aconsumer of
restoredValue- it readswarningonly.restoredValuehasexactly two consumers, both in
src/core/commands/clients.js:restored_valuein the
--jsonpayload andRestored <v>on stdout. Confirmed by grep onmaster; the docs added here state it that way.Finding B:
restoredValuemulti-entry semantics - prose (option D)detachJsonPathMarkerrestores every still-ourssetentry that carries aprev. All of them land on disk correctly. ButDetachFromDiskResultrestoredValueis a single scalar, so a multi-entry record reports only one ofthem, and no contract said which.
Per the decision, this lands as a stated contract, not a silent one-liner:
restoredValueis documented as meaningful only for a single-primary record,and the first-wins / last-wins / plural
restoredValues: string[]question isdeferred until a real multi-entry producer exists. Documented in:
src/core/config/types.d.ts-DetachFromDiskResult.restoredValue.hypaware-plugin-kernel-types.d.ts-PluginAttachProbeManifest.marker_record,stating that
setis unordered and that this contract does not make the firstentry primary.
src/core/cli/types.d.ts- therestored_valuefield ofhyp detach --json.llp/0109-openclaw-client-adapter.decision.md- a new#### restoredValue is single-primary onlysubsection under§Probe and detach (core-owned), plus the deferred question added to
§Open questions.
The LLP text records why a first-wins guard was rejected rather than merely
noting the gap: it would pick a different arbitrary survivor rather than fix the
defect, and it would promote "the first
setentry is primary" (a conventionthat exists today only inside
openclaw/src/settings.js) into behaviour of thepublished
marker_recordcontract, which the same section forbids core fromlearning. It also records the sibling incoherence -
removedcarries anundocumented first-wins guard in the same loop - as recorded, not resolved.
Small adjacent prose correction: the CLI payload's
restored_valuewas tagged"(codex)". The
jsonandjson_pathundos emit it too.Reproduction evidence
None, by construction, and deliberately so. Both findings are prose. No
behaviour changes in this PR, so nothing here has a failing-then-passing test.
For finding B specifically, a regression test would have to assert which value
a multi-entry record reports - the exact question the decision left open.
Pinning an unratified answer in a test is worse than no test, so none was
written. The existing single-primary coverage
(
test/core/client-detach-json-path.test.js:216,test/core/client-detach-disk.test.js) already pins the documented case.Checks
npm run typecheck: clean.npm test: 3028 pass / 8 fail, identical to a pristineorigin/masterbaseline run in a separate worktree. All 8 are the pre-existing
test/core/leave-command.test.jsfailures (leave after join removes the seed and reports the server,leave clears an applied central slot...,leave reverses org-driven attaches...,leave after join also warns...,leave is idempotent...,leave still tears down...,leave removes the assets...,leave self-heals an org attach...). Same eight by name on both sides.Sequencing
No file here overlaps any of the five open, held PRs. LLP 0045 and
src/core/config/client_detach_disk.jswere both deliberately avoided becausePR #495 changes them; the contract statement lives in the type docs and the LLP
0109 lineage instead, which is where the decision put it.
git merge-tree --write-treeof this branch against the heads of #456, #462, #491, #493 and#495 exits 0 with no conflicts.
One consequence worth flagging for a follow-up: the natural
@ref LLP 0109#restoredvalue-is-single-primary-onlyannotation belongs above thesetloop in
src/core/config/client_detach_disk.js, which #495 is holding. It wasleft out rather than seeding a conflict into the queue.
Fixes #443