instar 1.3.547 → 1.3.549
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +112 -0
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +29 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CoherenceJournal.d.ts +1 -1
- package/dist/core/CoherenceJournal.d.ts.map +1 -1
- package/dist/core/CoherenceJournal.js +23 -2
- package/dist/core/CoherenceJournal.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +27 -1
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/TopicOperatorReplicatedStore.d.ts +250 -0
- package/dist/core/TopicOperatorReplicatedStore.d.ts.map +1 -0
- package/dist/core/TopicOperatorReplicatedStore.js +413 -0
- package/dist/core/TopicOperatorReplicatedStore.js.map +1 -0
- package/dist/core/UserRegistryReplicatedStore.d.ts +287 -0
- package/dist/core/UserRegistryReplicatedStore.d.ts.map +1 -0
- package/dist/core/UserRegistryReplicatedStore.js +527 -0
- package/dist/core/UserRegistryReplicatedStore.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +10 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +3 -1
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +40 -0
- package/dist/server/routes.js.map +1 -1
- package/dist/users/TopicOperatorStore.d.ts +24 -0
- package/dist/users/TopicOperatorStore.d.ts.map +1 -1
- package/dist/users/TopicOperatorStore.js +28 -0
- package/dist/users/TopicOperatorStore.js.map +1 -1
- package/dist/users/UserManager.d.ts +27 -0
- package/dist/users/UserManager.d.ts.map +1 -1
- package/dist/users/UserManager.js +44 -0
- package/dist/users/UserManager.js.map +1 -1
- package/package.json +1 -1
- package/src/data/builtin-manifest.json +63 -63
- package/src/scaffold/templates.ts +3 -1
- package/upgrades/1.3.548.md +24 -0
- package/upgrades/1.3.549.md +36 -0
- package/upgrades/side-effects/multi-machine-replicated-store-ws26-user-topicop.md +123 -0
- package/upgrades/side-effects/ws52-incb-b4-livetest-route.md +42 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
The **user registry + the topic-operator binding are now the SIXTH + SEVENTH concrete consumers of the HLC replicated-store foundation and the SECOND + THIRD PII kinds** (after WS2.3 relationships) — `user-record` and `topic-operator-record` ride the foundation primitives (envelope / union-reader / conflict-store / rollback-unmerge / bounds) so a registered user / a verified operator known on one machine is known on the others. They REUSE the WS2.3 PII machinery rather than reinventing it. **With these landed, the WS2 memory family is COMPLETE** (7 kinds: preferences, relationships, learnings, knowledge, evolution-actions, user-registry, topic-operator; playbook deferred). Per `docs/specs/multi-machine-replicated-store-foundation.md`.
|
|
9
|
+
|
|
10
|
+
- **The `user-record` replicated kind** (`src/core/UserRegistryReplicatedStore.ts`) — a STRICT typed schema that is a **discriminated union on `op`** and **type-clamps every known field on receive** (`createdAt` is ISO-8601-only, `telegramUserId` is a finite number, `channels[]`/`permissions[]`/free text are length-clamped, a path-shaped channel `type` is jailed out — so a foreign, attacker-controlled record can't smuggle markup through a render slot). The **disclosure-minimized projection** strips the local `userId` (the cross-machine-unstable id). The cross-machine `recordKey` is the **channel set** — `sha256(sorted "type:identifier" pairs)`, the same identity model relationships uses — so the SAME user on two machines collapses to ONE record. HIGH impact at the **replication** layer (append-both-and-flag — auto-merging two divergent profiles could fuse two distinct humans) but **advisory** at the **read** layer: a replicated user record is a HINT about what the agent's OTHER machines know, NEVER the authoritative answer to "who is this inbound sender?" — identity RESOLUTION of an inbound principal stays LOCAL-ONLY (the local channel index always wins). A removed user emits a channel-keyed tombstone (the resurrection guard).
|
|
11
|
+
- **The `topic-operator-record` replicated kind** (`src/core/TopicOperatorReplicatedStore.ts`) — the same hardened machinery, with the load-bearing safety invariant baked in. The cross-machine `recordKey` is `sha256(topicId + ":" + verified-uid)` — keyed on the topic + the AUTHENTICATED uid, NEVER a content-name (Know-Your-Principal). The disclosure-minimized projection is exactly `{platform, uid, names, boundAt}`. **THE LOAD-BEARING SAFETY INVARIANT**: a replicated topic-operator record is UNTRUSTED peer data — it can NEVER become this machine's authoritative answer to "who is my verified operator?". The local auth-derived binding (`TopicOperatorStore.setOperator` from an authenticated sender) is ALWAYS authoritative; the replicated record is advisory context only, rendered as quoted untrusted data that EXPLICITLY says so, and there is NO apply path back into `TopicOperatorStore` by construction. This is the mechanical arm of the constitution's Know-Your-Principal standard.
|
|
12
|
+
- **DUAL REGISTRY** — both kinds are registered in BOTH `JOURNAL_KINDS` (`CoherenceJournal.ts` — the static serve/apply/advert half, each with a `DEFAULT_RETENTION` entry that is never `rotateKeep:0` for compliance) AND `ReplicatedKindRegistry` (the dynamic half). A kind in only one silently replicates nothing; the coupling test asserts both.
|
|
13
|
+
- **Emit-on-write funnels + tombstones** — `UserManager` gains an injected (dark-by-default) `UserReplicationEmitter`: `persistUsers()` re-emits a `put` per surviving user and `removeUser()` emits an `op:'delete'` channel-keyed tombstone (else a peer re-replicates a locally-removed user forever — resurrection). `TopicOperatorStore` gains a `TopicOperatorReplicationEmitter`: `setOperator()` emits a `put` on a REAL bind (the idempotent re-bind is not re-emitted). Both are best-effort: a throwing emitter never breaks the local write.
|
|
14
|
+
- **Read-only neutralized union** — each merged read resolves THROUGH the bypass-proof `ReplicatedStoreReader` and renders each foreign record inside a `<replicated-untrusted-data origin="…">` envelope. A replicated record never clobbers a divergent local one — and for topic-operator, it never reaches the local principal authority.
|
|
15
|
+
- **Config + advert + awareness + migration** — `multiMachine.stateSync.userRegistry` + `.topicOperator` `{ enabled:false, dryRun:true }` added to ConfigDefaults (both classified in `DARK_GATE_EXCLUSIONS`; the dark-gate line-map recomputed by hand); the `stateSyncReceive` advert self-reports both from the registry; the "One Memory" CLAUDE.md section gains two WS2.6 lines in both `generateClaudeMd` and an idempotent `migrateClaudeMd` splicer — so the awareness, ESPECIALLY the topic-operator untrusted-replicated invariant, reaches already-deployed agents before any operator enables the replication.
|
|
16
|
+
|
|
17
|
+
Pure MECHANISM, dark by default. A single-machine / flag-off agent is a strict no-op (no PII ever crosses a machine boundary while dark).
|
|
18
|
+
|
|
19
|
+
## What to Tell Your User
|
|
20
|
+
|
|
21
|
+
None while dark — internal substrate. The user-visible capability — the people I have registered as users, and the record of which verified operator a topic was bound to, follow you across your machines (collapsing the same user by their channel set instead of duplicating, never by an internal id) — becomes real only when an operator turns on cross-machine replication for those stores. Two safety promises hold even then: I never let a record from another machine decide who is messaging me right now (that stays a local decision), and I never let a record from another machine decide who my verified operator is (only an authenticated message on this machine binds that). That awareness ships in the One Memory section of my project notes so I can honestly answer whether your registered users follow you across machines, and explain that a peer machine's view of your operator is advisory context only. With this, my whole memory family — preferences, relationships, learnings, knowledge base, action items, registered users, and operator bindings — can follow you across machines once you turn it on.
|
|
22
|
+
|
|
23
|
+
## Summary of New Capabilities
|
|
24
|
+
|
|
25
|
+
None user-facing while dark. Two new internal modules: `UserRegistryReplicatedStore.ts` and `TopicOperatorReplicatedStore.ts`. `UserManager` + `TopicOperatorStore` each gain an injected (dark) replication emit seam. No new routes (the foundation /state/conflicts, /state/resolve-conflict, /state/quarantine surface is reused).
|
|
26
|
+
|
|
27
|
+
Migration parity: `PostUpdateMigrator` wires both `multiMachine.stateSync.userRegistry` / `.topicOperator` config defaults and the dark-gate map entries into already-deployed agents on update, and idempotently splices the two WS2.6 One-Memory awareness lines (including the topic-operator untrusted-replicated invariant) into a deployed agent's CLAUDE.md — so existing agents receive the capability and its safety prose, not just new installs.
|
|
28
|
+
|
|
29
|
+
## Evidence
|
|
30
|
+
|
|
31
|
+
- `tests/unit/UserRegistryReplicatedStore.test.ts` — dual-registry coupling; recordKey-identity-collapses-cross-machine (channel-set fingerprint, NEVER the local userId; order-independent; case-drift absorbed; collision-resistant; channel-less is null); disclosure-min-strips-local-id; fat-record-replicates + 64KB-named-error; type-clamp (ISO-8601 / finite-number / jailed-channel reject smuggled markup); op:delete-tombstone-erasure; advisory append-both union merge; foreign render safety; own-origin materialization keys on the channel set. Green.
|
|
32
|
+
- `tests/unit/TopicOperatorReplicatedStore.test.ts` — dual-registry coupling; recordKey-identity-collapses-cross-machine (topic+uid, NEVER a content-name; a different uid is a different record; field-straddle guard); disclosure-min (exactly {platform, uid, names, boundAt}); 64KB-named-error; type-clamp; **untrusted-replicated-operator-never-authoritative** (the foreign render always says NOT the verified operator + the module exposes NO apply/set/establish export); op:delete-tombstone-erasure; advisory append-both. Green.
|
|
33
|
+
- `tests/unit/ws26-user-topicop-wiring.test.ts` — dual-registry + server.ts registration/union-reader wiring + both manager emit seams + ConfigDefaults dark defaults + dev-gate exclusions + the awareness section + the migrator splice + §12 union-reader-cannot-be-bypassed + **THE BLOCKER cross-store proof** (a peer-originated topic-operator record never changes the local getOperator() authority) + dark-ship strict no-op (no emitter = byte-identical manager behavior). Green.
|
|
34
|
+
- `tests/unit/PostUpdateMigrator-ws26OneMemory.test.ts` — the chained migrator splices BOTH WS2.6 One Memory bullets (incl. the untrusted-replicated-operator invariant prose) into a deployed WS2.5 agent's CLAUDE.md, before the When-to-use bullet, idempotently. Green.
|
|
35
|
+
- `tests/integration/ws26-user-topicop-emit.test.ts` — the emit-on-mutation contract with a real AgentServer (upsertUser fires the put funnel keyed on the channel set; removeUser fires the tombstone; setOperator fires the put on a real bind and skips the idempotent re-bind; getOperator stays the local bind; the shared /state/conflicts substrate is alive). Green.
|
|
36
|
+
- `tests/e2e/ws26-user-topicop-alive.test.ts` — the Phase-1 "feature is alive" E2E: enabled = a user-record conflict (two divergent profiles for the same channel set) is open + readable + resolvable over HTTP (200); both kinds registered (dual-registry); disabled = 503; routes require Bearer auth. Green.
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Side-Effects Review — WS2.6 user-registry + topic-operator replicated stores (the last two memory-family PII kinds, dark)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `multi-machine-replicated-store-ws26-user-topicop`
|
|
4
|
+
**Date:** `2026-06-13`
|
|
5
|
+
**Author:** `Instar Agent (echo)`
|
|
6
|
+
**Second-pass reviewer:** `5-lens adversarial review (untrusted-replicated-operator / recordKey-identity / disclosure-min-PII-leak / tombstone-erasure / dark-ship-inertness) — verdict + named tests appended below`
|
|
7
|
+
**Parent principle:** Cross-Machine Coherence — One Agent, Robust Under Degraded Conditions
|
|
8
|
+
|
|
9
|
+
## Summary of the change
|
|
10
|
+
|
|
11
|
+
WS2.6 adds the final two PII kinds to the WS2 memory family on the proven HLC
|
|
12
|
+
replicated-store foundation: `user-record` (the multi-user registry the `UserManager`
|
|
13
|
+
resolves an inbound message to) and `topic-operator-record` (which VERIFIED operator a
|
|
14
|
+
topic was bound to). Both are thin instantiations of the shipped WS2.3 relationships PII
|
|
15
|
+
template: discriminated-union-on-`op` schema (value + tombstone), strict type-clamp on
|
|
16
|
+
receive, disclosure-minimized projection, content/identity-surface `recordKey`, 64KB
|
|
17
|
+
per-entry cap with a named over-cap error, op:delete tombstone for offline-peer erasure,
|
|
18
|
+
HIGH-impact-at-replication / advisory-at-read. With these landed, the WS2 memory family
|
|
19
|
+
is COMPLETE (7 kinds: preferences, relationships, learnings, knowledge, evolution-actions,
|
|
20
|
+
user-registry, topic-operator; playbook deferred CMT-1416).
|
|
21
|
+
|
|
22
|
+
Both ship **DARK** behind `multiMachine.stateSync.userRegistry` and
|
|
23
|
+
`.topicOperator` (`enabled:false`, `dryRun:true` — the graduated dark→dryRun→live ladder).
|
|
24
|
+
Flag-off / single-machine = strict no-op; NO PII ever crosses a machine boundary while dark.
|
|
25
|
+
|
|
26
|
+
## Decision-point inventory (the decided forks, baked in verbatim)
|
|
27
|
+
|
|
28
|
+
- **user-record recordKey** = `sha256(sorted channel-set "type:identifier" pairs)` — a user IS
|
|
29
|
+
their channel identifiers (same model as relationships, mirroring `UserManager.channelIndex`).
|
|
30
|
+
NEVER the local `userId` (the cross-machine-unstable id).
|
|
31
|
+
- **topic-operator recordKey** = `sha256(topicId + ":" + verified-uid)`. NEVER a content-name
|
|
32
|
+
(Know-Your-Principal: the binding is auth-sender-derived).
|
|
33
|
+
- **Impact tier**: BOTH HIGH@replication (append-both-and-flag on concurrent divergence) /
|
|
34
|
+
advisory@read (a replicated PII record is a hint, never the authoritative answer).
|
|
35
|
+
- **disclosure-min**: user-record strips the local `userId`; topic-operator projects exactly
|
|
36
|
+
`{platform, uid, names, boundAt}` (no internal `boundFrom`).
|
|
37
|
+
- **THE LOAD-BEARING INVARIANT (topic-operator)**: a replicated topic-operator record is
|
|
38
|
+
UNTRUSTED peer data — NEVER this machine's authoritative answer to "who is my verified
|
|
39
|
+
operator?". Only the LOCAL authenticated `TopicOperatorStore.setOperator` binds the principal.
|
|
40
|
+
The store exposes NO apply/set/establish path back into `TopicOperatorStore` by construction.
|
|
41
|
+
|
|
42
|
+
## 1. Over-block
|
|
43
|
+
None. Both kinds ADD reach (replicated PII as advisory context) and never block a message, a
|
|
44
|
+
route, or a session. Flag-off / single-machine = strict no-op (named E2E + unit tests).
|
|
45
|
+
|
|
46
|
+
## 2. Under-block (the real risk surface)
|
|
47
|
+
The inverse risk: replicated PII is materialized into a union read. Mitigations: (a) strict
|
|
48
|
+
type-clamp on receive (ISO-8601 dates, finite numbers, jailed slugs) makes markup
|
|
49
|
+
un-smuggleable through a render slot; (b) every foreign record renders inside a
|
|
50
|
+
`<replicated-untrusted-data origin="…">` envelope; (c) the topic-operator foreign render
|
|
51
|
+
EXPLICITLY states the record is NOT the verified operator and cannot establish/override one;
|
|
52
|
+
(d) identity RESOLUTION of an inbound principal stays LOCAL-ONLY for user-record (the local
|
|
53
|
+
channel index always wins); (e) the topic-operator store has no apply path, so a replicated
|
|
54
|
+
record can never reach `getOperator()` authority. Accepted residual: a compromised
|
|
55
|
+
same-operator peer could inject advisory context text, but it cannot escalate to principal
|
|
56
|
+
authority and the blast radius is one operator's own pool — recorded as a known bound.
|
|
57
|
+
|
|
58
|
+
## 3. Level-of-abstraction fit
|
|
59
|
+
The two `*ReplicatedStore.ts` modules are PURE logic (no fs, no Date, no network) — schema +
|
|
60
|
+
projection + recordKey + merge + render, unit-testable in isolation. The wiring sits in
|
|
61
|
+
server.ts (kind registration + union reader) and the manager seams (`UserManager.persistUsers`/
|
|
62
|
+
`removeUser`, `TopicOperatorStore.setOperator`) exactly where the WS2.2–2.5 siblings sit. No
|
|
63
|
+
new transport, no new auth, no new tick.
|
|
64
|
+
|
|
65
|
+
## 4. Signal vs authority compliance
|
|
66
|
+
Fully signal-only. Neither kind gates or rewrites anything. The user-registry merged read is a
|
|
67
|
+
HINT (inbound resolution stays local-authoritative); the topic-operator merged read is advisory
|
|
68
|
+
context (the principal stays local-authoritative). The mesh path is the existing read/observe
|
|
69
|
+
replicated-store substrate — adds reach, never authority; the receiver type-clamps + envelopes.
|
|
70
|
+
|
|
71
|
+
## 5. Interactions
|
|
72
|
+
- Rides the existing `ReplicatedKindRegistry` + dual-registry (`JOURNAL_KINDS`) + union reader +
|
|
73
|
+
conflict/rollback substrate (no new machinery).
|
|
74
|
+
- Two new `enabled:false` ConfigDefaults paths shift the dark-gate EXPECTED line-map by +29;
|
|
75
|
+
hand-edited (regeneration forbidden) + two new attributed entries added.
|
|
76
|
+
- Coexists with the WS2.1–2.5 siblings (same `multiMachine.stateSync.*` block, independent flags).
|
|
77
|
+
- The maintainer's WS5.2 credential-repointing track was NOT touched (separate lane).
|
|
78
|
+
|
|
79
|
+
## 6. External surfaces
|
|
80
|
+
No new HTTP routes. The kinds register onto the shared registry and ride the existing
|
|
81
|
+
`/state/*` conflict/quarantine routes (already alive). Emission/serve/pull stay dark behind the
|
|
82
|
+
two new flags. The journal-backed emitter is wired at the manager seams but only attached when
|
|
83
|
+
the flag is on (a later rollout stage, mirroring the siblings).
|
|
84
|
+
|
|
85
|
+
## 7. Multi-machine posture (Cross-Machine Coherence) + Phase C
|
|
86
|
+
- **Per-machine emission; N-machine pool = N independent emitters.** Identity keyed on the
|
|
87
|
+
channel-set (user) / topic+uid (operator), NOT a per-machine id — so the SAME user/operator
|
|
88
|
+
across N machines is ONE record, never N duplicates.
|
|
89
|
+
- **The topic-operator untrusted-replicated invariant scales:** on N machines only the LOCAL
|
|
90
|
+
authenticated binding is authoritative for the principal; replicated records from N-1 peers
|
|
91
|
+
are advisory. A re-bind to a NEW operator (different uid) is a DIFFERENT record (different
|
|
92
|
+
key), so a replica can never overwrite the local binding.
|
|
93
|
+
- **No LAN/broadcast assumption.** Rides the existing per-peer replicated-store substrate.
|
|
94
|
+
- **Offline-peer erasure:** a removed user / unbound operator propagates an op:delete tombstone
|
|
95
|
+
keyed on the SAME identity surface, so an erased record stays erased on a peer that was offline
|
|
96
|
+
at delete time.
|
|
97
|
+
|
|
98
|
+
## Migration parity
|
|
99
|
+
- ConfigDefaults: two new dark blocks (no migrateConfig needed — an absent per-store key resolves
|
|
100
|
+
to dark, the safe default; identical to the WS2.2–2.5 siblings).
|
|
101
|
+
- CLAUDE.md template (`templates.ts`) + a chained migrator else-if in `PostUpdateMigrator` splice
|
|
102
|
+
the two new One Memory bullets — ESPECIALLY the topic-operator UNTRUSTED-REPLICATED-OPERATOR
|
|
103
|
+
invariant — into already-deployed agents before any operator enables the replication. Idempotent
|
|
104
|
+
(guarded by the unique 'User registry is the SECOND PII store' marker), with a focused test.
|
|
105
|
+
- devGatedFeatures: two new DARK_GATE_EXCLUSIONS entries (optional-integration).
|
|
106
|
+
- Docs: two new `under-the-hood.md` class blocks (≥2 mentions each — docs-coverage passes).
|
|
107
|
+
|
|
108
|
+
## 5-lens adversarial verdict (folded as named tests)
|
|
109
|
+
1. **Untrusted-replicated-operator (THE blocker)** — `untrusted-replicated-operator-never-authoritative`:
|
|
110
|
+
the foreign render ALWAYS says NOT the verified operator; the module exposes NO apply/set/establish
|
|
111
|
+
export; the wiring test proves `getOperator()` authority is UNCHANGED by a replicated record. PASS.
|
|
112
|
+
2. **recordKey identity** — `recordKey-identity-collapses-cross-machine`: same channel-set/topic+uid →
|
|
113
|
+
one key regardless of local id; a different uid is a different record. PASS.
|
|
114
|
+
3. **disclosure-min / PII leak** — `disclosure-min-strips-local-id` (userId never on wire),
|
|
115
|
+
topic-operator projection exactly `{platform,uid,names,boundAt}`, `64KB-named-error`, `type-clamp`
|
|
116
|
+
(markup injection blocked, path-shaped slug jailed). PASS.
|
|
117
|
+
4. **tombstone erasure** — `op:delete-tombstone-erasure`: tombstone keyed on the SAME identity surface;
|
|
118
|
+
schema op:delete branch accepts it. PASS.
|
|
119
|
+
5. **dark-ship inertness** — `dark-ship-strict-noop`: no emitter ⇒ manager behaves byte-identically;
|
|
120
|
+
E2E DISABLED → 503. PASS.
|
|
121
|
+
|
|
122
|
+
No residual blockers. Known accepted bound: a compromised same-operator peer can inject advisory
|
|
123
|
+
context (cannot escalate to authority; one-operator blast radius).
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Side-Effects Review — WS5.2 Increment B / B4: the livetest battery entrypoint (promotion gate)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `ws52-incb-b4-livetest-route`
|
|
4
|
+
**Date:** 2026-06-13
|
|
5
|
+
**Author:** echo
|
|
6
|
+
**Second-pass reviewer:** independent reviewer subagent — CONCUR (a credential-action route that can trigger real swaps → Phase 5 required)
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Adds `POST /credentials/livetest` (src/server/routes.ts) — the reachable entrypoint for the §5 livetest battery (the Step-10 `CredentialRepointingLivetest` harness). It wires the harness to the REAL swap executor + identity oracle and runs the automatable round-trips (the dry-run→live PROMOTION gate). The destructive surface is protected by TWO independent gates: the harness performs ZERO swaps unless `armed:true` is in the body (the operator explicitly arms the battery), AND even armed, the executor's own `dryRun` keeps writes off until a deliberate `dryRun:false`. Dark → 503 (credLeverGuard); every named slot is validated against the enumerated ledger set (→ 400) before the harness runs. This completes Increment B's tooling — running the battery live is the operator's enablement moment (CMT-1494).
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `POST /credentials/livetest` — add — a credential-action entrypoint. It can trigger credential swaps, but ONLY when BOTH (a) `armed:true` (harness gate) AND (b) `dryRun:false` (executor gate, set only at construction, never touched by the route) hold. Both gates are independently reviewed (Step 10 harness armed-guard; Step 5 executor dryRun). The route adds only slot-validation + the dark gate.
|
|
15
|
+
|
|
16
|
+
---
|
|
17
|
+
|
|
18
|
+
## 1. Over-block
|
|
19
|
+
No legitimate input is wrongly rejected: the slot validation rejects only values not in the enumerated ledger set (path-traversal `../`/`~`/absolute can't reach a write — the executor re-validates too). The dark 503 and the not-armed refusal are the conservative directions.
|
|
20
|
+
|
|
21
|
+
## 2. Under-block
|
|
22
|
+
The honest exposure: an operator with the Bearer token who arms the battery AND has flipped `dryRun:false` can move real credentials via this route — which is the INTENT (the promotion gate is the operator's enablement action). The reviewer confirmed no path moves a real credential without BOTH gates; the harness's always-restore leaves no residual exchange even on a forward-verify failure.
|
|
23
|
+
|
|
24
|
+
## 3. Level-of-abstraction fit
|
|
25
|
+
Correct: the route is a thin wrapper that wires the already-reviewed harness to the real executor + oracle. It holds no battery logic of its own (that's the harness) and no swap mechanics (that's the executor).
|
|
26
|
+
|
|
27
|
+
## 4. Signal vs authority compliance
|
|
28
|
+
- [x] Yes — but the authority is the operator's, exercised through two independent explicit gates (armed + dryRun:false). The route grants no autonomous authority; it is a manual, operator-armed entrypoint. (Ref: docs/signal-vs-authority.md.)
|
|
29
|
+
|
|
30
|
+
## 5. Interactions
|
|
31
|
+
- **Double-gate independence:** `armed` (request body) and `dryRun` (executor construction config) are independent; the route passes `armed` through and never overrides `dryRun`. Verified by integration tests (not-armed → refused/zero-swaps; the executor's dry-run path returns 'dry-run' with zero writes).
|
|
32
|
+
- **Validation before run:** slot validation + the dark gate run BEFORE harness construction/run.
|
|
33
|
+
- No shadowing/race — a manual, synchronous request.
|
|
34
|
+
|
|
35
|
+
## 6. External surfaces
|
|
36
|
+
- A new authenticated route. Dark → 503 (unchanged for a fleet/disabled agent). On a live (dev) agent, NOT armed → a refused report (zero swaps); armed → the battery runs (in dry-run, the round-trips report honestly that identities didn't exchange — real verification needs `dryRun:false`). The report is scrubbed via credSend and carries no token/blob material.
|
|
37
|
+
|
|
38
|
+
## 7. Multi-machine posture (Cross-Machine Coherence)
|
|
39
|
+
- **Machine-local BY DESIGN.** The battery validates THIS machine's keychain/slots before promoting THIS machine's feature; the route operates on the local ledger + executor + oracle. No cross-machine surface.
|
|
40
|
+
|
|
41
|
+
## 8. Rollback cost
|
|
42
|
+
Trivial. Revert the commit → the route is gone; no state, no migration. Any swap the armed+live battery ever performs is the reversible, oracle-verified, always-restored round-trip the executor + harness own.
|