Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions hypaware-plugin-kernel-types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,16 @@ export interface PluginAttachProbeManifest {
* `json_path` only: dotted path RELATIVE to the marker object to a
* string property holding the JSON-encoded self-describing undo
* record (e.g. `headers.x-hypaware-marker`).
*
* The record's `set` list is **unordered**: core replays every entry and
* nothing in this contract makes the first one primary. A record whose
* replay restores more than one prior value restores them all on disk,
* but the single `DetachFromDiskResult.restoredValue` reported back (and
* the `restored_value` field of `hyp detach --json`) is only meaningful
* for a record with at most one restoring entry; for several, which one
* is reported is unspecified. Declare at most one restoring `set` entry
* per record if the restored value must be reported (LLP 0109
* §restoredValue is single-primary only).
*/
marker_record?: string
}
Expand Down
46 changes: 46 additions & 0 deletions llp/0109-openclaw-client-adapter.decision.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,47 @@ core format dispatch (probe + `detachClientFromDisk`):
This keeps LLP 0045's invariants: one core undo, driven entirely from the
settings file on disk, and the marker is a self-describing undo record.

#### restoredValue is single-primary only

The undo restores **every** still-ours `set` entry that carries a `prev`,
but `DetachFromDiskResult.restoredValue` is a single scalar. A record whose
replay restores more than one prior therefore restores them all correctly
**on disk** and reports only one of them to the caller. That field is
defined here as **meaningful only for a single-primary record**: one with at
most one restoring `set` entry. For a multi-entry record the reported value
is unspecified, and no caller may depend on which one it is.

Unlike `warning`, folding is not available. `restoredValue` has exactly two
consumers, both in [`src/core/commands/clients.js`](../src/core/commands/clients.js):
`restored_value` in the `hyp detach --json` payload, and ` Restored <v>` on
stdout. Both render it as a bare scalar, so joining two values would be
actively wrong rather than merely ugly. (The attach handler's span `detail`
consumer reads `warning`, not this field.)

The multi-entry semantics are **deliberately left unstated rather than
settled by a guard**. A first-wins guard, matching the one `removed` already
carries three lines later in the same loop, would not fix the defect: it
would pick a different arbitrary survivor and still drop the rest. Worse, it
would promote "the first `set` entry is primary" into behaviour of the
published `marker_record` contract. That convention exists today only inside
the OpenClaw plugin (`openclaw/src/settings.js` reads `setEntries[0]` as
primary); this section's own rule - core knows `json_path` semantics, never
"OpenClaw" - says core must not learn it.

Nothing shipped can reach the case: `claude` is `json` and `codex` is
`toml`, so neither enters this loop, and OpenClaw, the only real
`json_path` + `marker_record` producer, always emits exactly one `set`
entry. It is unreached, not unreachable by construction: `marker_record`
is part of the published kernel contract, so a third-party plugin can
declare two `set` entries on day one. When such a producer exists, decide
then between first-wins, last-wins, and a new plural
`restoredValues: string[]`, with a concrete consumer in front of you.

The sibling `removed` field's first-wins guard is itself undocumented, so a
multi-entry record can describe one entry by one rule and another by the
opposite rule. That incoherence is **recorded here, not resolved**:
resolving it is the same deferred decision.

### Gateway capture

- The plugin requires `hypaware.ai-gateway ^2.0.0`, registers the client
Expand Down Expand Up @@ -194,6 +235,11 @@ settings file on disk, and the marker is a self-describing undo record.
- Whether OpenClaw session JSONL (`~/.openclaw/agents/<id>/sessions/`)
should feed a settlement enricher for native session identity, like
the Claude transcript enricher (LLP 0027).
- What `restoredValue` should report when a `json_path` undo restores
more than one `set` entry (first-wins and a primacy convention,
last-wins, or a plural `restoredValues: string[]`). Deferred until a
real multi-entry producer exists; documented above as single-primary
only in the meantime (issue #443).

## References

Expand Down
9 changes: 8 additions & 1 deletion src/core/cli/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,14 @@ export interface ClientResult {
prev_value?: unknown
/** Value removed on detach, when one was present. */
removed?: string
/** Prior value restored on detach (codex). */
/**
* Prior value restored on detach. Emitted by every core undo format that
* records a prior (`json`, `toml`, `json_path`), not just codex.
*
* Single-primary only: an undo that restores several priors restores them
* all on disk but reports one unspecified value here (LLP 0109
* §restoredValue is single-primary only).
*/
restored_value?: string
/** Non-fatal warning emitted by the adapter. */
warning?: string
Expand Down
19 changes: 18 additions & 1 deletion src/core/config/types.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,24 @@ export interface DetachFromDiskResult {
settingsPath?: string
/** The managed value deleted (e.g. the gateway base URL) when there was no prior to restore. */
removed?: string
/** The prior value restored from the undo record. */
/**
* The prior value restored from the undo record.
*
* **Single-primary only.** An undo whose replay restores more than one
* prior (a `json_path` record with two still-ours `set` entries that each
* carry a `prev`) restores them all on disk but reports only one of them
* here, and *which* one is unspecified. Unlike `warning` there is no fold
* available: both consumers render this as a bare scalar
* (`src/core/commands/clients.js` puts it in the `hyp detach --json`
* payload as `restored_value` and prints ` Restored <v>` on stdout), so
* joining two values would be wrong rather than merely ugly.
*
* No shipped producer emits a multi-restore record, and the multi-entry
* semantics are deliberately unstated rather than pinned by a guard (LLP
* 0109 §restoredValue is single-primary only). A caller that needs every
* restored value should get a new `restoredValues: string[]` field, not
* read meaning into this one.
*/
restoredValue?: string
/**
* Set when a managed value was overridden externally and left in place. The
Expand Down
Loading