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
|
@@ -0,0 +1,533 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TopicProfileTransferCarrier — the §5.3 transfer-follow carrier
|
|
3
|
+
* (TOPIC-PROFILE-SPEC §5.3, "pull-at-ACQUIRE — a named prerequisite sub-task").
|
|
4
|
+
*
|
|
5
|
+
* A Topic Profile is STICKY OPERATOR INTENT and must follow the topic across a
|
|
6
|
+
* session-pool move. Verified on target: `POST /pool/transfer` carries no
|
|
7
|
+
* payload and has no receive-side hook, and topics also change machines
|
|
8
|
+
* through paths that never run the planner (hard failover, quota-aware
|
|
9
|
+
* placement, lease movement). So the carriage hook lives at the
|
|
10
|
+
* **ownership-ACQUIRE chokepoint**: when THIS machine acquires a topic, it
|
|
11
|
+
* pulls the topic's profile entry from the previous owner over the existing
|
|
12
|
+
* Ed25519 machine-auth channel (the `topic-profile-pull` MeshRpc verb) —
|
|
13
|
+
* covering planner transfers, failovers, and placement moves with ONE
|
|
14
|
+
* mechanism.
|
|
15
|
+
*
|
|
16
|
+
* Spec discipline implemented here:
|
|
17
|
+
* - **Batch + latency bounds** (§5.3 round-5 scalability): acquires arriving
|
|
18
|
+
* within the batching window coalesce into ONE pull per previous-owner
|
|
19
|
+
* peer carrying all topics acquired from it; the durable retry is keyed
|
|
20
|
+
* per (peer, batch) with backoff — never N independent per-topic retries.
|
|
21
|
+
* Defaults-with-disclosure notices aggregate into one summary per landing.
|
|
22
|
+
* - **Fully async**: `onTopicAcquired` is fire-and-forget — the spawn NEVER
|
|
23
|
+
* waits (the strong form of the §5.3 ~1-2s budget); reconcile-on-pull-
|
|
24
|
+
* landing handles late arrival.
|
|
25
|
+
* - **Unreachable previous owner**: the pull is filed DURABLY
|
|
26
|
+
* (`state/topic-profile-pending-pulls.json`) before the first attempt, so
|
|
27
|
+
* a crash/restart never loses it; while it is pending, resolution keeps
|
|
28
|
+
* using the LOCAL entry (`hasPendingPull` is the staleness signal for the
|
|
29
|
+
* resolution layer's disclosure).
|
|
30
|
+
* - **Late pull never clobbers a fresher local operator write** (§5.3
|
|
31
|
+
* rounds 5-8, event-ordered and clock-free): a local operator (or
|
|
32
|
+
* token-trust HTTP) write DURABLY AMENDS the pending-pull record itself —
|
|
33
|
+
* removes that topic from the (peer, batch) retry entry — via
|
|
34
|
+
* `onLocalWriteDurable`, which the write surface calls ONLY after the
|
|
35
|
+
* triggering mutate's flush has durably landed (`store.mutate` resolves
|
|
36
|
+
* only then, so "after await mutate" IS the contract; a flush-refused
|
|
37
|
+
* write throws and cancels nothing). The amendment survives restarts
|
|
38
|
+
* exactly as well as the pull it cancels. The apply path re-reads the
|
|
39
|
+
* durable record at landing time, so a write racing an in-flight pull
|
|
40
|
+
* cancels the REPLACE too. System-attributed writes NEVER cancel.
|
|
41
|
+
* - **updatedAt backstop** (clock-skew-aware, for pulls landing with no
|
|
42
|
+
* recorded local write event): local newer wins, ties favor local, and
|
|
43
|
+
* the pulled entry's updatedAt is clamped to ≤ the pull-receipt time for
|
|
44
|
+
* the comparison (a future-dated pulled entry is treated as older,
|
|
45
|
+
* audited). System-attributed local writes never supersede via the
|
|
46
|
+
* backstop (§5.3 round-6 — the breaker must not shed a transferred pin
|
|
47
|
+
* through a side door).
|
|
48
|
+
* - **Newly-pending supersedes older pending** for the same topic (rapid
|
|
49
|
+
* A→B→C→B re-transfers can't leave two pulls racing).
|
|
50
|
+
* - **protocol-unsupported ≠ unreachable**: a peer answering `no-handler`
|
|
51
|
+
* (its instar predates the verb) PARKS the record until
|
|
52
|
+
* `peerSupportsPull` reports support — no backoff spin against a
|
|
53
|
+
* permanent 404.
|
|
54
|
+
* - **Absent on the previous owner clears nothing** — the local entry stays
|
|
55
|
+
* (a failover-then-transfer-back must not wipe a still-valid pin).
|
|
56
|
+
* - **Receiving-machine revalidation is MANDATORY**: every landing goes
|
|
57
|
+
* through `store.replaceEntry(..., { revalidate })` (§10.2 closed-enum +
|
|
58
|
+
* framework-compat clamp; dropped fields disclosed). Provenance travels
|
|
59
|
+
* verbatim but the audit tags the landing `origin:'transfer:<machineId>'`
|
|
60
|
+
* — peer-asserted, never a locally-verified principal.
|
|
61
|
+
*
|
|
62
|
+
* The serve side (`createTopicProfilePullHandler`) is the pool-layer handler
|
|
63
|
+
* the dispatcher registers for the `topic-profile-pull` verb: it serves this
|
|
64
|
+
* machine's OWN entries (current + dry-run shadow — `previous` stays local,
|
|
65
|
+
* undo means "back to what this machine had").
|
|
66
|
+
*/
|
|
67
|
+
import fs from 'node:fs';
|
|
68
|
+
import path from 'node:path';
|
|
69
|
+
/** Upper bound on topics served per pull (a mass failover stays one request). */
|
|
70
|
+
export const MAX_TOPICS_PER_PULL = 500;
|
|
71
|
+
/**
|
|
72
|
+
* The serve-side handler for the `topic-profile-pull` MeshRpc verb — pool
|
|
73
|
+
* layer registers it on the dispatcher. Stateless; reads the authoritative
|
|
74
|
+
* in-memory cache (O(topics)). Absent entries answer `present:false` so the
|
|
75
|
+
* receiver can distinguish "no entry" (clear nothing) from a null profile.
|
|
76
|
+
*/
|
|
77
|
+
export function createTopicProfilePullHandler(deps) {
|
|
78
|
+
const cap = deps.maxTopicsPerPull ?? MAX_TOPICS_PER_PULL;
|
|
79
|
+
return (cmd) => {
|
|
80
|
+
if (!Array.isArray(cmd.topics) || cmd.topics.some((t) => typeof t !== 'string')) {
|
|
81
|
+
return { ok: false, reason: 'malformed-topics' };
|
|
82
|
+
}
|
|
83
|
+
if (cmd.topics.length > cap) {
|
|
84
|
+
return { ok: false, reason: 'too-many-topics' };
|
|
85
|
+
}
|
|
86
|
+
const entries = cmd.topics.map((topicKey) => {
|
|
87
|
+
const entry = deps.store.get(topicKey);
|
|
88
|
+
if (!entry || (entry.current === null && entry.intendedProfile === null)) {
|
|
89
|
+
return { topicKey, present: false, current: null, intendedProfile: null };
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
topicKey,
|
|
93
|
+
present: true,
|
|
94
|
+
current: entry.current ? { ...entry.current } : null,
|
|
95
|
+
intendedProfile: entry.intendedProfile ? { ...entry.intendedProfile } : null,
|
|
96
|
+
};
|
|
97
|
+
});
|
|
98
|
+
return { ok: true, entries };
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
const DEFAULT_BATCH_WINDOW_MS = 250;
|
|
102
|
+
const DEFAULT_RETRY_BACKOFF_START_MS = 30_000;
|
|
103
|
+
const DEFAULT_RETRY_BACKOFF_MAX_MS = 30 * 60_000;
|
|
104
|
+
const DEFAULT_PENDING_TTL_MS = 7 * 24 * 60 * 60_000;
|
|
105
|
+
export class TopicProfileTransferCarrier {
|
|
106
|
+
d;
|
|
107
|
+
ledgerFile;
|
|
108
|
+
/** Staged acquires per previous-owner peer, awaiting the batch window. */
|
|
109
|
+
staged = new Map();
|
|
110
|
+
batchTimers = new Map();
|
|
111
|
+
/** Single-flight per peer (attempts never overlap per peer). */
|
|
112
|
+
inFlight = new Map();
|
|
113
|
+
episodeSeq = 0;
|
|
114
|
+
/** Durable ledger cache (loaded lazily, kept in sync with every write). */
|
|
115
|
+
pulls = null;
|
|
116
|
+
constructor(deps) {
|
|
117
|
+
this.d = deps;
|
|
118
|
+
this.ledgerFile = path.join(deps.stateDir, 'state', 'topic-profile-pending-pulls.json');
|
|
119
|
+
}
|
|
120
|
+
now() {
|
|
121
|
+
return this.d.now?.() ?? new Date();
|
|
122
|
+
}
|
|
123
|
+
// ── the ACQUIRE hook (fire-and-forget, never blocks a spawn) ──────────────
|
|
124
|
+
/**
|
|
125
|
+
* THIS machine acquired ownership of a topic. Stages a pull from the
|
|
126
|
+
* previous owner; acquires within the batch window coalesce into ONE pull
|
|
127
|
+
* per peer (§5.3 batch bound). Fire-and-forget: returns immediately.
|
|
128
|
+
*/
|
|
129
|
+
onTopicAcquired(topicKey, prevOwnerMachineId) {
|
|
130
|
+
const key = String(topicKey);
|
|
131
|
+
let prev = prevOwnerMachineId;
|
|
132
|
+
if (prev === undefined) {
|
|
133
|
+
try {
|
|
134
|
+
prev = this.d.prevOwnerOf?.(key) ?? null;
|
|
135
|
+
}
|
|
136
|
+
catch (e) {
|
|
137
|
+
// @silent-fallback-ok: audited (kind:'prev-owner-lookup-failed') —
|
|
138
|
+
// the ACQUIRE hook is fire-and-forget and must never block a spawn
|
|
139
|
+
// (§5.3); with no resolvable previous owner there is nothing to pull
|
|
140
|
+
// from, and the local entry (if any) remains authoritative.
|
|
141
|
+
this.d.audit?.({ kind: 'prev-owner-lookup-failed', topic: key, error: e instanceof Error ? e.message : String(e) });
|
|
142
|
+
prev = null;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
if (!prev || prev === this.d.selfMachineId)
|
|
146
|
+
return; // nothing to pull / self-move
|
|
147
|
+
let set = this.staged.get(prev);
|
|
148
|
+
if (!set) {
|
|
149
|
+
set = new Set();
|
|
150
|
+
this.staged.set(prev, set);
|
|
151
|
+
}
|
|
152
|
+
set.add(key);
|
|
153
|
+
if (!this.batchTimers.has(prev)) {
|
|
154
|
+
const peer = prev;
|
|
155
|
+
const t = setTimeout(() => {
|
|
156
|
+
this.batchTimers.delete(peer);
|
|
157
|
+
void this.flushStagedPeer(peer).catch((e) => {
|
|
158
|
+
this.d.logger?.(`topic-profile pull flush failed for ${peer}: ${e instanceof Error ? e.message : String(e)}`);
|
|
159
|
+
});
|
|
160
|
+
}, this.d.batchWindowMs ?? DEFAULT_BATCH_WINDOW_MS);
|
|
161
|
+
t.unref?.();
|
|
162
|
+
this.batchTimers.set(peer, t);
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/** Force-drain every staged batch now (tests + shutdown). */
|
|
166
|
+
async flushStaged() {
|
|
167
|
+
const peers = [...this.staged.keys()];
|
|
168
|
+
for (const peer of peers) {
|
|
169
|
+
const t = this.batchTimers.get(peer);
|
|
170
|
+
if (t) {
|
|
171
|
+
clearTimeout(t);
|
|
172
|
+
this.batchTimers.delete(peer);
|
|
173
|
+
}
|
|
174
|
+
await this.flushStagedPeer(peer);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
async flushStagedPeer(peer) {
|
|
178
|
+
const set = this.staged.get(peer);
|
|
179
|
+
this.staged.delete(peer);
|
|
180
|
+
if (!set || set.size === 0)
|
|
181
|
+
return;
|
|
182
|
+
const topics = [...set];
|
|
183
|
+
// File the durable record FIRST (a crash between filing and the attempt
|
|
184
|
+
// re-fires on the next tick), superseding any older pending pull for
|
|
185
|
+
// these topics (§5.3 — two pulls must never race for one topic).
|
|
186
|
+
const episodeId = `${peer}:${this.now().getTime()}:${++this.episodeSeq}`;
|
|
187
|
+
this.amendLedger((pulls) => {
|
|
188
|
+
for (const rec of pulls) {
|
|
189
|
+
const before = rec.topics.length;
|
|
190
|
+
rec.topics = rec.topics.filter((t) => !set.has(t));
|
|
191
|
+
if (rec.topics.length !== before) {
|
|
192
|
+
this.d.audit?.({
|
|
193
|
+
kind: 'pull-superseded-by-newer-pull',
|
|
194
|
+
oldEpisode: rec.episodeId,
|
|
195
|
+
newEpisode: episodeId,
|
|
196
|
+
removed: before - rec.topics.length,
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
const next = pulls.filter((r) => r.topics.length > 0);
|
|
201
|
+
next.push({
|
|
202
|
+
episodeId,
|
|
203
|
+
peer,
|
|
204
|
+
topics,
|
|
205
|
+
attempts: 0,
|
|
206
|
+
nextRetryAt: 0,
|
|
207
|
+
parkedForProtocol: false,
|
|
208
|
+
createdAt: this.now().toISOString(),
|
|
209
|
+
});
|
|
210
|
+
return next;
|
|
211
|
+
});
|
|
212
|
+
this.d.audit?.({ kind: 'pull-filed', episodeId, peer, topicCount: topics.length });
|
|
213
|
+
await this.attemptPeer(peer);
|
|
214
|
+
}
|
|
215
|
+
// ── the §5.3 cancel marker (local write supersedes the pending REPLACE) ───
|
|
216
|
+
/**
|
|
217
|
+
* A LOCAL profile write for `topicKey` has DURABLY landed (the caller
|
|
218
|
+
* invokes this only after `store.mutate(...)` resolved — `mutate` resolves
|
|
219
|
+
* only after its flush is durable, so a flush-refused write throws first
|
|
220
|
+
* and cancels nothing). Operator and token-trust HTTP writes durably amend
|
|
221
|
+
* every pending-pull record to drop the topic — the pending REPLACE is
|
|
222
|
+
* CANCELLED, event-ordered and clock-free. System-attributed writes never
|
|
223
|
+
* cancel (§5.3 round-6 — the breaker can't shed a transferred pin).
|
|
224
|
+
*/
|
|
225
|
+
onLocalWriteDurable(topicKey, origin) {
|
|
226
|
+
if (origin === 'system')
|
|
227
|
+
return;
|
|
228
|
+
const key = String(topicKey);
|
|
229
|
+
// Staged (not yet filed) acquires for this topic are dropped too.
|
|
230
|
+
for (const [peer, set] of this.staged) {
|
|
231
|
+
if (set.delete(key) && set.size === 0)
|
|
232
|
+
this.staged.delete(peer);
|
|
233
|
+
}
|
|
234
|
+
const cancelledFrom = [];
|
|
235
|
+
this.amendLedger((pulls) => {
|
|
236
|
+
for (const rec of pulls) {
|
|
237
|
+
if (rec.topics.includes(key)) {
|
|
238
|
+
rec.topics = rec.topics.filter((t) => t !== key);
|
|
239
|
+
cancelledFrom.push(rec.episodeId);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
return pulls.filter((r) => r.topics.length > 0);
|
|
243
|
+
});
|
|
244
|
+
for (const episodeId of cancelledFrom) {
|
|
245
|
+
this.d.audit?.({ kind: 'pull-superseded-by-local-write', topic: key, origin, episodeId });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
// ── retry drain (tick / peer-online) ──────────────────────────────────────
|
|
249
|
+
/** Is a pull pending for this topic? (Resolution's staleness signal, §5.3.) */
|
|
250
|
+
hasPendingPull(topicKey) {
|
|
251
|
+
const key = String(topicKey);
|
|
252
|
+
if ([...this.staged.values()].some((s) => s.has(key)))
|
|
253
|
+
return true;
|
|
254
|
+
return this.loadLedger().some((r) => r.topics.includes(key));
|
|
255
|
+
}
|
|
256
|
+
/** Observability: the durable pending records (copies). */
|
|
257
|
+
pending() {
|
|
258
|
+
return this.loadLedger().map((r) => ({ ...r, topics: [...r.topics] }));
|
|
259
|
+
}
|
|
260
|
+
/**
|
|
261
|
+
* Slow tick (the server calls this every ~60s): TTL-sweep, then attempt
|
|
262
|
+
* every due record (backoff respected; parked records re-checked against
|
|
263
|
+
* the protocol handshake).
|
|
264
|
+
*/
|
|
265
|
+
async tick() {
|
|
266
|
+
const nowMs = this.now().getTime();
|
|
267
|
+
const ttl = this.d.pendingTtlMs ?? DEFAULT_PENDING_TTL_MS;
|
|
268
|
+
const expired = [];
|
|
269
|
+
this.amendLedger((pulls) => pulls.filter((r) => {
|
|
270
|
+
const created = Date.parse(r.createdAt);
|
|
271
|
+
if (Number.isFinite(created) && nowMs - created > ttl) {
|
|
272
|
+
expired.push(r);
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
return true;
|
|
276
|
+
}));
|
|
277
|
+
for (const r of expired) {
|
|
278
|
+
this.d.audit?.({ kind: 'pull-expired', episodeId: r.episodeId, peer: r.peer, topicCount: r.topics.length });
|
|
279
|
+
}
|
|
280
|
+
const duePeers = new Set(this.loadLedger()
|
|
281
|
+
.filter((r) => (r.parkedForProtocol ? this.d.peerSupportsPull?.(r.peer) === true : r.nextRetryAt <= nowMs))
|
|
282
|
+
.map((r) => r.peer));
|
|
283
|
+
for (const peer of duePeers)
|
|
284
|
+
await this.attemptPeer(peer);
|
|
285
|
+
}
|
|
286
|
+
/** A peer came back online — drain its pending pulls now (backoff bypassed). */
|
|
287
|
+
async onPeerOnline(machineId) {
|
|
288
|
+
const has = this.loadLedger().some((r) => r.peer === machineId);
|
|
289
|
+
if (!has)
|
|
290
|
+
return;
|
|
291
|
+
this.amendLedger((pulls) => {
|
|
292
|
+
for (const r of pulls) {
|
|
293
|
+
if (r.peer === machineId && !r.parkedForProtocol)
|
|
294
|
+
r.nextRetryAt = 0;
|
|
295
|
+
}
|
|
296
|
+
return pulls;
|
|
297
|
+
});
|
|
298
|
+
await this.attemptPeer(machineId);
|
|
299
|
+
}
|
|
300
|
+
// ── attempt + apply ────────────────────────────────────────────────────────
|
|
301
|
+
attemptPeer(peer) {
|
|
302
|
+
const existing = this.inFlight.get(peer);
|
|
303
|
+
if (existing)
|
|
304
|
+
return existing; // single-flight per peer
|
|
305
|
+
const run = this.attemptPeerInner(peer).finally(() => {
|
|
306
|
+
this.inFlight.delete(peer);
|
|
307
|
+
});
|
|
308
|
+
this.inFlight.set(peer, run);
|
|
309
|
+
return run;
|
|
310
|
+
}
|
|
311
|
+
async attemptPeerInner(peer) {
|
|
312
|
+
const nowMs = this.now().getTime();
|
|
313
|
+
const records = this.loadLedger().filter((r) => r.peer === peer);
|
|
314
|
+
for (const rec of records) {
|
|
315
|
+
if (rec.parkedForProtocol) {
|
|
316
|
+
if (this.d.peerSupportsPull?.(peer) !== true)
|
|
317
|
+
continue; // still parked
|
|
318
|
+
this.amendLedger((pulls) => {
|
|
319
|
+
const live = pulls.find((p) => p.episodeId === rec.episodeId);
|
|
320
|
+
if (live)
|
|
321
|
+
live.parkedForProtocol = false;
|
|
322
|
+
return pulls;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
else if (rec.nextRetryAt > nowMs) {
|
|
326
|
+
continue; // not due
|
|
327
|
+
}
|
|
328
|
+
// Pre-attempt handshake check: a known-unsupported peer parks without
|
|
329
|
+
// burning a transport round-trip (§5.3 rolling-update skew).
|
|
330
|
+
if (this.d.peerSupportsPull?.(peer) === false) {
|
|
331
|
+
this.park(rec.episodeId, peer);
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
let outcome;
|
|
335
|
+
try {
|
|
336
|
+
// Re-read the live record — a local write may have amended it while
|
|
337
|
+
// we were iterating.
|
|
338
|
+
const live = this.loadLedger().find((p) => p.episodeId === rec.episodeId);
|
|
339
|
+
if (!live || live.topics.length === 0)
|
|
340
|
+
continue;
|
|
341
|
+
outcome = await this.d.sendPull(peer, [...live.topics]);
|
|
342
|
+
}
|
|
343
|
+
catch (e) {
|
|
344
|
+
outcome = { kind: 'unreachable', detail: e instanceof Error ? e.message : String(e) };
|
|
345
|
+
}
|
|
346
|
+
if (outcome.kind === 'protocol-unsupported') {
|
|
347
|
+
this.park(rec.episodeId, peer);
|
|
348
|
+
continue;
|
|
349
|
+
}
|
|
350
|
+
if (outcome.kind === 'unreachable') {
|
|
351
|
+
const startMs = this.d.retryBackoffStartMs ?? DEFAULT_RETRY_BACKOFF_START_MS;
|
|
352
|
+
const maxMs = this.d.retryBackoffMaxMs ?? DEFAULT_RETRY_BACKOFF_MAX_MS;
|
|
353
|
+
let attempts = 0;
|
|
354
|
+
this.amendLedger((pulls) => {
|
|
355
|
+
const live = pulls.find((p) => p.episodeId === rec.episodeId);
|
|
356
|
+
if (live) {
|
|
357
|
+
live.attempts += 1;
|
|
358
|
+
attempts = live.attempts;
|
|
359
|
+
live.nextRetryAt = this.now().getTime() + Math.min(maxMs, startMs * 2 ** (live.attempts - 1));
|
|
360
|
+
}
|
|
361
|
+
return pulls;
|
|
362
|
+
});
|
|
363
|
+
this.d.audit?.({ kind: 'pull-unreachable', episodeId: rec.episodeId, peer, attempts, detail: outcome.detail });
|
|
364
|
+
continue;
|
|
365
|
+
}
|
|
366
|
+
await this.applyLanding(rec.episodeId, peer, outcome.entries);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
park(episodeId, peer) {
|
|
370
|
+
this.amendLedger((pulls) => {
|
|
371
|
+
const live = pulls.find((p) => p.episodeId === episodeId);
|
|
372
|
+
if (live)
|
|
373
|
+
live.parkedForProtocol = true;
|
|
374
|
+
return pulls;
|
|
375
|
+
});
|
|
376
|
+
this.d.audit?.({ kind: 'pull-parked-protocol-unsupported', episodeId, peer });
|
|
377
|
+
}
|
|
378
|
+
/**
|
|
379
|
+
* A pull landed. Per topic: the durable record is re-read (a local write
|
|
380
|
+
* that amended it mid-flight cancels that topic's REPLACE), ownership is
|
|
381
|
+
* re-checked (actuation authority), the updatedAt backstop runs, then the
|
|
382
|
+
* §10.2-revalidated wholesale REPLACE applies. Disclosures aggregate into
|
|
383
|
+
* ONE summary per landing.
|
|
384
|
+
*/
|
|
385
|
+
async applyLanding(episodeId, peer, entries) {
|
|
386
|
+
const live = this.loadLedger().find((p) => p.episodeId === episodeId);
|
|
387
|
+
const liveTopics = new Set(live?.topics ?? []);
|
|
388
|
+
const receiptIso = this.now().toISOString();
|
|
389
|
+
let applied = 0;
|
|
390
|
+
const disclosures = [];
|
|
391
|
+
for (const entry of entries) {
|
|
392
|
+
const key = entry.topicKey;
|
|
393
|
+
if (!liveTopics.has(key)) {
|
|
394
|
+
// Cancelled by a local write (the durable amendment) or superseded by
|
|
395
|
+
// a newer pull while this one was in flight — the REPLACE is dropped.
|
|
396
|
+
this.d.audit?.({ kind: 'pull-landing-cancelled', topic: key, episodeId, peer });
|
|
397
|
+
continue;
|
|
398
|
+
}
|
|
399
|
+
// Ownership recheck — we may no longer own the topic (re-transferred
|
|
400
|
+
// while the pull was pending); the current owner's own pull covers truth.
|
|
401
|
+
let owner = null;
|
|
402
|
+
try {
|
|
403
|
+
owner = this.d.ownerOf(key).owner;
|
|
404
|
+
}
|
|
405
|
+
catch {
|
|
406
|
+
// @silent-fallback-ok: the ownership RE-check is a best-effort guard
|
|
407
|
+
// against landing onto a re-transferred topic; a reader error means
|
|
408
|
+
// owner-UNKNOWN, which must not be treated as not-owner (that would
|
|
409
|
+
// drop a valid pull) — every landing outcome below is audited.
|
|
410
|
+
owner = null;
|
|
411
|
+
}
|
|
412
|
+
if (owner !== null && owner !== this.d.selfMachineId) {
|
|
413
|
+
this.d.audit?.({ kind: 'pull-skipped-not-owner', topic: key, episodeId, owner });
|
|
414
|
+
liveTopics.delete(key); // resolved — deliberately skipped, never retried
|
|
415
|
+
continue;
|
|
416
|
+
}
|
|
417
|
+
if (!entry.present) {
|
|
418
|
+
// Absent on the previous owner clears NOTHING — the local entry stays
|
|
419
|
+
// (a failover-then-transfer-back must not wipe a still-valid pin).
|
|
420
|
+
this.d.audit?.({ kind: 'pull-absent-on-previous-owner', topic: key, episodeId, peer });
|
|
421
|
+
liveTopics.delete(key); // resolved
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
424
|
+
// updatedAt backstop (clock-free cancel is primary; this catches pulls
|
|
425
|
+
// landing with no recorded local write event): local newer wins, ties
|
|
426
|
+
// favor local — and a pulled updatedAt clamped above the pull-receipt
|
|
427
|
+
// time (future-dated: forged or skew-ahead) is treated as OLDER than
|
|
428
|
+
// any local entry, audited (§5.3 round-6 — a peer-asserted timestamp
|
|
429
|
+
// must never outrank a genuinely fresher local pin). System-attributed
|
|
430
|
+
// local writes never supersede via the backstop.
|
|
431
|
+
const localCurrent = this.d.store.resolve(key);
|
|
432
|
+
const arrivingUpdatedAt = typeof entry.current?.updatedAt === 'string' ? entry.current.updatedAt : null;
|
|
433
|
+
if (localCurrent && arrivingUpdatedAt && !localCurrent.updatedBy.startsWith('system:')) {
|
|
434
|
+
const futureDated = arrivingUpdatedAt > receiptIso;
|
|
435
|
+
if (futureDated) {
|
|
436
|
+
this.d.audit?.({ kind: 'pull-updatedat-clamped', topic: key, episodeId, peer });
|
|
437
|
+
}
|
|
438
|
+
if (futureDated || localCurrent.updatedAt >= arrivingUpdatedAt) {
|
|
439
|
+
this.d.audit?.({
|
|
440
|
+
kind: 'pull-superseded-by-local-write',
|
|
441
|
+
topic: key,
|
|
442
|
+
origin: 'updatedAt-backstop',
|
|
443
|
+
episodeId,
|
|
444
|
+
});
|
|
445
|
+
liveTopics.delete(key); // resolved — local wins
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
// §10.2 MANDATORY receiving-machine revalidation, then wholesale REPLACE.
|
|
450
|
+
try {
|
|
451
|
+
const result = await this.d.store.replaceEntry(key, { current: entry.current, intendedProfile: entry.intendedProfile }, { revalidate: { fallbackFramework: this.d.effectiveFramework() } });
|
|
452
|
+
applied += 1;
|
|
453
|
+
this.d.audit?.({
|
|
454
|
+
kind: 'pull-applied',
|
|
455
|
+
topic: key,
|
|
456
|
+
episodeId,
|
|
457
|
+
origin: `transfer:${peer}`,
|
|
458
|
+
delta: result.delta,
|
|
459
|
+
droppedFieldCount: result.droppedFields.length,
|
|
460
|
+
});
|
|
461
|
+
for (const dropped of result.droppedFields) {
|
|
462
|
+
disclosures.push(`topic ${key}: the transferred ${dropped.field} didn't validate on this machine (${dropped.reason}) — using the default`);
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
catch (e) {
|
|
466
|
+
// FlushRefusedError etc. — the topic STAYS in the record (it remains
|
|
467
|
+
// in liveTopics); the next tick retries the pull (REPLACE is
|
|
468
|
+
// idempotent). Back the episode off so a wedged disk doesn't spin.
|
|
469
|
+
this.d.logger?.(`topic-profile REPLACE failed for ${key}: ${e instanceof Error ? e.message : String(e)}`);
|
|
470
|
+
this.amendLedger((pulls) => {
|
|
471
|
+
const liveRec = pulls.find((p) => p.episodeId === episodeId);
|
|
472
|
+
if (liveRec) {
|
|
473
|
+
const startMs = this.d.retryBackoffStartMs ?? DEFAULT_RETRY_BACKOFF_START_MS;
|
|
474
|
+
liveRec.nextRetryAt = this.now().getTime() + startMs;
|
|
475
|
+
}
|
|
476
|
+
return pulls;
|
|
477
|
+
});
|
|
478
|
+
continue;
|
|
479
|
+
}
|
|
480
|
+
liveTopics.delete(key); // resolved — applied
|
|
481
|
+
}
|
|
482
|
+
// Resolve the episode: topics remaining in liveTopics are ONLY the
|
|
483
|
+
// flush-failed ones (everything applied/skipped was deleted above) —
|
|
484
|
+
// they stay in the record for retry; the rest leave it.
|
|
485
|
+
this.amendLedger((pulls) => {
|
|
486
|
+
const liveRec = pulls.find((p) => p.episodeId === episodeId);
|
|
487
|
+
if (liveRec)
|
|
488
|
+
liveRec.topics = liveRec.topics.filter((t) => liveTopics.has(t));
|
|
489
|
+
return pulls.filter((r) => r.topics.length > 0);
|
|
490
|
+
});
|
|
491
|
+
if (applied > 0 || disclosures.length > 0) {
|
|
492
|
+
const lines = [`${applied} topic profile${applied === 1 ? '' : 's'} arrived from ${peer}.`, ...disclosures];
|
|
493
|
+
this.d.notify?.(lines.join('\n'));
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
// ── durable ledger (atomic tmp+rename, restart-proof) ─────────────────────
|
|
497
|
+
loadLedger() {
|
|
498
|
+
if (this.pulls)
|
|
499
|
+
return this.pulls;
|
|
500
|
+
try {
|
|
501
|
+
const parsed = JSON.parse(fs.readFileSync(this.ledgerFile, 'utf-8'));
|
|
502
|
+
this.pulls = Array.isArray(parsed?.pulls)
|
|
503
|
+
? parsed.pulls.filter((r) => !!r &&
|
|
504
|
+
typeof r === 'object' &&
|
|
505
|
+
typeof r.episodeId === 'string' &&
|
|
506
|
+
typeof r.peer === 'string' &&
|
|
507
|
+
Array.isArray(r.topics) &&
|
|
508
|
+
r.topics.every((t) => typeof t === 'string'))
|
|
509
|
+
: [];
|
|
510
|
+
}
|
|
511
|
+
catch {
|
|
512
|
+
/* @silent-fallback-ok: an absent/corrupt pending-pull ledger only loses queued retries for moves that already disclosed "pins reconcile when reachable" — the reflex (next acquire or onPeerOnline) re-files; never a boot failure (TOPIC-PROFILE-SPEC §5.3) */
|
|
513
|
+
this.pulls = [];
|
|
514
|
+
}
|
|
515
|
+
return this.pulls;
|
|
516
|
+
}
|
|
517
|
+
/** Apply a mutation to the ledger and persist it durably (tmp+rename). */
|
|
518
|
+
amendLedger(fn) {
|
|
519
|
+
const next = fn(this.loadLedger());
|
|
520
|
+
this.pulls = next;
|
|
521
|
+
try {
|
|
522
|
+
fs.mkdirSync(path.dirname(this.ledgerFile), { recursive: true });
|
|
523
|
+
const tmp = `${this.ledgerFile}.${process.pid}.tmp`;
|
|
524
|
+
fs.writeFileSync(tmp, JSON.stringify({ version: 1, pulls: next }, null, 2));
|
|
525
|
+
fs.renameSync(tmp, this.ledgerFile);
|
|
526
|
+
}
|
|
527
|
+
catch (e) {
|
|
528
|
+
/* @silent-fallback-ok: a failed ledger persist keeps the in-memory amendment authoritative for this process; the durable copy self-heals at the next successful amend — logged, never thrown into the acquire/write path (TOPIC-PROFILE-SPEC §5.3) */
|
|
529
|
+
this.d.logger?.(`pending-pull ledger persist failed: ${e instanceof Error ? e.message : String(e)}`);
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
//# sourceMappingURL=TopicProfileTransferCarrier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"TopicProfileTransferCarrier.js","sourceRoot":"","sources":["../../src/core/TopicProfileTransferCarrier.ts"],"names":[],"mappings":"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"}
|
|
@@ -9,10 +9,47 @@
|
|
|
9
9
|
* Storage: {stateDir}/topic-resume-map.json
|
|
10
10
|
* Entries auto-prune after 24 hours.
|
|
11
11
|
*/
|
|
12
|
+
/** §8 provenance tag — gates the none-loss claim (TOPIC-PROFILE-SPEC §7/§8). */
|
|
13
|
+
export type ResumeProvenance = 'hook' | 'mtime-fallback';
|
|
14
|
+
export interface ResumeEntry {
|
|
15
|
+
uuid: string;
|
|
16
|
+
savedAt: string;
|
|
17
|
+
sessionName: string;
|
|
18
|
+
/**
|
|
19
|
+
* §8 framework tag. Untagged legacy entries are grandfathered as
|
|
20
|
+
* 'claude-code' (provably safe — this map has only ever captured Claude
|
|
21
|
+
* JSONL UUIDs) and tagged lazily on the next write.
|
|
22
|
+
*/
|
|
23
|
+
framework?: string;
|
|
24
|
+
/** Untagged legacy entries grandfather as 'hook' (same provable basis). */
|
|
25
|
+
provenance?: ResumeProvenance;
|
|
26
|
+
/**
|
|
27
|
+
* §8 — "remove" means PARK, not delete: parked entries are ignored by
|
|
28
|
+
* resolution (get() returns null) but recoverable by the §10.4 breaker
|
|
29
|
+
* revert / §10.3 undo via unpark(). Holds the parking reason.
|
|
30
|
+
*/
|
|
31
|
+
parked?: string | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* §8 resume-writer gate (TOPIC-PROFILE-SPEC round-3 adversarial): EVERY
|
|
35
|
+
* writer — the beforeSessionKill listener, the 60s heartbeat
|
|
36
|
+
* (refreshResumeMappings), the 8s post-spawn proactive save, and the
|
|
37
|
+
* shutdown/refresh-route saves — funnels through save()/refreshResumeMappings,
|
|
38
|
+
* so a single gate at this chokepoint covers all four structurally. The gate
|
|
39
|
+
* refuses a write for a topic whose resolved profile framework is not
|
|
40
|
+
* claude-code, or whose topic is under an active mid-framework-switch
|
|
41
|
+
* suppression marker. Without it, the heartbeat would re-poison the map
|
|
42
|
+
* within a minute of a framework switch.
|
|
43
|
+
*/
|
|
44
|
+
export type ResumeWriteGate = (topicId: number) => {
|
|
45
|
+
allowed: boolean;
|
|
46
|
+
reason?: string;
|
|
47
|
+
};
|
|
12
48
|
export declare class TopicResumeMap {
|
|
13
49
|
private filePath;
|
|
14
50
|
private projectDir;
|
|
15
51
|
private tmuxPath;
|
|
52
|
+
private writeGate;
|
|
16
53
|
constructor(stateDir: string, projectDir: string, tmuxPath?: string);
|
|
17
54
|
/**
|
|
18
55
|
* Compute the Claude Code project directory name for this project.
|
|
@@ -40,15 +77,44 @@ export declare class TopicResumeMap {
|
|
|
40
77
|
* the most recent JSONL file regardless of which session it belongs to.
|
|
41
78
|
*/
|
|
42
79
|
findUuidForSession(tmuxSession: string, claudeSessionId?: string): string | null;
|
|
80
|
+
/**
|
|
81
|
+
* Install the §8 profile write-gate (TOPIC-PROFILE-SPEC). All writers are
|
|
82
|
+
* gated at this single chokepoint — the heartbeat, the kill listener, the
|
|
83
|
+
* post-spawn save and the shutdown saves all land in save()/
|
|
84
|
+
* refreshResumeMappings. No gate installed ⇒ today's behavior (ungated).
|
|
85
|
+
*/
|
|
86
|
+
setWriteGate(gate: ResumeWriteGate | null): void;
|
|
87
|
+
private gateAllows;
|
|
43
88
|
/**
|
|
44
89
|
* Persist a resume mapping before killing an idle session.
|
|
90
|
+
* Entries are framework-tagged ('claude-code' — this map only ever holds
|
|
91
|
+
* Claude JSONL UUIDs) and provenance-tagged (§8). A refused (gated) write
|
|
92
|
+
* is a silent no-op — the gate's caller owns disclosure.
|
|
45
93
|
*/
|
|
46
|
-
save(topicId: number, uuid: string, sessionName: string): void;
|
|
94
|
+
save(topicId: number, uuid: string, sessionName: string, provenance?: ResumeProvenance): void;
|
|
47
95
|
/**
|
|
48
96
|
* Look up a resume UUID for a topic. Returns null if not found,
|
|
49
97
|
* expired, or the JSONL file no longer exists.
|
|
50
98
|
*/
|
|
51
99
|
get(topicId: number): string | null;
|
|
100
|
+
/**
|
|
101
|
+
* §8 last-line guard: the spawn path REFUSES a resume id whose framework
|
|
102
|
+
* tag mismatches the resolved framework, falling to CONTINUATION with
|
|
103
|
+
* disclosure. Untagged legacy entries grandfather as 'claude-code'.
|
|
104
|
+
*/
|
|
105
|
+
getForFramework(topicId: number, resolvedFramework: string): string | null;
|
|
106
|
+
/**
|
|
107
|
+
* §8 provenance read — the none-loss rows require HOOK provenance; an
|
|
108
|
+
* mtime-fallback-only entry classifies as CONTINUATION-class loss.
|
|
109
|
+
* Untagged legacy entries grandfather as 'hook' (provably safe).
|
|
110
|
+
*/
|
|
111
|
+
getProvenance(topicId: number): ResumeProvenance | null;
|
|
112
|
+
/** Raw entry (parked included) for the §8 park/un-park machinery. */
|
|
113
|
+
getEntryRaw(topicId: number): ResumeEntry | null;
|
|
114
|
+
/** §8 — "remove" means PARK, not delete (deletion destroys the cheap recovery). */
|
|
115
|
+
park(topicId: number, reason: string): void;
|
|
116
|
+
/** §10.4 revert / §10.3 undo — un-park the entry. Returns true when un-parked. */
|
|
117
|
+
unpark(topicId: number): boolean;
|
|
52
118
|
/**
|
|
53
119
|
* Remove an entry after successful resume (prevents stale reuse).
|
|
54
120
|
*/
|