instar 1.3.489 → 1.3.491
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/dashboard/index.html +30 -0
- package/dist/commands/server.d.ts.map +1 -1
- package/dist/commands/server.js +1124 -28
- package/dist/commands/server.js.map +1 -1
- package/dist/config/ConfigDefaults.d.ts.map +1 -1
- package/dist/config/ConfigDefaults.js +23 -0
- package/dist/config/ConfigDefaults.js.map +1 -1
- package/dist/core/CodexResumeMap.d.ts +95 -0
- package/dist/core/CodexResumeMap.d.ts.map +1 -0
- package/dist/core/CodexResumeMap.js +283 -0
- package/dist/core/CodexResumeMap.js.map +1 -0
- package/dist/core/GuardPostureStore.d.ts +38 -0
- package/dist/core/GuardPostureStore.d.ts.map +1 -0
- package/dist/core/GuardPostureStore.js +87 -0
- package/dist/core/GuardPostureStore.js.map +1 -0
- package/dist/core/MachinePoolRegistry.d.ts +16 -0
- package/dist/core/MachinePoolRegistry.d.ts.map +1 -1
- package/dist/core/MachinePoolRegistry.js +24 -1
- package/dist/core/MachinePoolRegistry.js.map +1 -1
- package/dist/core/MeshRpc.d.ts +3 -0
- package/dist/core/MeshRpc.d.ts.map +1 -1
- package/dist/core/MeshRpc.js +5 -0
- package/dist/core/MeshRpc.js.map +1 -1
- package/dist/core/ModelSwapService.d.ts +26 -11
- package/dist/core/ModelSwapService.d.ts.map +1 -1
- package/dist/core/ModelSwapService.js +59 -21
- package/dist/core/ModelSwapService.js.map +1 -1
- package/dist/core/PeerPresencePuller.d.ts +9 -0
- package/dist/core/PeerPresencePuller.d.ts.map +1 -1
- package/dist/core/PeerPresencePuller.js +1 -1
- package/dist/core/PeerPresencePuller.js.map +1 -1
- package/dist/core/PostUpdateMigrator.d.ts +16 -0
- package/dist/core/PostUpdateMigrator.d.ts.map +1 -1
- package/dist/core/PostUpdateMigrator.js +92 -4
- package/dist/core/PostUpdateMigrator.js.map +1 -1
- package/dist/core/SessionManager.d.ts +4 -0
- package/dist/core/SessionManager.d.ts.map +1 -1
- package/dist/core/SessionManager.js +3 -0
- package/dist/core/SessionManager.js.map +1 -1
- package/dist/core/SessionRefresh.d.ts +36 -7
- package/dist/core/SessionRefresh.d.ts.map +1 -1
- package/dist/core/SessionRefresh.js +90 -29
- package/dist/core/SessionRefresh.js.map +1 -1
- package/dist/core/TopicProfileOrchestrator.d.ts +480 -0
- package/dist/core/TopicProfileOrchestrator.d.ts.map +1 -0
- package/dist/core/TopicProfileOrchestrator.js +1404 -0
- package/dist/core/TopicProfileOrchestrator.js.map +1 -0
- package/dist/core/TopicProfileResolver.d.ts +104 -0
- package/dist/core/TopicProfileResolver.d.ts.map +1 -0
- package/dist/core/TopicProfileResolver.js +231 -0
- package/dist/core/TopicProfileResolver.js.map +1 -0
- package/dist/core/TopicProfileStore.d.ts +308 -0
- package/dist/core/TopicProfileStore.d.ts.map +1 -0
- package/dist/core/TopicProfileStore.js +781 -0
- package/dist/core/TopicProfileStore.js.map +1 -0
- package/dist/core/TopicProfileTransferCarrier.d.ts +227 -0
- package/dist/core/TopicProfileTransferCarrier.d.ts.map +1 -0
- package/dist/core/TopicProfileTransferCarrier.js +533 -0
- package/dist/core/TopicProfileTransferCarrier.js.map +1 -0
- package/dist/core/TopicResumeMap.d.ts +67 -1
- package/dist/core/TopicResumeMap.d.ts.map +1 -1
- package/dist/core/TopicResumeMap.js +114 -1
- package/dist/core/TopicResumeMap.js.map +1 -1
- package/dist/core/classifyProfileChange.d.ts +83 -0
- package/dist/core/classifyProfileChange.d.ts.map +1 -0
- package/dist/core/classifyProfileChange.js +274 -0
- package/dist/core/classifyProfileChange.js.map +1 -0
- package/dist/core/devGatedFeatures.d.ts.map +1 -1
- package/dist/core/devGatedFeatures.js +6 -0
- package/dist/core/devGatedFeatures.js.map +1 -1
- package/dist/core/frameworkSessionLaunch.d.ts +26 -0
- package/dist/core/frameworkSessionLaunch.d.ts.map +1 -1
- package/dist/core/frameworkSessionLaunch.js +58 -0
- package/dist/core/frameworkSessionLaunch.js.map +1 -1
- package/dist/core/slackRefreshBinding.d.ts +83 -0
- package/dist/core/slackRefreshBinding.d.ts.map +1 -0
- package/dist/core/slackRefreshBinding.js +54 -0
- package/dist/core/slackRefreshBinding.js.map +1 -0
- package/dist/core/topicProfileIngress.d.ts +128 -0
- package/dist/core/topicProfileIngress.d.ts.map +1 -0
- package/dist/core/topicProfileIngress.js +249 -0
- package/dist/core/topicProfileIngress.js.map +1 -0
- package/dist/core/topicProfileValidation.d.ts +109 -0
- package/dist/core/topicProfileValidation.d.ts.map +1 -0
- package/dist/core/topicProfileValidation.js +247 -0
- package/dist/core/topicProfileValidation.js.map +1 -0
- package/dist/core/topicProfileWriteSurface.d.ts +222 -0
- package/dist/core/topicProfileWriteSurface.d.ts.map +1 -0
- package/dist/core/topicProfileWriteSurface.js +631 -0
- package/dist/core/topicProfileWriteSurface.js.map +1 -0
- package/dist/core/types.d.ts +66 -0
- package/dist/core/types.d.ts.map +1 -1
- package/dist/core/types.js.map +1 -1
- package/dist/lifeline/TelegramLifeline.d.ts.map +1 -1
- package/dist/lifeline/TelegramLifeline.js +6 -0
- package/dist/lifeline/TelegramLifeline.js.map +1 -1
- package/dist/messaging/TelegramAdapter.d.ts +10 -1
- package/dist/messaging/TelegramAdapter.d.ts.map +1 -1
- package/dist/messaging/TelegramAdapter.js +41 -1
- package/dist/messaging/TelegramAdapter.js.map +1 -1
- package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts +8 -0
- package/dist/monitoring/ActiveWorkSilenceSentinel.d.ts.map +1 -1
- package/dist/monitoring/ActiveWorkSilenceSentinel.js +8 -0
- package/dist/monitoring/ActiveWorkSilenceSentinel.js.map +1 -1
- package/dist/monitoring/ContextWedgeSentinel.d.ts +8 -0
- package/dist/monitoring/ContextWedgeSentinel.d.ts.map +1 -1
- package/dist/monitoring/ContextWedgeSentinel.js +8 -0
- package/dist/monitoring/ContextWedgeSentinel.js.map +1 -1
- package/dist/monitoring/GuardPostureTripwire.d.ts +3 -27
- package/dist/monitoring/GuardPostureTripwire.d.ts.map +1 -1
- package/dist/monitoring/GuardPostureTripwire.js +8 -76
- package/dist/monitoring/GuardPostureTripwire.js.map +1 -1
- package/dist/monitoring/GuardRegistry.d.ts +48 -0
- package/dist/monitoring/GuardRegistry.d.ts.map +1 -0
- package/dist/monitoring/GuardRegistry.js +49 -0
- package/dist/monitoring/GuardRegistry.js.map +1 -0
- package/dist/monitoring/SessionReaper.d.ts +7 -0
- package/dist/monitoring/SessionReaper.d.ts.map +1 -1
- package/dist/monitoring/SessionReaper.js +9 -0
- package/dist/monitoring/SessionReaper.js.map +1 -1
- package/dist/monitoring/SessionWatchdog.d.ts +8 -0
- package/dist/monitoring/SessionWatchdog.d.ts.map +1 -1
- package/dist/monitoring/SessionWatchdog.js +8 -0
- package/dist/monitoring/SessionWatchdog.js.map +1 -1
- package/dist/monitoring/SocketDisconnectSentinel.d.ts +8 -0
- package/dist/monitoring/SocketDisconnectSentinel.d.ts.map +1 -1
- package/dist/monitoring/SocketDisconnectSentinel.js +8 -0
- package/dist/monitoring/SocketDisconnectSentinel.js.map +1 -1
- package/dist/monitoring/guardManifest.d.ts +67 -0
- package/dist/monitoring/guardManifest.d.ts.map +1 -0
- package/dist/monitoring/guardManifest.js +536 -0
- package/dist/monitoring/guardManifest.js.map +1 -0
- package/dist/monitoring/guardPosture.d.ts +77 -0
- package/dist/monitoring/guardPosture.d.ts.map +1 -0
- package/dist/monitoring/guardPosture.js +198 -0
- package/dist/monitoring/guardPosture.js.map +1 -0
- package/dist/monitoring/guardPostureView.d.ts +100 -0
- package/dist/monitoring/guardPostureView.d.ts.map +1 -0
- package/dist/monitoring/guardPostureView.js +294 -0
- package/dist/monitoring/guardPostureView.js.map +1 -0
- package/dist/monitoring/probes/GuardPostureProbe.d.ts +82 -0
- package/dist/monitoring/probes/GuardPostureProbe.d.ts.map +1 -0
- package/dist/monitoring/probes/GuardPostureProbe.js +384 -0
- package/dist/monitoring/probes/GuardPostureProbe.js.map +1 -0
- package/dist/scaffold/templates.d.ts.map +1 -1
- package/dist/scaffold/templates.js +18 -0
- package/dist/scaffold/templates.js.map +1 -1
- package/dist/scheduler/JobScheduler.d.ts +10 -0
- package/dist/scheduler/JobScheduler.d.ts.map +1 -1
- package/dist/scheduler/JobScheduler.js +12 -0
- package/dist/scheduler/JobScheduler.js.map +1 -1
- package/dist/server/AgentServer.d.ts +40 -0
- package/dist/server/AgentServer.d.ts.map +1 -1
- package/dist/server/AgentServer.js +54 -3
- package/dist/server/AgentServer.js.map +1 -1
- package/dist/server/CapabilityIndex.d.ts.map +1 -1
- package/dist/server/CapabilityIndex.js +12 -0
- package/dist/server/CapabilityIndex.js.map +1 -1
- package/dist/server/peerUrlGuard.d.ts +35 -0
- package/dist/server/peerUrlGuard.d.ts.map +1 -0
- package/dist/server/peerUrlGuard.js +93 -0
- package/dist/server/peerUrlGuard.js.map +1 -0
- package/dist/server/routes.d.ts +31 -0
- package/dist/server/routes.d.ts.map +1 -1
- package/dist/server/routes.js +407 -1
- package/dist/server/routes.js.map +1 -1
- package/package.json +3 -2
- package/scripts/lint-guard-manifest.js +264 -0
- package/src/data/builtin-manifest.json +67 -67
- package/src/data/state-coherence-registry.json +54 -18
- package/src/scaffold/templates.ts +18 -0
- package/upgrades/1.3.490.md +34 -0
- package/upgrades/1.3.491.md +37 -0
- package/upgrades/side-effects/guard-posture-endpoint.md +101 -0
- package/upgrades/side-effects/topic-profile.md +142 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema-note": "Machine form of docs/specs/STATE-COHERENCE-REGISTRY.md (the approved census, census-date 2026-06-05). AUTHORED, NOT build-generated (committed-generated-manifest conflict-loop lesson, COHERENCE-JOURNAL-SPEC
|
|
2
|
+
"$schema-note": "Machine form of docs/specs/STATE-COHERENCE-REGISTRY.md (the approved census, census-date 2026-06-05). AUTHORED, NOT build-generated (committed-generated-manifest conflict-loop lesson, COHERENCE-JOURNAL-SPEC §3.6). One entry per durable state category the census enumerates. Consumed by scripts/lint-state-registry.js: every direct durable-write site targeting a state dir / .instar/ must match an entry's `paths` (or carry an inline `/* state-registry: <category> */` annotation). Fields: category (kebab-case id) | description | scope (machine-local | must-be-coherent | derived-cache | coherent-on-demand) | freshness (real-time | eventual | session-boundary | n/a) | conflictShape (single-writer | append-only | lww | n/a) | transport (git | peer-http | none | deferred) | paths (glob-ish hints) | grandfathered (census marked uncertainty, §4e). New state declares its class here at birth.",
|
|
3
3
|
"version": 1,
|
|
4
4
|
"censusDate": "2026-06-05",
|
|
5
5
|
"sourceDoc": "docs/specs/STATE-COHERENCE-REGISTRY.md",
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
"category": "pending-pulls",
|
|
21
|
-
"description": "Durable working-set pending-pull ledger - outstanding fetches whose producer was offline/unreachable/revoked/mid-run; re-fired on reappearance/run-stopped (WORKING-SET-HANDOFF-SPEC P2
|
|
21
|
+
"description": "Durable working-set pending-pull ledger - outstanding fetches whose producer was offline/unreachable/revoked/mid-run; re-fired on reappearance/run-stopped (WORKING-SET-HANDOFF-SPEC P2 §3.4). Machine-local: records describe THIS machine's outstanding fetches; single-writer serialized mutate() funnel.",
|
|
22
22
|
"scope": "machine-local",
|
|
23
23
|
"freshness": "live",
|
|
24
24
|
"conflictShape": "single-writer",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
},
|
|
31
31
|
{
|
|
32
32
|
"category": "pull-opkeys",
|
|
33
|
-
"description": "Durable (topic,epoch) operation-key window for working-set pull dedupe - restart-proof at-most-once scheduling per move (WORKING-SET-HANDOFF-SPEC P2
|
|
33
|
+
"description": "Durable (topic,epoch) operation-key window for working-set pull dedupe - restart-proof at-most-once scheduling per move (WORKING-SET-HANDOFF-SPEC P2 §3.3). Machine-local, bounded window (200 keys), single-writer (the coordinator).",
|
|
34
34
|
"scope": "machine-local",
|
|
35
35
|
"freshness": "live",
|
|
36
36
|
"conflictShape": "single-writer",
|
|
@@ -42,7 +42,7 @@
|
|
|
42
42
|
},
|
|
43
43
|
{
|
|
44
44
|
"category": "visibility-guard",
|
|
45
|
-
"description": "Peer-visibility guard dedupe state - which improper revocations were already surfaced (keyed on revokedAt, cross-boot) + per-machine disappearance episodes for flap bounding (WORKING-SET-HANDOFF-SPEC P2
|
|
45
|
+
"description": "Peer-visibility guard dedupe state - which improper revocations were already surfaced (keyed on revokedAt, cross-boot) + per-machine disappearance episodes for flap bounding (WORKING-SET-HANDOFF-SPEC P2 §3.6). Machine-local notice hygiene, never authority.",
|
|
46
46
|
"scope": "machine-local",
|
|
47
47
|
"freshness": "live",
|
|
48
48
|
"conflictShape": "single-writer",
|
|
@@ -66,7 +66,7 @@
|
|
|
66
66
|
},
|
|
67
67
|
{
|
|
68
68
|
"category": "commitment-replicas",
|
|
69
|
-
"description": "Per-peer read-only replicas of other machines' commitment stores (COMMITMENTS-COHERENCE-SPEC P1.5
|
|
69
|
+
"description": "Per-peer read-only replicas of other machines' commitment stores (COMMITMENTS-COHERENCE-SPEC P1.5 §3.2). Derived cache - rebuilt by a full re-pull; written ONLY by the commitments-sync receive path; first-hop sender-bound; incarnation-fenced.",
|
|
70
70
|
"scope": "derived-cache",
|
|
71
71
|
"freshness": "eventual",
|
|
72
72
|
"conflictShape": "single-writer",
|
|
@@ -78,7 +78,7 @@
|
|
|
78
78
|
},
|
|
79
79
|
{
|
|
80
80
|
"category": "commitment-pending-mutations",
|
|
81
|
-
"description": "Durable owner-routed mutation intents awaiting an offline owner (COMMITMENTS-COHERENCE-SPEC P1.5
|
|
81
|
+
"description": "Durable owner-routed mutation intents awaiting an offline owner (COMMITMENTS-COHERENCE-SPEC P1.5 §3.4). Stores INTENT only - a FRESH signed envelope is re-issued at fire time; single-writer serialized funnel; per-(origin,id) and per-owner enqueue bounds; TTL 7d with one honest expiry notice.",
|
|
82
82
|
"scope": "machine-local",
|
|
83
83
|
"freshness": "live",
|
|
84
84
|
"conflictShape": "single-writer",
|
|
@@ -90,7 +90,7 @@
|
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
92
|
"category": "commitment-opkeys",
|
|
93
|
-
"description": "Owner-side durable opKey replay window for commitment-mutate (COMMITMENTS-COHERENCE-SPEC P1.5
|
|
93
|
+
"description": "Owner-side durable opKey replay window for commitment-mutate (COMMITMENTS-COHERENCE-SPEC P1.5 §3.4) - the replay control beyond the 60s envelope nonce window; records verdicts, survives restarts, TTL >= pending-mutation TTL.",
|
|
94
94
|
"scope": "machine-local",
|
|
95
95
|
"freshness": "live",
|
|
96
96
|
"conflictShape": "single-writer",
|
|
@@ -246,7 +246,7 @@
|
|
|
246
246
|
},
|
|
247
247
|
{
|
|
248
248
|
"category": "job-definitions",
|
|
249
|
-
"description": "Declarative job definitions (cron / prompt / supervision). The coherent half of jobs.json plus markdown job specs; run state is machine-local (see job-run-state). Field split required before whole-file sync (census
|
|
249
|
+
"description": "Declarative job definitions (cron / prompt / supervision). The coherent half of jobs.json plus markdown job specs; run state is machine-local (see job-run-state). Field split required before whole-file sync (census §5).",
|
|
250
250
|
"scope": "must-be-coherent",
|
|
251
251
|
"freshness": "eventual",
|
|
252
252
|
"conflictShape": "lww",
|
|
@@ -344,7 +344,7 @@
|
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"category": "fleet-config",
|
|
347
|
-
"description": "Fleet-wide config: feature flags, messaging[] config, monitoring tunables, multiMachine policy. SHARES config.json with machine-local fields (port, paths) -- split-required before whole-file sync (census
|
|
347
|
+
"description": "Fleet-wide config: feature flags, messaging[] config, monitoring tunables, multiMachine policy. SHARES config.json with machine-local fields (port, paths) -- split-required before whole-file sync (census §5).",
|
|
348
348
|
"scope": "must-be-coherent",
|
|
349
349
|
"freshness": "eventual",
|
|
350
350
|
"conflictShape": "lww",
|
|
@@ -561,7 +561,7 @@
|
|
|
561
561
|
},
|
|
562
562
|
{
|
|
563
563
|
"category": "audit-destructive-ops",
|
|
564
|
-
"description": "Destructive-ops audit stream (411M live -- needs rotation, census
|
|
564
|
+
"description": "Destructive-ops audit stream (411M live -- needs rotation, census §7). Append-only per-machine.",
|
|
565
565
|
"scope": "machine-local",
|
|
566
566
|
"freshness": "n/a",
|
|
567
567
|
"conflictShape": "append-only",
|
|
@@ -806,7 +806,7 @@
|
|
|
806
806
|
},
|
|
807
807
|
{
|
|
808
808
|
"category": "derived-semantic-memory",
|
|
809
|
-
"description": "semantic.db / memory.db / episodes -- rebuildable from evidence. (Live-disk: stale since Jun 1 -- investigate separately, census
|
|
809
|
+
"description": "semantic.db / memory.db / episodes -- rebuildable from evidence. (Live-disk: stale since Jun 1 -- investigate separately, census §1.6/§7.)",
|
|
810
810
|
"scope": "derived-cache",
|
|
811
811
|
"freshness": "eventual",
|
|
812
812
|
"conflictShape": "n/a",
|
|
@@ -909,7 +909,7 @@
|
|
|
909
909
|
},
|
|
910
910
|
{
|
|
911
911
|
"category": "uncertain-instructions-tracking",
|
|
912
|
-
"description": "state/instructions-tracking/ (59M, 15,180 UUID-keyed JSONL files). Census did not map an owner; volume says it matters. Owner + rotation + class needed (census
|
|
912
|
+
"description": "state/instructions-tracking/ (59M, 15,180 UUID-keyed JSONL files). Census did not map an owner; volume says it matters. Owner + rotation + class needed (census §4e).",
|
|
913
913
|
"scope": "machine-local",
|
|
914
914
|
"freshness": "eventual",
|
|
915
915
|
"conflictShape": "append-only",
|
|
@@ -921,7 +921,7 @@
|
|
|
921
921
|
},
|
|
922
922
|
{
|
|
923
923
|
"category": "uncertain-topic-intent-store",
|
|
924
|
-
"description": "TopicIntentStore. Code references it; on-disk location unverified (census
|
|
924
|
+
"description": "TopicIntentStore. Code references it; on-disk location unverified (census §4e).",
|
|
925
925
|
"scope": "derived-cache",
|
|
926
926
|
"freshness": "eventual",
|
|
927
927
|
"conflictShape": "n/a",
|
|
@@ -934,7 +934,7 @@
|
|
|
934
934
|
},
|
|
935
935
|
{
|
|
936
936
|
"category": "uncertain-shared-state-ledger",
|
|
937
|
-
"description": "shared-state.jsonl (SharedStateLedger). Nominally the git-synced cross-machine ledger -- is anything consuming it on real machines? May be the vestigial ancestor of the P1 journal (census
|
|
937
|
+
"description": "shared-state.jsonl (SharedStateLedger). Nominally the git-synced cross-machine ledger -- is anything consuming it on real machines? May be the vestigial ancestor of the P1 journal (census §4e).",
|
|
938
938
|
"scope": "must-be-coherent",
|
|
939
939
|
"freshness": "eventual",
|
|
940
940
|
"conflictShape": "append-only",
|
|
@@ -947,7 +947,7 @@
|
|
|
947
947
|
},
|
|
948
948
|
{
|
|
949
949
|
"category": "uncertain-correction-capture-backlog",
|
|
950
|
-
"description": "correction-capture-backlog.db vs correction-ledger.db: two stores, one sentinel -- which is canonical? (census
|
|
950
|
+
"description": "correction-capture-backlog.db vs correction-ledger.db: two stores, one sentinel -- which is canonical? (census §4e).",
|
|
951
951
|
"scope": "machine-local",
|
|
952
952
|
"freshness": "eventual",
|
|
953
953
|
"conflictShape": "append-only",
|
|
@@ -959,7 +959,7 @@
|
|
|
959
959
|
},
|
|
960
960
|
{
|
|
961
961
|
"category": "uncertain-project-round-worktrees",
|
|
962
|
-
"description": "ProjectRoundWorktrees / TaskFlow stores. Routes-managed; durable form unverified (census
|
|
962
|
+
"description": "ProjectRoundWorktrees / TaskFlow stores. Routes-managed; durable form unverified (census §4e).",
|
|
963
963
|
"scope": "machine-local",
|
|
964
964
|
"freshness": "eventual",
|
|
965
965
|
"conflictShape": "single-writer",
|
|
@@ -972,7 +972,7 @@
|
|
|
972
972
|
},
|
|
973
973
|
{
|
|
974
974
|
"category": "legacy-singletons",
|
|
975
|
-
"description": "Stale legacy singletons (anti-patterns.json, quick-facts.json, project-registry.json, May 20 vintage, no live writers found). Retire per census
|
|
975
|
+
"description": "Stale legacy singletons (anti-patterns.json, quick-facts.json, project-registry.json, May 20 vintage, no live writers found). Retire per census §7.",
|
|
976
976
|
"scope": "machine-local",
|
|
977
977
|
"freshness": "n/a",
|
|
978
978
|
"conflictShape": "single-writer",
|
|
@@ -998,7 +998,7 @@
|
|
|
998
998
|
},
|
|
999
999
|
{
|
|
1000
1000
|
"category": "stream-tickets",
|
|
1001
|
-
"description": "Pool Dashboard Streaming single-use bearer tickets (POOL-DASHBOARD-STREAM-SPEC
|
|
1001
|
+
"description": "Pool Dashboard Streaming single-use bearer tickets (POOL-DASHBOARD-STREAM-SPEC §2.3). Minted by the pool-stream-ticket mesh verb, consumed once on the /pool-stream WS upgrade. Persisted so a captured ticket cannot replay across a serving-machine restart (consumed-set survives). Machine-local; single-writer (the store's own funnel); TTL-bounded + GC'd.",
|
|
1002
1002
|
"scope": "machine-local",
|
|
1003
1003
|
"freshness": "live",
|
|
1004
1004
|
"conflictShape": "single-writer",
|
|
@@ -1007,6 +1007,42 @@
|
|
|
1007
1007
|
"state/stream-tickets.json"
|
|
1008
1008
|
],
|
|
1009
1009
|
"grandfathered": false
|
|
1010
|
+
},
|
|
1011
|
+
{
|
|
1012
|
+
"category": "guard-posture-snapshot",
|
|
1013
|
+
"description": "Boot-time guard posture snapshot written by the GuardPostureTripwire at every server boot; read by GET /guards for diverged-pending-restart derivation (GUARD-POSTURE-ENDPOINT-SPEC §2.2). Machine-local observability, never authority.",
|
|
1014
|
+
"scope": "machine-local",
|
|
1015
|
+
"freshness": "boot",
|
|
1016
|
+
"conflictShape": "single-writer",
|
|
1017
|
+
"transport": "none",
|
|
1018
|
+
"paths": [
|
|
1019
|
+
"state/guard-posture.json"
|
|
1020
|
+
],
|
|
1021
|
+
"grandfathered": false
|
|
1022
|
+
},
|
|
1023
|
+
{
|
|
1024
|
+
"category": "guard-posture-peers",
|
|
1025
|
+
"description": "Durable last-known guard posture per peer machine with RECEIVER-side receipt time (GUARD-POSTURE-ENDPOINT-SPEC §2.3(c)) - survives local restarts so a dark peer's posture renders with its honest age. Written only at the heartbeat-ingestion chokepoint (registry-keyed identity). Machine-local observability, never authority.",
|
|
1026
|
+
"scope": "machine-local",
|
|
1027
|
+
"freshness": "live",
|
|
1028
|
+
"conflictShape": "single-writer",
|
|
1029
|
+
"transport": "none",
|
|
1030
|
+
"paths": [
|
|
1031
|
+
"state/guard-posture-peers.json"
|
|
1032
|
+
],
|
|
1033
|
+
"grandfathered": false
|
|
1034
|
+
},
|
|
1035
|
+
{
|
|
1036
|
+
"category": "guard-posture-episodes",
|
|
1037
|
+
"description": "GuardPostureProbe episode state - per-anomaly first/last-seen for the persist-across-ticks rule, flap windows, and the open-episode marker that keeps alerts to ONE aggregated Attention item per episode (GUARD-POSTURE-ENDPOINT-SPEC §2.4). Machine-local notice hygiene, never authority.",
|
|
1038
|
+
"scope": "machine-local",
|
|
1039
|
+
"freshness": "live",
|
|
1040
|
+
"conflictShape": "single-writer",
|
|
1041
|
+
"transport": "none",
|
|
1042
|
+
"paths": [
|
|
1043
|
+
"state/guard-posture-episodes.json"
|
|
1044
|
+
],
|
|
1045
|
+
"grandfathered": false
|
|
1010
1046
|
}
|
|
1011
1047
|
]
|
|
1012
1048
|
}
|
|
@@ -419,6 +419,15 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
|
|
|
419
419
|
- Also: when a session is autonomously shut down, you get a "your session was shut down — <reason>" notice (recovery-bounces and your own operator kills stay silent). Turn the notice off with \`{"monitoring": {"reapNotify": {"enabled": false}}}\`.
|
|
420
420
|
- Proactive: user asks "where did my session go?" / "why did X disappear?" / "did something get killed?" → GET /sessions/reap-log and explain the most recent reaped/skipped entries for that session.
|
|
421
421
|
|
|
422
|
+
### Guard Posture — which safety systems are genuinely on (\`GET /guards\`)
|
|
423
|
+
|
|
424
|
+
Every guard (monitoring sentinels, reapers, the scheduler, …) is graded by what can be VERIFIED, never by what the config wishes: \`on-confirmed\` / \`on-unverified\` / \`on-stale\` / \`on-dry-run\` / \`off\` (\`dark-default\` = ships-dark, quiet vs \`diverged-from-default\` = default-on but currently off — the load-shed signature) / \`diverged-pending-restart\` / \`errored\` / \`missing\` / \`off-runtime-divergent\`. Only the "off that shouldn't be off" and runtime-contradiction classes alert — a ships-dark feature that is off is normal, never noise.
|
|
425
|
+
- This machine: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/guards\`
|
|
426
|
+
- Every machine (heartbeat-fresh, or last-known posture with its age for a dark peer): \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/guards?scope=pool"\`
|
|
427
|
+
- **When to use** (PROACTIVE — this is the trigger): "are my guards on?" / "why didn't the watchdog/reaper fire on machine X?" / a post-incident sweep after ANY load-shed → read \`/guards?scope=pool\` and report the deviant rows instead of guessing from config memory. The Machines dashboard tab shows each machine's last-known posture with its age — even for a peer that is currently dark.
|
|
428
|
+
- **HAZARD — re-enabling a guard via \`PATCH /config\`**: send the guard's FULL config block (the merge is one-level-deep and a partial block erases sibling tuning); read the current block from the source machine first (\`GET /guards\` shows posture; the config block itself comes from that machine's config).
|
|
429
|
+
- Three complementary layers, one shared inventory: the Guard-Posture Tripwire covers enabled→disabled transitions at boot (\`logs/guard-posture.jsonl\`); \`/guards\` is the steady-state read; the GuardPostureProbe raises ONE aggregated Attention item when an anomaly persists across consecutive probes.
|
|
430
|
+
|
|
422
431
|
**Coherence Journal** — The durable "what happened where, and where are the files?" answer for a multi-machine agent. Every machine writes append-only event streams (topic placement + why it moved, session open/close/reap, autonomous runs + their artifact paths); the read API serves a merged view so you answer placement/artifact questions from local disk instead of grepping rotating logs on the right machine.
|
|
423
432
|
- Read it: \`curl -H "Authorization: Bearer $AUTH" "http://localhost:${port}/coherence/journal?topic=N&kind=topic-placement&limit=50"\` → \`{ entries: [...], streams: {...}, skippedCorrupt }\` (+ a partial-result flag when a read bound was hit). Filters: \`topic\`, \`kind\` (\`topic-placement\`|\`session-lifecycle\`|\`autonomous-run\`), \`machine\`, \`limit\`, \`cursor\`. Read-only; 503 when the journal is not enabled on this agent.
|
|
424
433
|
- TRUST NOTE: entries tagged \`source: "replica"\` are another machine's copied history — always a little stale (\`stalenessMs\` says how much). NEVER make a kill/spawn/move decision off journal data; it answers questions, the live systems decide.
|
|
@@ -752,6 +761,13 @@ This routes feedback to the Instar maintainers automatically. Valid types: \`bug
|
|
|
752
761
|
- Each framework gets its own circuit breaker (a Claude trip can't pause Codex). If a routed framework's CLI is missing it degrades to the default and reports it; if it's just rate-limited the component falls back to its heuristic (no herd onto the default). Routes INTERNAL component calls only — spawned interactive sessions stay governed by \`topicFrameworks\`.
|
|
753
762
|
- **When to use** (PROACTIVE): when the user is hitting rate limits and asks how to spread load, or says "run my sentinels on Codex" / "move the background checks off Claude" → point them at \`sessions.componentFrameworks\` and \`GET /intelligence/routing\`. Restart sessions to apply (config is read into the router at the call path, but a file edit needs the server to pick it up). (Spec: \`docs/specs/per-component-framework-routing.md\`.)
|
|
754
763
|
|
|
764
|
+
**Topic Profile (per-topic model, thinking, framework pins)** — Every conversation topic can carry a durable profile pinning its BASELINE model (an explicit id OR a tier — never both), thinking depth (\`off\`/\`low\`/\`medium\`/\`high\`/\`max\`), and framework (\`claude-code\`/\`codex-cli\`/…). Pins survive restarts and follow the topic. **The conversational surface is PRIMARY** (PROACTIVE — these are the triggers): when the user says "use codex here", "pin this topic to Fable", or "set high thinking on this topic", that IS the request — propose the change back in plain words, confirm, and the pin is durable from then on. NEVER instruct the user to type \`/topic\`; the \`/topic\` command exists only as a power-user convenience.
|
|
765
|
+
- What is this topic pinned to? \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/topic-profile/TOPIC_ID\` — Registry First: read it, never guess (no entry = the topic runs on defaults).
|
|
766
|
+
- Why/when did a pin change? Read \`logs/topic-profile-changes.jsonl\` — the per-change audit (who set what, when, old → new).
|
|
767
|
+
- A pinned model/framework that is no longer available falls back to defaults with a once-per-transition notice — the session keeps working; a pin is never a block.
|
|
768
|
+
- A baseline pin does NOT disable the heavy-work ultra escalation (\`escalationOverride: 'inherit'\` is the default); it steps aside only when the operator explicitly opts the topic out (\`'suppress'\`).
|
|
769
|
+
- Config: \`.instar/config.json\` → \`topicProfiles\` (\`dryRun\`, debounce windows, stagger cap, breaker threshold; \`defaults\` = per-topic config-default model/thinking). Writes ship dark behind the dev-agent gate with \`dryRun: true\` (intended respawns are logged, not performed); resolution (reads) is always on.
|
|
770
|
+
|
|
755
771
|
**Approval-as-Data** — Every operator approval becomes durable data instead of a one-shot "approved" with no memory. Each decision is recorded as \`approved-as-is\` vs \`approved-with-change\` vs \`rejected\`, with the WHY of each divergence, so over time you can see — per decision-class — where the operator takes your recommendation as-is vs revises it, and close the gap (your recommendations trend toward what they'd pick). Tracks approvals WHEREVER they occur: an official spec sign-off, a decision approved in chat, anywhere. Signed, append-only; read paths never gate.
|
|
756
772
|
- Record a decision: \`curl -X POST -H "Authorization: Bearer $AUTH" http://localhost:${port}/approvals -H 'Content-Type: application/json' -d '{"subject":"<what-was-approved>","decisionClass":"<bucket>","surface":"chat","mode":"approved-as-is"}'\`. For a change: \`"mode":"approved-with-change","divergences":[{"category":"scope-correction","summary":"…","why":"…"}]\` (categories: \`missing-principle | risk-reduction | scope-correction | efficiency | new-information | style\`).
|
|
757
773
|
- See the agreement ratios: \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/approvals/summary\` → per class \`{ total, approvedAsIs, ratio, streak, autoApprovalEligible, divergenceCounts }\` + a \`bySurface\` breakdown. List rows: \`GET /approvals?limit=50&decisionClass=…&surface=…\`.
|
|
@@ -911,6 +927,8 @@ I maintain registries that are the source of truth for specific categories. Thes
|
|
|
911
927
|
| My state change history? | \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/git/log\` |
|
|
912
928
|
| Other agents on this machine? | \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/agents\` |
|
|
913
929
|
| Behavioral issues logged while onboarding a framework? / the onboarding playbook? | \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/framework-issues\` (read-only) + \`/framework-issues/playbook?targetFramework=X\` — the Framework-Onboarding Mentor System's issue ledger (observability only; never gates). Log a discovered issue: \`POST /framework-issues/observe\` {framework,bucket,severity,title,dedupKey,...} |
|
|
930
|
+
| What is this topic pinned to (model/thinking/framework)? | \`curl -H "Authorization: Bearer $AUTH" http://localhost:${port}/topic-profile/TOPIC_ID\` — no entry = the topic runs on defaults |
|
|
931
|
+
| Why/when did this topic's pin change? | \`logs/topic-profile-changes.jsonl\` (per-change audit) |
|
|
914
932
|
| Project architecture? | This file (CLAUDE.md), then project docs |
|
|
915
933
|
|
|
916
934
|
**Why this matters:** Searching 1000 files to answer a question that a single state file could answer is slower AND less reliable. Broad searches find stale narratives. State files are current. This applies at EVERY level — including sub-agents I spawn. When spawning a research agent, include the relevant state file reference in its prompt so it searches WITH context, not blind.
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: patch -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Every conversation topic can now carry a durable **profile** that pins its baseline framework (`claude-code` / `codex-cli` / …), model (an explicit id OR a tier — never both), and thinking depth (`off` / `low` / `medium` / `high` / `max`). The pin survives restarts and follows the topic. When a pin changes, a new orchestrator picks the gentlest way to apply it: a within-framework Claude model-tier change on an idle session swaps in flight with zero loss; otherwise the session is restarted via `claude --resume` (no conversation lost) or, when no resume point is capturable, continued from recent history + memory — with the real cost disclosed honestly in each case. Protected and mid-task sessions are never interrupted (a busy session applies the switch when it goes idle, or immediately if the operator says "switch now").
|
|
9
|
+
|
|
10
|
+
The conversational surface is primary: "use codex here", "pin this topic to Fable", "set high thinking on this topic" — the agent proposes the change back in plain words, confirms, and the pin is durable from then on. A `/topic-profile` HTTP route and a power-user `/topic` command exist for the dashboard, but the agent never instructs the user to type a command.
|
|
11
|
+
|
|
12
|
+
Ships **dark** behind a dev-agent gate (dry-run by default); the fleet serves 503 until it graduates.
|
|
13
|
+
|
|
14
|
+
## What to Tell Your User
|
|
15
|
+
|
|
16
|
+
If you run more than one topic and want some of them on a different model, a different agent framework, or a different thinking depth — and you want that choice to STICK — you can now just say so in that topic ("use codex here", "pin this to Fable", "set high thinking"). I'll confirm it and remember it across restarts. If a topic is mid-task when you change it, I won't interrupt the work — I'll apply the change when it finishes, or right away if you tell me to "switch now". I'll always tell you honestly whether applying the change costs anything (most of the time it doesn't).
|
|
17
|
+
|
|
18
|
+
## Summary of New Capabilities
|
|
19
|
+
|
|
20
|
+
- Durable per-topic `{ framework, model, thinkingMode }` profile that survives restarts and follows the topic.
|
|
21
|
+
- Conversational set/change/read of a topic's profile via the propose-confirm flow (verified-operator–gated).
|
|
22
|
+
- Gentlest-swap orchestration: in-flight model-tier swap (idle Claude) → `claude --resume` restart → continuation, each with honest loss disclosure.
|
|
23
|
+
- Protected/busy/autonomous sessions are never profile-killed; "switch now" overrides busy (never protection).
|
|
24
|
+
- A `/topic-profile` operator/dashboard read-write route and a `/topic` power-user command.
|
|
25
|
+
- Cross-machine: a topic's profile is pulled to whichever machine acquires the topic (mesh `topic-profile-pull` verb).
|
|
26
|
+
|
|
27
|
+
## Evidence
|
|
28
|
+
|
|
29
|
+
- `pnpm build` EXIT 0 (clean). no-silent-fallbacks ratchet 5/5 (baseline untouched).
|
|
30
|
+
- Unit tier green; integration 278 files / 2592 passed; topic-profile e2e lifecycle 8/8.
|
|
31
|
+
- Composition-root wiring locked by `tests/unit/topic-profile-server-wiring.test.ts` (20 assertions).
|
|
32
|
+
- Swap-method decision covered both canary arms in `tests/unit/classifyProfileChange.test.ts`.
|
|
33
|
+
- Side-effects review + required second-pass review (session lifecycle + gates): `upgrades/side-effects/topic-profile.md` (reviewer concurred — no ship-blocking concerns).
|
|
34
|
+
- Spec: `docs/specs/TOPIC-PROFILE-SPEC.md` (converged round 16, approved).
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Upgrade Guide — vNEXT
|
|
2
|
+
|
|
3
|
+
<!-- assembled-by: assemble-next-md -->
|
|
4
|
+
<!-- bump: minor -->
|
|
5
|
+
|
|
6
|
+
## What Changed
|
|
7
|
+
|
|
8
|
+
Implements `docs/specs/GUARD-POSTURE-ENDPOINT-SPEC.md` (converged 5 iterations, operator-approved 2026-06-12, topic 13481 — GAP-001: the Mac Mini's SessionReaper stayed disabled for a week because no API exposed a machine's guard posture).
|
|
9
|
+
|
|
10
|
+
- **`GET /guards`** (Bearer-auth, read-only, always-on — deliberately no config gate): the full guard inventory (shared extractor ∪ declared manifest, one definition shared with the boot tripwire) with verification-graded effective states: `on-confirmed` / `on-unverified` / `on-stale` (dead tick loop — `enabled:true, lastTickAt:0` can never read green again) / `on-dry-run` / `off{dark-default | diverged-from-default}` / `diverged-pending-restart` (disk edit not yet live) / `errored` / `missing` (expected runtime never registered) / `off-runtime-divergent` (runtime contradicts an on-config — the in-memory load-shed class). Strict closed-field projection (sensitive guard config values like `alertTopicId` can never leak; Tier-1 leak tests).
|
|
11
|
+
- **`GET /guards?scope=pool`**: every registered machine accounted for by name — a posture row or a classified failure row (`timeout | unreachable | unauthorized | route-missing | no-known-url | offline | url-rejected | error`), never a silent omission, never a 500. The Bearer token is only attached to https/allowlisted peer URLs (`src/server/peerUrlGuard.ts`, operator-extensible via `multiMachine.peerUrlAllowlist`); non-recursive; rate-limited.
|
|
12
|
+
- **Heartbeat piggyback + durability**: the capacity heartbeat carries a compact posture block, bound to the authenticated sender at ingestion, aged by the RECEIVER's clock, persisted durably (`GuardPostureStore`) and reloaded at boot — a dark peer's last-known posture renders with its real age on the **Machines tab** ("guards: N on (M confirmed) · ⚠ … · as of 2d ago").
|
|
13
|
+
- **GuardPostureProbe** (SystemReviewer family): persisting anomalies (deviant offs, runtime divergence, stale, missing, errored, flapping) raise ONE aggregated, episode-deduped Attention item; dark-default offs stay quiet; offline peers evaluated from durable last-known posture, never a doomed fan-out.
|
|
14
|
+
- **Runtime self-registration**: SessionReaper, JobScheduler, SessionWatchdog, SocketDisconnect/ActiveWorkSilence/ContextWedge sentinels (+ the wedge's autoRecovery sub-row) register cheap sync `guardStatus()` getters into a boot `GuardRegistry`, reconciled against the declared manifest (`src/monitoring/guardManifest.ts` + `NOT_A_GUARD` classifications).
|
|
15
|
+
- **CI ratchet**: `scripts/lint-guard-manifest.js` — every guard-shaped boot component must be classified in the manifest or `NOT_A_GUARD` with a real reason; ships with the complete backfill (a real finding: `WorktreeReaper` is dormant/unwired).
|
|
16
|
+
- **Agent awareness + migration parity**: `generateClaudeMd()` Guards block (including the `PATCH /config` full-block warning — the one-level-deep merge erases sibling tuning) + content-sniffed `migrateClaudeMd()` migration; `/guards` registered in CAPABILITY_INDEX.
|
|
17
|
+
- Remote guard FLIPPING is deliberately out of scope (authority expansion — tracked follow-up spec `GAP-001-remote-guard-flip`).
|
|
18
|
+
|
|
19
|
+
## What to Tell Your User
|
|
20
|
+
|
|
21
|
+
- "You can now see which safety systems are genuinely working on every machine — 'on in the settings' no longer counts as 'working'. The Machines tab shows each machine's guards with honest freshness, and I get one grouped alert if something that should be on stays off."
|
|
22
|
+
|
|
23
|
+
## Summary of New Capabilities
|
|
24
|
+
|
|
25
|
+
| Capability | How to Use |
|
|
26
|
+
|-----------|-----------|
|
|
27
|
+
| Honest guard posture, one machine | `GET /guards` (Bearer) — or ask the agent "are my guards on?" |
|
|
28
|
+
| Fleet-wide posture sweep | `GET /guards?scope=pool` — every machine accounted by name |
|
|
29
|
+
| Dark-peer last-known posture | Machines dashboard tab — posture line with real age |
|
|
30
|
+
| Persisting-anomaly alert | Automatic (GuardPostureProbe → one aggregated Attention item per episode) |
|
|
31
|
+
|
|
32
|
+
## Evidence
|
|
33
|
+
|
|
34
|
+
- Tier-1: `tests/unit/monitoring/guard-posture-view.test.ts` (29 — every precedence-table edge incl. the `lastTickAt:0 → on-stale` Mini pin + projection/leak allowlists), `guard-posture-snapshot.test.ts` (10 — one-disk-read pin, dev-gate resolution), `guard-posture-probe.test.ts` (18 — episode/flap/data-source rules), `peer-url-guard.test.ts` (7), `lint-guard-manifest.test.ts` (11), `PostUpdateMigrator-guardsCapabilitySection.test.ts`.
|
|
35
|
+
- Tier-2: `tests/integration/guards-route.test.ts` (15 — auth pins, classified peer failures over real ephemeral peer servers, token-never-attached on url-rejected, receiver-clock ingestion + durable reload).
|
|
36
|
+
- Tier-3: `tests/e2e/guards-endpoint-lifecycle.test.ts` (17 — feature-alive 200 with the runtime-enrichment floor, disk-flip → `diverged-pending-restart` over real files, HTTP-level leak pin, wired source guards on every server.ts registration).
|
|
37
|
+
- Side-effects artifact: `upgrades/side-effects/guard-posture-endpoint.md` (second-pass reviewed).
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Side-Effects Review — Guard-Posture Endpoint (GET /guards)
|
|
2
|
+
|
|
3
|
+
**Version / slug:** `guard-posture-endpoint`
|
|
4
|
+
**Date:** `2026-06-12`
|
|
5
|
+
**Author:** `echo (instar-dev agent)`
|
|
6
|
+
**Second-pass reviewer:** `required (feature has "guard" in its name + touches monitoring surface) — pending, appended below before ship`
|
|
7
|
+
|
|
8
|
+
## Summary of the change
|
|
9
|
+
|
|
10
|
+
Implements the approved converged spec `docs/specs/GUARD-POSTURE-ENDPOINT-SPEC.md`: a read-only, Bearer-authenticated `GET /guards` endpoint reporting every shipped guard's honest effective state (on-confirmed / on-unverified / on-stale / on-dry-run / off{dark-default, diverged-from-default} / diverged-pending-restart / errored / missing / off-runtime-divergent), a static guard manifest + runtime GuardRegistry reconciled at boot, a compact posture block riding the existing capacity heartbeat with durable last-known persistence, a pool-scope fan-out accounting for every registered machine, a GuardPostureProbe raising one episode-deduped Attention item for persisting anomalies, a repo-CI manifest lint with complete component backfill, and the CLAUDE.md template/migration + CAPABILITY_INDEX awareness updates. Files: `src/monitoring/guardPosture.ts` (shared extractor lifted from GuardPostureTripwire — single funnel), `guardManifest.ts`, `GuardRegistry.ts`, `guardPostureView.ts`, `probes/GuardPostureProbe.ts`, routes/boot wiring, heartbeat types/sender/receiver, `scripts/lint-guard-manifest.js`, scaffold template + PostUpdateMigrator, three test tiers.
|
|
11
|
+
|
|
12
|
+
## Decision-point inventory
|
|
13
|
+
|
|
14
|
+
- `GET /guards` route — **add** — read-only projection; no block/allow decisions on any flow.
|
|
15
|
+
- `GuardPostureProbe` — **add** — detector producing Attention-queue SIGNALS (episode-deduped); takes no action, re-enables nothing.
|
|
16
|
+
- `scope=pool` rate limit — **add** — transport-layer mechanics (anti-amplification), not a judgment decision.
|
|
17
|
+
- `peerUrlGuard` (https/allowlist before Bearer forward) — **add** — hard safety guard on credential egress; refusal is VISIBLE (`url-rejected` failure row), never silent.
|
|
18
|
+
- `GuardPostureTripwire` — **pass-through** — extraction logic moved to the shared module; behavior unchanged (its tests still pass unmodified).
|
|
19
|
+
- Heartbeat ingestion — **modify** — adds a posture block bound to the AUTHENTICATED sender; never a new accept/reject decision.
|
|
20
|
+
|
|
21
|
+
## 1. Over-block
|
|
22
|
+
|
|
23
|
+
No block/allow surface on message or agent-behavior flow. Two narrow rejection surfaces exist and are scoped:
|
|
24
|
+
- The peer-URL allowlist can refuse a LEGITIMATE peer URL if an operator uses an unusual tunnel domain → the peer renders as a named `url-rejected` failure row (visible, diagnosable), and the heartbeat path still carries its posture. Mitigation documented; allowlist is a shared helper so a domain fix lands once.
|
|
25
|
+
- The pool rate limit can briefly defer a rapid sweep — partial results semantics, never a 500.
|
|
26
|
+
|
|
27
|
+
## 2. Under-block
|
|
28
|
+
|
|
29
|
+
- Posture is self-attested telemetry: a compromised machine can report all-confirmed (spec §3(g) accepted; cross-check = deep read vs heartbeat disagreement, which the probe flags).
|
|
30
|
+
- `on-unverified` covers every non-instrumented guard — a crashed non-instrumented guard is indistinguishable from a healthy one (grey, honestly labeled; instrumentation grows over time; the E2E floor pins sessionReaper + scheduler enrichment so the headline guards can't regress to grey).
|
|
31
|
+
- Staleness detection only applies to guards declaring `expectedTickMs` — an event-driven guard with a dead event loop is not detected (stated scope line, spec §2.2).
|
|
32
|
+
|
|
33
|
+
## 3. Level-of-abstraction fit
|
|
34
|
+
|
|
35
|
+
Read surface sits at the route layer over pure derivation modules (`guardPostureView.ts`) — same layering as reap-log/TokenLedger (always-on read-only observability precedent). The extraction lives in ONE shared module consumed by both the tripwire and the endpoint (single-funnel; prevents inventory drift). The probe consumes the same inventory rather than re-deriving. No parallel-to-a-smarter-gate structure exists: nothing here blocks.
|
|
36
|
+
|
|
37
|
+
## 4. Signal vs authority compliance
|
|
38
|
+
|
|
39
|
+
**Reference:** docs/signal-vs-authority.md
|
|
40
|
+
|
|
41
|
+
- [x] No — this change produces signals (posture rows, probe Attention items) consumed by humans/agents; it holds no blocking authority over any flow.
|
|
42
|
+
|
|
43
|
+
The two block-shaped pieces fall in the doc's exempt classes: the rate limit is transport-layer mechanics ("idempotency/transport" class), and the peer-URL allowlist is a hard safety guard on credential egress ("safety guards on irreversible actions" class — sending a Bearer token to a hostile URL is irreversible) whose refusals are visible rows. The spec's §2.5 de-scope keeps the WRITE lever (real authority) out of this change entirely.
|
|
44
|
+
|
|
45
|
+
## 5. Interactions
|
|
46
|
+
|
|
47
|
+
- **Tripwire:** shares the extractor; tripwire behavior unchanged (tests pass unmodified). The endpoint READS the tripwire's boot snapshot (`state/guard-posture.json`) but never writes it — no write race (tripwire writes once at boot).
|
|
48
|
+
- **Heartbeat:** posture block is additive on `MachineCapacity`; older peers ignore unknown fields (verified shape is additive). Ingestion binds to authenticated sender identity — cannot shadow another machine's row (the merge-identity rule both fan-out and heartbeat already follow).
|
|
49
|
+
- **Attention queue:** probe emits through the existing budgeted funnel with a stable `healthKey` (episode dedup) — aggregated single item, P17-compliant; cannot topic-flood (the breaker is upstream of every attention emit).
|
|
50
|
+
- **Double-fire with tripwire:** tripwire alarms on boot TRANSITIONS; probe alarms on persisting STEADY-STATE anomalies. Distinct triggers, complementary by design (spec §2.6); both reference the same inventory so they can't disagree about what a guard is.
|
|
51
|
+
- **No shadowing:** `/guards` adds no middleware ahead of existing checks; Bearer middleware unchanged.
|
|
52
|
+
|
|
53
|
+
## 6. External surfaces
|
|
54
|
+
|
|
55
|
+
- New authenticated API surface (`GET /guards`) — operationally sensitive (attack-timing oracle); contained per spec §3: Bearer-only, never on exemption lists, never on /health//ping/signed-URL surfaces; pool fan-out forwards the token ONLY to https/allowlisted peer URLs. The `scope=pool` rate limit answers 429 when exceeded (transport mechanics); per-guard getter errors surface in-process messages only (never peer/TLS internals — those are enum-normalized in failure rows).
|
|
56
|
+
- Heartbeat payload grows by a few hundred bytes (bounded by manifest size); replicated to operator-paired machines only — accepted, contained exposure (spec §3(f)). The sender computes the block per 30s beat (one disk config read + in-memory inventory build, try/caught; a failed compute omits the block for that beat).
|
|
57
|
+
- TWO new durable machine-local state files (both registered in the State-Coherence Registry + annotated at their write sites, per second-pass review): `state/guard-posture-peers.json` (GuardPostureStore — durable last-known peer posture with receiver-side receipt time) and `state/guard-posture-episodes.json` (GuardPostureProbe episode state). The pre-existing tripwire snapshot `state/guard-posture.json` was registered alongside. The `guardPosture`/`guardPostureReceivedAt` fields are additive on the in-memory/API `MachineCapacity` view only — older readers ignore them.
|
|
58
|
+
- CLAUDE.md template grows a Guards block; existing agents receive it via content-sniffed `migrateClaudeMd()` (Migration Parity); includes the PATCH /config full-block warning (interim hazard containment for the de-scoped write lever).
|
|
59
|
+
- Dashboard Machines tab shows posture summary + age.
|
|
60
|
+
|
|
61
|
+
## 7. Rollback cost
|
|
62
|
+
|
|
63
|
+
Pure code change at the route/probe/dashboard layer: revert and ship a patch. Heartbeat block is additive — peers on the old version ignore it; durable record fields are ignored by old readers (no data migration needed either way). The CLAUDE.md template section requires a removal migration if rolled back (stated in spec §7 — Migration Parity cuts both ways). No agent state repair.
|
|
64
|
+
|
|
65
|
+
## Conclusion
|
|
66
|
+
|
|
67
|
+
The build implements a read-only observability surface whose design center is honesty layering (config-on ≠ working). The review confirms: no brittle logic holds blocking authority; both rejection surfaces are exempt-class with visible refusals; the heaviest risks are information-disclosure-shaped and carry shipping-dependency mitigations (projection allowlist + leak tests, URL allowlist before token forward, sender-bound ingestion). Clear to ship once all three test tiers are green and the second-pass reviewer concurs.
|
|
68
|
+
|
|
69
|
+
## Second-pass review (if required)
|
|
70
|
+
|
|
71
|
+
**Reviewer:** independent reviewer subagent (adversarial audit, 2026-06-12)
|
|
72
|
+
**Independent read of the artifact: concern raised → resolved → concur**
|
|
73
|
+
|
|
74
|
+
Initial verdict raised three concerns, all resolved before ship:
|
|
75
|
+
- *Unregistered durable state files* — `state/guard-posture-peers.json` and `state/guard-posture-episodes.json` were not in the State-Coherence Registry (the lint's documented variable-path blind spot). RESOLVED: both registered + inline `/* state-registry: ... */` annotations at the write sites; the pre-existing tripwire snapshot registered alongside.
|
|
76
|
+
- *§6 wording misdescribed durability* (claimed durable-machine-record fields; actually a separate store file). RESOLVED: §6 corrected to name both files and the in-memory-only capacity fields.
|
|
77
|
+
- *`deepReadPeer` implemented but unwired* (a silent scope reduction). RESOLVED: wired in server.ts — plain `GET /guards` against the peer, token only past the peerUrlGuard, online-peers-with-stale/missing-posture only.
|
|
78
|
+
|
|
79
|
+
Reviewer affirmation on the core questions: "No brittle logic holds blocking authority over message flow, session lifecycle, or dispatch — the only two rejection surfaces are exactly the exempt classes the artifact claims. All §3 shipping dependencies are genuinely implemented AND tested, not just claimed. Once the state-registry registration and the §6 wording fix land, I concur with shipping." Both landed; concurrence stands.
|
|
80
|
+
|
|
81
|
+
## Verification phase (LARGE build, 5 independent reviewers)
|
|
82
|
+
|
|
83
|
+
- **Spec-correctness**: all 8 acceptance criteria VERIFIED with code+test locations; precedence table matches §2.2 exactly. No findings.
|
|
84
|
+
- **Security**: IPv6 private ranges were over-blocked (safe direction) — now granted LAN trust (loopback/ULA/link-local) with boundary tests; redirect-safety rationale documented (Fetch strips Authorization cross-origin — deliberate). All ten threat-model rows verified implemented+tested.
|
|
85
|
+
- **Scalability**: REAL BUG fixed — store write-on-change compared `generatedAt` (always fresh → wrote every beat); now field-wise semantic comparison. Heartbeat posture compute now mtime-caches the resolved-config snapshot (was 2× structuredClone per 30s beat). 90-day TTL bounds the durable store; deep merge depth-capped at 64.
|
|
86
|
+
- **Test-quality**: added getter-invocation wiring spy, route-level one-read pin, async-getter-structurally-unusable pin (the sync-enforcement mechanism behind <100ms), staleness boundary at exactly 5×, dryRun override precedence, snapshot-unavailable+runtime-divergent combination.
|
|
87
|
+
- **Integration/regression**: no blockers; single-machine no-op verified; boot-order/TDZ verified safe. Fixes: first-failure logging for the heartbeat posture compute, explicit pool-not-wired return in deepReadPeer, defensive dashboard math on partial posture blocks.
|
|
88
|
+
|
|
89
|
+
## Evidence pointers
|
|
90
|
+
|
|
91
|
+
- `.instar/state/build/must-haves.md` (truths T1–T18), `threats.md` (STRIDE T-01…T-10), `plan.md`
|
|
92
|
+
- Tier-1: `tests/unit/monitoring/guard-posture-view.test.ts`, `guard-posture-snapshot.test.ts` (48+10 green at step 2)
|
|
93
|
+
- Spec: `docs/specs/GUARD-POSTURE-ENDPOINT-SPEC.md` (approved 2026-06-12, topic 13481)
|
|
94
|
+
|
|
95
|
+
## Addendum — agent-awareness migration collision fix
|
|
96
|
+
|
|
97
|
+
The pool-sessions-visibility CLAUDE.md migration's idempotency sniff was the bare string `scope=pool`; the new Guards section (which mentions `/guards?scope=pool`) would have permanently satisfied it, silently blocking that migration for any agent carrying the Guards block. Tightened to the route-qualified `sessions?scope=pool` with its test updated — found by the awareness-block test pass.
|
|
98
|
+
|
|
99
|
+
## Addendum — CI lint + ratchet compliance
|
|
100
|
+
|
|
101
|
+
`scripts/lint-guard-manifest.js` (+ unit test) wired into the composite lint chain; complete NOT_A_GUARD backfill (33 entries; one real finding: WorktreeReaper has no importer — dormant code, tracked). PrincipalGuard's reason reworded to dodge an llm-attribution-ratchet scanner false positive; the four new guard-path catch blocks carry @silent-fallback-ok justifications (no-silent-fallbacks ratchet stays at baseline).
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
Side-Effects Review Artifact — Topic Profile.
|
|
3
|
+
Driven by docs/specs/TOPIC-PROFILE-SPEC.md (review-convergence 2026-06-11; approved: true).
|
|
4
|
+
-->
|
|
5
|
+
|
|
6
|
+
# Side-Effects Review — Topic Profile (sticky per-topic framework / model / thinking-mode)
|
|
7
|
+
|
|
8
|
+
**Version / slug:** `topic-profile`
|
|
9
|
+
**Date:** `2026-06-12`
|
|
10
|
+
**Author:** `echo`
|
|
11
|
+
**Second-pass reviewer:** `echo (dedicated reviewer subagent — session lifecycle + gates, see below)`
|
|
12
|
+
|
|
13
|
+
## Summary of the change
|
|
14
|
+
|
|
15
|
+
Topic Profile gives every conversation topic a durable, sticky profile that pins its BASELINE framework (`claude-code`/`codex-cli`/…), model (an explicit id OR a tier — never both), and thinking depth (`off`/`low`/`medium`/`high`/`max`). Pins survive restarts and follow the topic. The profile is resolved at session spawn; when a pin changes, an orchestrator (`TopicProfileOrchestrator`) classifies the change (`classifyProfileChange`) and picks the GENTLEST swap path: a within-framework Claude model-tier change on a confirmed-idle session swaps in-flight (zero loss, only when the §11 canary verifies an independent thinking-control read — which v1 ships OFF, so it degrades to resume); otherwise a kill + `claude --resume` (none-loss) or, when no resume is capturable, a CONTINUATION (recent-history + memory). The conversational surface is PRIMARY ("use codex here" / "pin this topic to Fable" via the propose-confirm ingress); the `/topic-profile` HTTP route + `/topic` command are operator/power-user surfaces. The feature ships DARK behind a dev-agent gate (`resolveDevAgentGate`, dryRun default true) — the fleet serves 503.
|
|
16
|
+
|
|
17
|
+
Primary files: `src/core/TopicProfileStore.ts`, `TopicProfileResolver.ts`, `TopicProfileOrchestrator.ts`, `TopicProfileTransferCarrier.ts`, `classifyProfileChange.ts`, `CodexResumeMap.ts`, `topicProfileIngress.ts`, `topicProfileWriteSurface.ts`, `topicProfileValidation.ts`, `slackRefreshBinding.ts`; wiring in `src/commands/server.ts` (composition root) + `src/server/routes.ts` + `AgentServer.ts`; `SessionRefresh.ts` (§10.5 Slack respawn); migrations in `PostUpdateMigrator.ts`; awareness in `scaffold/templates.ts`; classification in `server/CapabilityIndex.ts`.
|
|
18
|
+
|
|
19
|
+
## Decision-point inventory
|
|
20
|
+
|
|
21
|
+
- `classifyProfileChange()` (swap-method decision) — **add** — pure function: maps (lastApplied, resolved, idle/canary/resume verification state) → `none | in-flight | resume | continuation`, with protected-session deferral. The source of truth; tested across every §7/§11 matrix row both canary arms.
|
|
22
|
+
- `TopicProfileOrchestrator` §8 kill/respawn orchestration — **add** — debounced, globally-capped (K=2) respawn engine; protected sessions never profile-killed; busy/autonomous sessions defer (switch-now overrides busy, never protection); §10.4 circuit breaker parks the pin + reverts after N attributable failures.
|
|
23
|
+
- Write-surface operator gate — **add** — every profile WRITE requires the topic's verified bound operator + the `X-Instar-Request` intent header (route) / authorized-sender (ingress); deny-by-default.
|
|
24
|
+
- Dev-agent dark gate (`resolveDevAgentGate`) — **add** — the whole feature resolves LIVE per-call; dark arm returns 503 (route) / no-op (orchestrator).
|
|
25
|
+
- §8(2) resume-map write-gate chokepoint — **modify** — ALL `_topicResumeMap` writers now pass through `orchestrator.claudeResumeWriteGate(topicId)` so a park/unpark during an in-flight swap can't be clobbered.
|
|
26
|
+
- Obligation-7 ingress `switch-now` → `orchestrator.handleSwitchNow` bridge — **add** — routes an operator's "switch now" reply to the orchestrator's own armed confirm when no write-surface slot is armed (precedence-preserving).
|
|
27
|
+
- Migrations: CLAUDE.md awareness section, additive config defaults, `/topic-profile` capability classification, framework-shadow markers — **add** — Migration-Parity for existing agents.
|
|
28
|
+
- Legacy `topic-frameworks.json` mirror — **modify (read-only seed)** — one-directional: the new store seeds FROM it once; the store never writes back. Full retirement is deferred and tracked (CMT-1368).
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 1. Over-block
|
|
33
|
+
|
|
34
|
+
**What legitimate inputs does this change reject that it shouldn't?**
|
|
35
|
+
|
|
36
|
+
The only block/allow surface is the profile WRITE gate. Concrete over-block shapes:
|
|
37
|
+
- A legitimate operator says "use codex here" in a topic that has NO verified bound operator yet → the write is refused (403 `no-bound-operator`). This is intentional (Know-Your-Principal: an unverified principal can't set routing), but it IS an over-block in the narrow sense that a real operator's intent is declined until their identity is bound from an authenticated send. Mitigation: operator binding is automatic from the authenticated sender, so in practice the first authorized message binds them; the refusal only fires for a genuinely unbound topic.
|
|
38
|
+
- A `/topic-profile` route write without the `X-Instar-Request` header → 403. Intentional CSRF-class guard; a same-origin dashboard/agent caller always sends it.
|
|
39
|
+
- A malformed topic key (non-numeric) at the route boundary → 400 (clamped). Correct.
|
|
40
|
+
|
|
41
|
+
No conversational MESSAGE is ever blocked — a non-trigger turn falls straight through to normal conversation; only an explicit profile-trigger phrase from an authorized sender is acted on.
|
|
42
|
+
|
|
43
|
+
---
|
|
44
|
+
|
|
45
|
+
## 2. Under-block
|
|
46
|
+
|
|
47
|
+
**What failure modes does this still miss?**
|
|
48
|
+
|
|
49
|
+
- The §11 thinking-control canary ships OFF (`claudeThinkingControlAvailable:false`), so the orchestrator NEVER claims an in-flight thinking swap it can't verify — it degrades to resume. This is deliberately under-claiming (the safe direction); the "miss" is that a thinking-mode change always costs a respawn rather than a zero-loss in-flight swap, until the canary is built. Disclosed in the swap-quality matrix.
|
|
50
|
+
- `readIdle` is three-valued and fails toward BUSY (an unconfirmed pane read defers the kill). A session that is actually idle but reads `unconfirmed` will defer a respawn to the next tick — a latency miss, never a wrong kill.
|
|
51
|
+
- The in-flight swap, on an `unconfirmed` swap result, never guesses again — it falls back to kill+resume on the next confirmed-idle window. A swap that genuinely succeeded but reported `unconfirmed` would cost an unnecessary respawn (none-loss), not a wrong state.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 3. Level-of-abstraction fit
|
|
56
|
+
|
|
57
|
+
**Is this at the right layer?**
|
|
58
|
+
|
|
59
|
+
The swap DECISION is a pure low-level classifier (`classifyProfileChange`) — cheap, deterministic, fully unit-tested both arms. The ORCHESTRATION (kill/respawn/in-flight/defer) is a stateful authority that consumes that classification plus live idle/verification reads. This is the correct split: the brittle part (which swap method) is a pure function with no side effects; the authority part (when to actually kill) layers protection/busy/breaker disciplines on top and is the only thing that touches sessions. It FEEDS the existing session-spawn path (`spawnSessionForTopic`) and the existing model-swap route (`ModelSwapService.swap`) rather than re-implementing them; it USES the existing `_topicResumeMap` (gated at one chokepoint) rather than a parallel resume store. No lower-level primitive is re-implemented.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 4. Signal vs authority compliance
|
|
64
|
+
|
|
65
|
+
**Required reference:** [docs/signal-vs-authority.md](../../docs/signal-vs-authority.md)
|
|
66
|
+
|
|
67
|
+
**Does this change hold blocking authority with brittle logic?**
|
|
68
|
+
|
|
69
|
+
- [x] No — this change has no block/allow surface on the hot path. A profile change is a ROUTING decision (P2: "a profile change is a routing decision, never a block"). The orchestrator produces RESPAWNS, never a block of a user message or an operation.
|
|
70
|
+
|
|
71
|
+
The one allow/deny surface (the write gate) is not brittle: it is the existing verified-operator binding (an authenticated-sender fact, not a heuristic) plus a static intent-header check. No LLM, no brittle classifier owns any block authority. The orchestrator's kill decisions are disciplined by hard invariants (protected never killed — fails CLOSED to protected on a read fault; busy defers; breaker parks), not by a guess.
|
|
72
|
+
|
|
73
|
+
---
|
|
74
|
+
|
|
75
|
+
## 5. Interactions
|
|
76
|
+
|
|
77
|
+
- **Shadowing:** the §8(2) resume-map write-gate sits in front of EVERY `_topicResumeMap` writer. Verified it gates (park/unpark coordination) without dropping writes — an ungated write during an in-flight swap was the clobber risk; the chokepoint serializes it. The ingress `switch-now` bridge runs only in the empty-write-surface-slot branch, so it never shadows the propose-confirm/reapply handlers (precedence preserved; pinned by `topic-profile-server-wiring.test.ts`).
|
|
78
|
+
- **Double-fire:** the spawn-path success recorder is guarded by `!_orchestratorSpawnInFlight.has(topicId)` so an orchestrator-initiated respawn does not double-count into the §10.4 breaker (which it is mid-evaluating). Verified.
|
|
79
|
+
- **Races:** the store is a single-writer CAS (`mutate()`); concurrent writes serialize. Protected-session reads fail CLOSED. The 30s orchestrator tick and the carrier tick are unref'd and best-effort (a fault is swallowed and retried next tick — `@silent-fallback-ok` justified). Disclosures route through the adapter directly, bypassing the `/telegram/reply` exact-duplicate window, and carry an audit sequence so consecutive notices are never byte-identical (interaction with the duplicate-message suppression — confirmed non-colliding).
|
|
80
|
+
- **Feedback loops:** none. A respawn re-resolves the (now-updated) pin and applies it once; `recordApplied` marks it so the next reconcile sees convergence, not a re-trigger.
|
|
81
|
+
|
|
82
|
+
---
|
|
83
|
+
|
|
84
|
+
## 6. External surfaces
|
|
85
|
+
|
|
86
|
+
- **Other agents (same machine / mesh):** adds one MeshRpc verb `topic-profile-pull` (capability-gated; a peer must advertise it). The transfer carrier pulls a topic's profile at acquire-time; a local durable write cancels a pending REPLACE pull. No effect on peers that don't advertise the capability.
|
|
87
|
+
- **Install base (existing agents):** receives the CLAUDE.md awareness section, additive config defaults (add-missing-only), the `/topic-profile` capability classification, and framework-shadow markers — all via `PostUpdateMigrator` (idempotent). New agents get them via `init`.
|
|
88
|
+
- **External systems:** Telegram disclosures (the §8 propose-confirm / switch-now / breaker notices) post to the topic via the adapter. No Slack/GitHub/Cloudflare surface beyond the existing §10.5 Slack session-refresh path.
|
|
89
|
+
- **Persistent state:** new additive files — `state/topic-profiles.json` (the pin store), `state/topic-profile-orchestrator.json` (orchestrator slots), `logs/`-side audit appends (`appendTopicProfileAudit`). The legacy `topic-frameworks.json` is read ONLY (one-directional seed). No existing file's schema changes.
|
|
90
|
+
- **Timing:** a profile change on a busy/autonomous session defers to the next idle window — user-visible only as "I'll apply the switch the moment it goes idle, or say 'switch now'."
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
## 7. Rollback cost
|
|
95
|
+
|
|
96
|
+
- **Hot-fix:** the feature ships DARK behind `resolveDevAgentGate` + `dryRun:true`. The instant back-out is config (no code revert needed): the dark arm already serves 503 / no-op on the fleet. A code revert is a clean patch — the new modules are additive; the only modification to existing hot paths is the resume-map write-gate chokepoint (reverts to ungated) and the ingress switch-now branch (reverts to the plain no-op reply).
|
|
97
|
+
- **Data migration:** none required. The new state files are additive and self-initializing; deleting them resets profiles to defaults with no corruption. The legacy mirror is untouched (read-only).
|
|
98
|
+
- **Agent state repair:** none — existing agents that received the migration keep an awareness section + inert config defaults that no-op while the gate is dark.
|
|
99
|
+
- **User visibility:** none during rollback — a dark feature going darker is invisible; a dev-agent that had it enabled would simply stop applying new pins (existing sessions unaffected; pins persist in the store for when it re-enables).
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Conclusion
|
|
104
|
+
|
|
105
|
+
This review produced no design changes to the core orchestration (it was already converged over 16 rounds), but the integrating session DID change one thing as a direct result of re-examining the deferral surface: obligation 7 (the ingress `switch-now` confirm bridge) was found to be a genuine dead-end (the orchestrator disclosed "say 'switch now'" while the ingress replied "nothing pending") and was BUILT rather than shipped deferred — precedence-preserving, so no existing confirm flow changed behavior. The feature is clear to ship: it holds no brittle block authority (a profile change is a routing decision), the one allow/deny surface is the existing verified-operator gate, every kill decision is disciplined by hard invariants that fail in the safe direction, and the whole feature is dark-gated with a config-only back-out. Residual deferrals (legacy-mirror retirement, maturation-track sink, the §11 in-flight thinking canary) are tracked via durable commitments CMT-1368/CMT-1369 and the spec's §11 contingency.
|
|
106
|
+
|
|
107
|
+
---
|
|
108
|
+
|
|
109
|
+
## Second-pass review (if required)
|
|
110
|
+
|
|
111
|
+
**Reviewer:** echo (dedicated reviewer subagent — required: the change touches session lifecycle (kill/respawn) AND gate/auth surfaces)
|
|
112
|
+
**Independent read of the artifact: concur** — I traced the real code on all six review axes and the artifact's safety claims hold; the change is shippable with the minor advisory follow-ups noted below (none ship-blocking).
|
|
113
|
+
|
|
114
|
+
Evidence verified against the code:
|
|
115
|
+
|
|
116
|
+
- **Session-lifecycle safety (PASS).** Protection is a hard precondition. `respawnPhase` checks `classification.protectedDeferral` and defers (`TopicProfileOrchestrator.ts:943-948`) BEFORE it ever computes `switchNow` (`:950`) or reaches the kill path (`:993+`), so "switch now" structurally cannot kill a protected session. `protectedDeferral` is sourced from `session.isProtected` in the classifier (`classifyProfileChange.ts:157, 74-75`), and the dep FAILS CLOSED: `isProtectedSession` returns `true` on a read fault (`server.ts:13361-13368`, `catch { return true }`). The global respawn cap K is real and enforced — `maxConcurrentProfileRespawns: 2` (`server.ts:13386`) gates the FIFO drain loop (`TopicProfileOrchestrator.ts:794`). An `unconfirmed` idle read defers rather than kills: `readIdle` returns `'unconfirmed'` on a null tail (`server.ts:13260`), and at kill time `deferUntilIdle = busyish = !confirmedIdle` treats unconfirmed as busy (`classifyProfileChange.ts:151-153`, `:976` defers). The in-flight (no-kill) row also honors `protectedDeferral` (`:943` runs before the in-flight branch at `:953`).
|
|
117
|
+
|
|
118
|
+
- **Gate / auth surface (PASS).** Every write requires a verified bound operator: `authorize()` refuses operator writes when unbound or sender≠bound-operator, and refuses token writes when no operator is bound (`topicProfileWriteSurface.ts:697-734`). The HTTP route is token-trust (`principal: { kind: 'token' }`, `routes.ts:5467`) with body `updatedBy` ignored by construction. The dark gate is consulted LIVE per-call, not a cached literal: `regime`/`getConfig` are closures calling `resolveDevAgentGate(cfg?.enabled, config)` on every invocation (`server.ts:3877-3883, 13377-13390`), and `resolveDevAgentGate` is a pure `explicitEnabled ?? !!config.developmentAgent` over the live config object (`devAgentGate.ts:40-45`) → DARK on the fleet. The route refuses without the intent header — `requireTopicProfileWrite` returns 403 when `req.headers['x-instar-request'] !== '1'` (`routes.ts:5432-5433`), applied to every mutating `/topic-profile/*` route.
|
|
119
|
+
|
|
120
|
+
- **§8(2) resume-map write-gate chokepoint (PASS).** `_topicResumeMap.setWriteGate(...)` is wired to `orchestrator.claudeResumeWriteGate` (`server.ts:13412-13414`). The gate is enforced INSIDE the map, not at callsites: `TopicResumeMap.save()` early-returns on a refused gate (`TopicResumeMap.ts:181`) and `refreshResumeMappings()` skips refused topics (`:347`). Since the ~20 `_topicResumeMap.save()` callsites in server.ts all funnel through that one method, the "ALL writers pass through the gate" claim is true — I found no `save`/`refreshResumeMappings` path that bypasses `gateAllows`.
|
|
121
|
+
|
|
122
|
+
- **Obligation-7 switch-now bridge (PASS, precedence-preserving).** `case 'switch-now'` peeks the write-surface slot FIRST (`server.ts:1593`); only the empty-slot branch (`!armedSlot`) consults `_topicProfileOrchestrator.handleSwitchNow` (`:1603-1606`); an armed write-surface slot routes to `handleProfileConfirm` UNCHANGED (`:1612`). `handleSwitchNow` only fires a slot whose `kind === 'switch-now'` (`:1224-1231`), and `executeSwitchNow` sets `switchNowOverride` but the respawn phase still re-checks protection first — so the bridge cannot override protection.
|
|
123
|
+
|
|
124
|
+
- **Signal-vs-authority (PASS).** The only allow/deny surface is the write gate, which is a verified-operator fact + a static `x-instar-request` header check — no LLM, no heuristic owns block authority on the hot path. Every kill decision is disciplined by hard invariants (protected fail-closed; busy/unconfirmed defer; breaker parks), consistent with the artifact's §4 claim.
|
|
125
|
+
|
|
126
|
+
Advisory follow-ups (NOT ship-blocking; the feature is dark-gated):
|
|
127
|
+
|
|
128
|
+
- **Codex resume gate is defined but unwired.** `orchestrator.codexResumeWriteGate` (`TopicProfileOrchestrator.ts:1524`) is never installed on `_codexResumeMap` — `CodexResumeMap` has no `setWriteGate` mechanism at all, and `_codexResumeMap` has zero external save callsites in server.ts (it is orchestrator-private, written only by `captureAtKill` under the respawn lock). So there is no actual mid-switch re-poison vector to gate today, and the absence is SAFE — but the artifact's §8(2) framing ("ALL resume-map writers pass through the gate") is precisely true only for the Claude map. Recommend a one-line note in the build report that the codex gate is dormant-by-design until codex resume is captured on the natural spawn path (the matching obligation to CMT-1368's codex work).
|
|
129
|
+
|
|
130
|
+
- **`gateAllows` fails OPEN on a gate throw** (`TopicResumeMap.ts:163-172`, `catch { return true }`). This is the deliberate "a broken gate must not silence resume capture" direction and is correctly distinct from the session-kill protection (which fails CLOSED). Worth one explicit sentence in the artifact's §2/§4 so the two opposite fail-directions (resume-write fails-open vs kill-protection fails-closed) are not conflated by a future reader. No code change needed.
|
|
131
|
+
|
|
132
|
+
Both follow-ups are documentation precision, not behavior defects. No protection-bypass, auth hole, or data-loss race found.
|
|
133
|
+
|
|
134
|
+
---
|
|
135
|
+
|
|
136
|
+
## Evidence pointers
|
|
137
|
+
|
|
138
|
+
- Build: `pnpm build` EXIT 0 (clean).
|
|
139
|
+
- Tests: unit tier green; integration 278 files / 2592 passed; topic-profile e2e lifecycle 8/8; my changed unit files 285/285 (`TopicProfileOrchestrator`, `capabilities-discoverability`, `topic-profile-server-wiring`, `feature-delivery-completeness`, `no-silent-fallbacks` ratchet 5/5).
|
|
140
|
+
- Wiring integrity: `tests/unit/topic-profile-server-wiring.test.ts` (20 assertions over the composition root — construction, object-identity late-bind, real-deps-not-noops, lifecycle hooks, carrier mesh verb, the §11 conservative-canary flags, the obligation-7 bridge).
|
|
141
|
+
- Canary both arms: `tests/unit/classifyProfileChange.test.ts` (canary-passed→in-flight, canary-off→resume, idle-unconfirmed→never-in-flight, cross-model/level/off↔on/codex-rollout rows).
|
|
142
|
+
- Recovery ledger: `docs/specs/reports/topic-profile-BUILD-PROGRESS.local.md`.
|