@yanlinglabs/winter-runtime-sdk 0.0.1

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.
Files changed (63) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +249 -0
  3. package/dist/directory/directory.d.ts +55 -0
  4. package/dist/directory/entries.d.ts +82 -0
  5. package/dist/directory/recovery.d.ts +49 -0
  6. package/dist/door.d.ts +247 -0
  7. package/dist/errors.d.ts +104 -0
  8. package/dist/index.d.ts +25 -0
  9. package/dist/index.js +6679 -0
  10. package/dist/messaging/attribution.d.ts +47 -0
  11. package/dist/messaging/dispatch.d.ts +78 -0
  12. package/dist/messaging/handlers.d.ts +56 -0
  13. package/dist/messaging/inbound.d.ts +110 -0
  14. package/dist/messaging/index.d.ts +39 -0
  15. package/dist/messaging/official-adapter.d.ts +36 -0
  16. package/dist/messaging/router.d.ts +101 -0
  17. package/dist/messaging/sessions.d.ts +49 -0
  18. package/dist/messaging/winter-adapter.d.ts +47 -0
  19. package/dist/native-args.d.ts +76 -0
  20. package/dist/official/adapter.d.ts +88 -0
  21. package/dist/official/aliases.d.ts +48 -0
  22. package/dist/official/auth.d.ts +117 -0
  23. package/dist/official/branding.d.ts +31 -0
  24. package/dist/official/callbacks.d.ts +143 -0
  25. package/dist/official/containment.d.ts +130 -0
  26. package/dist/official/env-allowlist.d.ts +237 -0
  27. package/dist/official/env-registry-rule.d.ts +12 -0
  28. package/dist/official/env-registry.d.ts +3 -0
  29. package/dist/official/errors.d.ts +250 -0
  30. package/dist/official/index.d.ts +31 -0
  31. package/dist/official/mcp-descriptors.d.ts +130 -0
  32. package/dist/official/options-template.d.ts +110 -0
  33. package/dist/official/spawn-proxy.d.ts +144 -0
  34. package/dist/official/spool.d.ts +80 -0
  35. package/dist/official/supervision.d.ts +49 -0
  36. package/dist/official/sweep.d.ts +65 -0
  37. package/dist/sdk.d.ts +214 -0
  38. package/dist/seams/context.d.ts +26 -0
  39. package/dist/seams/directory-store.d.ts +274 -0
  40. package/dist/seams/directory.d.ts +46 -0
  41. package/dist/seams/global-messaging.d.ts +30 -0
  42. package/dist/seams/handoff.d.ts +77 -0
  43. package/dist/seams/index.d.ts +11 -0
  44. package/dist/seams/keychain.d.ts +11 -0
  45. package/dist/seams/materialized-resume.d.ts +46 -0
  46. package/dist/seams/messaging-contract.d.ts +29 -0
  47. package/dist/seams/official-adapter.d.ts +125 -0
  48. package/dist/seams/official-sdk-shapes.d.ts +126 -0
  49. package/dist/seams/stubs.d.ts +34 -0
  50. package/dist/selection/child-runtime.d.ts +81 -0
  51. package/dist/selection/runtime-selection.d.ts +217 -0
  52. package/dist/selection/select-runtime.d.ts +213 -0
  53. package/dist/store/handoff-barrier.d.ts +238 -0
  54. package/dist/store/index.d.ts +11 -0
  55. package/dist/store/materialized-resume.d.ts +100 -0
  56. package/dist/store/pinned-probes.d.ts +17 -0
  57. package/dist/store/reconcile.d.ts +157 -0
  58. package/dist/store/temp-continuity.d.ts +92 -0
  59. package/dist/store/wiring.d.ts +250 -0
  60. package/dist/vendor-paths.d.ts +21 -0
  61. package/dist/version-matrix.d.ts +84 -0
  62. package/docs/conformance-rows.md +195 -0
  63. package/package.json +65 -0
@@ -0,0 +1,238 @@
1
+ import { type SessionKey } from "@yanlinglabs/winter-agent-sdk";
2
+ import { RuntimeSdkError } from "../errors.js";
3
+ import type { SeamContextWithDirectory } from "../seams/context.js";
4
+ import type { RuntimeDirectoryEntry } from "../seams/directory-store.js";
5
+ import type { HandoffBarrier, HandoffOutcome, HandoffPlan, HandoffStepNumber } from "../seams/handoff.js";
6
+ import type { MaterializedResumeDoor, MaterializedResumeProbeReport } from "../seams/materialized-resume.js";
7
+ import type { SerializedRuntimeAddress } from "../seams/messaging-contract.js";
8
+ import type { RuntimeKind, RuntimeSelection, SelectionInput } from "../selection/runtime-selection.js";
9
+ import { type MaterializedResumeDecoratorHandle } from "./materialized-resume.js";
10
+ import { type EngineTempLayout } from "./temp-continuity.js";
11
+ import { type SharedSessionStore } from "./wiring.js";
12
+ /** WS-05 §5.4's advertised levels. "Never silently downgrade an advertised level." */
13
+ export type CompatibilityLevel = "conversation" | "agent-state" | "full-filesystem";
14
+ /** WS-05 §12's eight steps, named once so a plan, an outcome and a test all read the same words. */
15
+ export declare const HANDOFF_STEPS: ReadonlyArray<{
16
+ step: HandoffStepNumber;
17
+ name: string;
18
+ }>;
19
+ /**
20
+ * A step's own answer. `ok: false` is "not proven", which is what makes a fork the honest offer.
21
+ *
22
+ * `producer` is step 8's only extra: WS-05 §5.4's `producerSdkVersion`/`producerEngineVersion` describe
23
+ * the runtime that will WRITE the session from now on, and only that runtime knows them (review r2,
24
+ * F11). A destination that does not report them leaves the fields OUT of the record, which is the one
25
+ * honest alternative — the first version wrote the SOURCE's versions under the destination's name.
26
+ */
27
+ export type HandoffStepReport = {
28
+ ok: true;
29
+ detail?: string;
30
+ producer?: {
31
+ sdkVersion?: string;
32
+ engineVersion?: string;
33
+ };
34
+ } | {
35
+ ok: false;
36
+ reason: string;
37
+ };
38
+ /** Structurally Lane A's `OfficialSessionHealth` (`src/official/adapter.ts`), never imported from it. */
39
+ export interface HandoffOwnerHealth {
40
+ launchedThroughProxy: boolean;
41
+ recordedLocalWriteRoot?: string;
42
+ transcriptHealth: "ok" | "repair-required";
43
+ }
44
+ /** Structurally Lane A's `HandoffEligibility` — its `officialHandoffEligibility()` produces one. */
45
+ export type HandoffEligibilityLike = {
46
+ eligible: true;
47
+ } | {
48
+ eligible: false;
49
+ reason: string;
50
+ detail: string;
51
+ };
52
+ /**
53
+ * The live runtime that owns the session today.
54
+ *
55
+ * EVERY MEMBER IS OPTIONAL EXCEPT THE THREE DRAINS, and an ABSENT owner is a supported case: an exited
56
+ * session has nothing to drain and nothing to close, which is a cold handoff rather than a failure. What
57
+ * an absent owner cannot do is REPORT — so a session with no owner also has no recorded local-write root,
58
+ * and step 4 falls back to comparing the canonical file against itself (see `compareAgainstLocalRoot`).
59
+ */
60
+ export interface HandoffSourceOwner {
61
+ readonly runtimeKind: RuntimeKind;
62
+ /** WS-14 §5/§6's session health. */
63
+ health?(): HandoffOwnerHealth | Promise<HandoffOwnerHealth>;
64
+ /** Lane A's `officialHandoffEligibility(health)`, when the owner is an official session. */
65
+ eligibility?(): HandoffEligibilityLike | Promise<HandoffEligibilityLike>;
66
+ /** §12 step 1's second half. */
67
+ stopNewTurns?(): void | Promise<void>;
68
+ /** §12 step 2. */
69
+ drainToIdleBoundary(): HandoffStepReport | Promise<HandoffStepReport>;
70
+ /** §12 step 3's SDK-stream half; the append barrier is the store's. */
71
+ drainStream(): HandoffStepReport | Promise<HandoffStepReport>;
72
+ /** §12 step 6's "close the owner gracefully". */
73
+ close(): HandoffStepReport | void | Promise<HandoffStepReport | void>;
74
+ /** `RuntimeSessionRecord.effectiveTempDir` as the source recorded it (WS-16 §4). */
75
+ readonly effectiveTempDir?: string;
76
+ }
77
+ /** What step 8 hands the destination runtime. */
78
+ export interface HandoffResumeTarget {
79
+ /**
80
+ * REQUIRED (review r4, Lane C nit 1). It was optional because an early draft could build a target
81
+ * before the directory row was read; `execute()` has read it since step 1 for several rounds, every
82
+ * construction site sets it, and a destination that cannot be told WHICH row it now owns cannot
83
+ * record anything against it.
84
+ */
85
+ address: SerializedRuntimeAddress;
86
+ runtimeKind: RuntimeKind;
87
+ /** §12 step 8: "resume the SAME backend UUID and project key". */
88
+ backendSessionId: string;
89
+ projectKey: string;
90
+ /** §12 step 8: init must confirm the same session AND level. */
91
+ compatibilityLevel: CompatibilityLevel;
92
+ /** The transcript the destination reads: the materialized copy, or the canonical file. */
93
+ resumePath: string;
94
+ /**
95
+ * The `<tmpdir>/claude-resume-<uuid>` root a `store-backed-resume` launch needs — it IS that
96
+ * generation's `CLAUDE_CONFIG_DIR` (WS-14 §1). Present for a `claude-agent` destination, absent for
97
+ * a Winter one, which reads the canonical store directly.
98
+ */
99
+ stagingRoot?: string;
100
+ /** WS-14 §1's two launch profiles, as the destination's own plan will declare it. */
101
+ profile?: "fresh-spool" | "store-backed-resume";
102
+ /** WS-05 §9.1's post-handoff `effectiveTempDir`. */
103
+ effectiveTempDir: string;
104
+ door: MaterializedResumeDoor;
105
+ selection: RuntimeSelection;
106
+ }
107
+ /** The runtime that will own the session after the barrier. */
108
+ export interface HandoffDestinationRuntime {
109
+ readonly runtimeKind: RuntimeKind;
110
+ /** §12 step 8. `ok: false` keeps the source owner and offers the fork. */
111
+ confirmInit(target: HandoffResumeTarget): HandoffStepReport | Promise<HandoffStepReport>;
112
+ }
113
+ /** Who is on each end. Both are optional; see `HandoffSourceOwner` and step 8's default. */
114
+ export interface HandoffParticipants {
115
+ source?(session: SessionKey, from: RuntimeKind): Promise<HandoffSourceOwner | undefined> | HandoffSourceOwner | undefined;
116
+ destination?(session: SessionKey, to: RuntimeKind): Promise<HandoffDestinationRuntime | undefined> | HandoffDestinationRuntime | undefined;
117
+ }
118
+ /** A `plan()` that cannot be built at all — the session is unknown, so nothing about it can be planned. */
119
+ export declare class HandoffPlanError extends RuntimeSdkError {
120
+ constructor(session: SessionKey, reason: string);
121
+ }
122
+ /** The outcome, widened with the detail the pinned union has no room for. Assignable to it. */
123
+ export type DetailedHandoffOutcome = HandoffOutcome & {
124
+ /** The step the barrier reached. Present on every arm, including `resumed` (always 8). */
125
+ step: HandoffStepNumber;
126
+ detail: string;
127
+ /** Everything step 8 handed the destination — a host renders the resume from this. */
128
+ target?: HandoffResumeTarget;
129
+ steps: Array<{
130
+ step: HandoffStepNumber;
131
+ name: string;
132
+ ok: boolean;
133
+ detail: string;
134
+ }>;
135
+ };
136
+ export interface HandoffBarrierDeps {
137
+ /** The one shared store. Built lazily from the peer + `winterHome` when a host does not pass one. */
138
+ shared?: SharedSessionStore;
139
+ /** Defaults to the peer's own `resolveWinterHome()` — the production answer, resolved at first use. */
140
+ winterHome?: string;
141
+ /**
142
+ * A decorator to use instead of the barrier's own.
143
+ *
144
+ * IT MUST BE OVER THE SAME STORE, and the barrier checks (review r1, F2) rather than trusting: two
145
+ * stores over one home means two decoration registries, and a decoration registered in one washes
146
+ * back through the other's canonical append gate. A host wiring both seams should take
147
+ * `barrier.decorator` instead of building a second one.
148
+ */
149
+ decorator?: MaterializedResumeDecoratorHandle;
150
+ /**
151
+ * R-7b-12: the pin's own probe report, which is what opens the PREFERRED door.
152
+ *
153
+ * Passed to the decorator the barrier BUILDS, so the one-store invariant above is untouched — a host
154
+ * that wanted PREFERRED used to have to construct a second decorator, which is exactly the wiring
155
+ * mistake `decorator` refuses. `createRuntimeSdk` fills it from
156
+ * `materializedResumeReportForPin(<the injected official peer's version>)`; absent, or a version with
157
+ * no recorded report, means `fallback`.
158
+ */
159
+ decorationReport?: MaterializedResumeProbeReport;
160
+ participants?: HandoffParticipants;
161
+ /** Where the handoff leases live. Defaults to `<winterHome>/runtimes/handoff-leases`. */
162
+ leaseRoot?: string;
163
+ /** Overrides the staging root a `claude-agent` destination resumes from (tests point it at a tmpdir). */
164
+ stagingRootFor?: (uuid: string) => string;
165
+ /** The temp layout for a session. Defaults to D18's derivation from the resolved brand. */
166
+ tempLayoutFor?: (entry: RuntimeDirectoryEntry, session: SessionKey) => EngineTempLayout;
167
+ /**
168
+ * The catalog and credentials a FRESH selection decision needs, so `plan()` can ask Lane D whether
169
+ * the destination branch can actually serve this session (fix wave, item 20).
170
+ *
171
+ * ABSENT MEANS UNREVIEWED, NOT ASSUMED-FINE. Only the host has the model catalog and the credential
172
+ * presence map; the barrier will not synthesise them, and a plan built without them says
173
+ * `selection.kind === "unreviewed"` in so many words rather than implying a check that never ran.
174
+ */
175
+ selectionInputFor?: (args: {
176
+ session: SessionKey;
177
+ from: RuntimeKind;
178
+ to: RuntimeKind;
179
+ persisted: RuntimeSelection;
180
+ }) => SelectionInput | Promise<SelectionInput>;
181
+ /** The handoff note's text. The host owns the wording; this is the default. */
182
+ noteText?: (args: {
183
+ from: RuntimeKind;
184
+ to: RuntimeKind;
185
+ session: SessionKey;
186
+ }) => string;
187
+ now?: () => Date;
188
+ }
189
+ export interface HandoffBarrierHandle extends HandoffBarrier {
190
+ execute(plan: HandoffPlan): Promise<DetailedHandoffOutcome>;
191
+ /** The shared store this barrier writes through — the same object both branches were given. */
192
+ readonly shared: SharedSessionStore;
193
+ /** The decorator, over that same store. The spine wires `decorator: barrier.decorator`. */
194
+ readonly decorator: MaterializedResumeDecoratorHandle;
195
+ }
196
+ /** A barrier and a decorator that do not share one store — a wiring mistake, refused rather than run. */
197
+ export declare class HandoffWiringError extends RuntimeSdkError {
198
+ constructor(reason: string);
199
+ }
200
+ /**
201
+ * WS-05 §12's mechanics. Lane C's implementation of the spine's seam.
202
+ *
203
+ * THE DIRECTORY IS READ THROUGH THE STORE SEAM, not through `context.directory`. Both are the same
204
+ * data; the difference is that `RuntimeDirectoryStore` is implemented (the spine ships an in-memory
205
+ * default) while `RuntimeDirectory` is Lane B's policy layer over it. A barrier that reached for the
206
+ * policy layer would be unable to run until another lane landed, for no gain: everything the barrier
207
+ * needs is a record, not a resolution.
208
+ */
209
+ export declare function createHandoffBarrier(context: SeamContextWithDirectory, deps?: HandoffBarrierDeps): HandoffBarrierHandle;
210
+ export type TranscriptValidation = {
211
+ ok: true;
212
+ detail: string;
213
+ } | {
214
+ ok: false;
215
+ reason: string;
216
+ };
217
+ /**
218
+ * §12 step 5: "validate JSONL framing, UUID uniqueness, parent-chain reachability, tool_use/result
219
+ * pairing, compaction boundary, subkeys, declared adjacent stores".
220
+ *
221
+ * FRAMING IS CHECKED ON THE BYTES, everything else on what `load()` returns — because framing is the one
222
+ * property `load()` REPAIRS (the concrete store quarantines a torn tail), so a check that ran only on the
223
+ * loaded entries would report clean on a file it had just truncated.
224
+ */
225
+ export declare function validateSessionTranscript(shared: SharedSessionStore, session: SessionKey, winterHome: string): Promise<TranscriptValidation>;
226
+ /** The producer record — the barrier's one authoritative write — did not reach the store. */
227
+ export declare class HandoffCommitError extends RuntimeSdkError {
228
+ constructor(reason: string);
229
+ }
230
+ interface HandoffLease {
231
+ path: string;
232
+ }
233
+ export declare function acquireHandoffLease(leaseRoot: string, session: SessionKey): HandoffLease;
234
+ export declare function releaseHandoffLease(lease: HandoffLease): void;
235
+ export declare class HandoffLeaseError extends RuntimeSdkError {
236
+ constructor(path: string, reason: string);
237
+ }
238
+ export {};
@@ -0,0 +1,11 @@
1
+ export { assertOneSharedStore, lazySharedSessionStore, assertStoreCompatibleOptions, BlindStoreImportError, createDecorationRegistry, createSharedSessionStore, DEFAULT_MIRROR_POLICY, SharedStoreOptionsError, SharedStoreUnavailableError, stripDecorations, } from "./wiring.js";
2
+ export type { CanonicalSessionStore, CanonicalSessionStoreConstructor, DecorationRegistry, MirrorErrorRecord, MirrorPolicy, SessionMirrorHealth, SettleReport, SharedSessionStore, SharedStoreIdentity, StoreBearingOptions, StripDecorationsResult, TranscriptHealth, } from "./wiring.js";
3
+ export { canonicalTranscriptPath, compareTranscriptTail, createTranscriptReconciler, guardedImportSessionToStore, isFile, isTranscriptPath, localTranscriptPath, reconcileLocalWriteRoot, scanLocalWriteRoot, TranscriptReconcileError, } from "./reconcile.js";
4
+ export type { LocalTranscript, ReconcileReport, TailComparison, TranscriptReconcileHook, TranscriptReconciler, TranscriptReconcileOutcome } from "./reconcile.js";
5
+ export { materializeTempContinuity, resolveEngineTempLayout, sessionTempDirFor, tempContinuityDisclosure, tempContinuityModeFor, TempContinuityError, VENDOR_ENGINE_DIR_PREFIX, } from "./temp-continuity.js";
6
+ export type { EngineTempLayout, EngineTempLayoutInput, TempContinuityDisclosure, TempContinuityInput, TempContinuityResult } from "./temp-continuity.js";
7
+ export { classifyCrashPairs, createMaterializedResumeDecorator, HANDOFF_ENTRY_LABEL, materializedTranscriptPath, MaterializedResumeError, PROVIDER_STATE_SUFFIX, } from "./materialized-resume.js";
8
+ export type { CrashPairClassification, StagedResumeResult, MaterializedResumeDecoratorHandle, MaterializedResumeDeps, MaterializedResumeProbeDetail, MaterializedResumeProbeLeg, PinnedRuntimeProbeLegs, } from "./materialized-resume.js";
9
+ export { MATERIALIZED_RESUME_PROBE_REPORTS, materializedResumeReportForPin } from "./pinned-probes.js";
10
+ export { acquireHandoffLease, createHandoffBarrier, HANDOFF_STEPS, HandoffCommitError, HandoffLeaseError, HandoffPlanError, HandoffWiringError, releaseHandoffLease, validateSessionTranscript } from "./handoff-barrier.js";
11
+ export type { CompatibilityLevel, DetailedHandoffOutcome, HandoffBarrierDeps, HandoffBarrierHandle, HandoffDestinationRuntime, HandoffEligibilityLike, HandoffOwnerHealth, HandoffParticipants, HandoffResumeTarget, HandoffSourceOwner, HandoffStepReport, TranscriptValidation, } from "./handoff-barrier.js";
@@ -0,0 +1,100 @@
1
+ import type { SessionKey, SessionStoreEntry } from "@yanlinglabs/winter-agent-sdk";
2
+ import { RuntimeSdkError } from "../errors.js";
3
+ import type { SeamContext } from "../seams/context.js";
4
+ import type { MaterializedResumeDecorator, MaterializedResumeInput, MaterializedResumeProbeReport, MaterializedResumeProbeResult, MaterializedResumeResult } from "../seams/materialized-resume.js";
5
+ import { type SharedSessionStore } from "./wiring.js";
6
+ /** The provider-state sidecar's suffix (WS-05 §13). Named here only so the probes can leave it alone. */
7
+ export declare const PROVIDER_STATE_SUFFIX = ".provider-state.jsonl";
8
+ /** The label every barrier-injected entry carries, so it can never read as an ordinary message. */
9
+ export declare const HANDOFF_ENTRY_LABEL = "handoff";
10
+ /** A decoration that cannot be produced — the transcript has nothing to anchor a note to. */
11
+ export declare class MaterializedResumeError extends RuntimeSdkError {
12
+ constructor(reason: string);
13
+ }
14
+ /** Where inside a staging root the destination runtime reads this session's transcript. */
15
+ export declare function materializedTranscriptPath(stagingRoot: string, key: SessionKey): string;
16
+ /**
17
+ * The legs only the pinned official runtime can answer.
18
+ *
19
+ * INJECTED, NEVER IMPORTED. This lane's probes must be runnable in a tree where the official adapter is
20
+ * not present, and the bed that CAN drive 0.3.250 lives in the official lane's tests. A probe with no
21
+ * bed records "unexercised" and the door stays shut.
22
+ */
23
+ export interface PinnedRuntimeProbeLegs {
24
+ /**
25
+ * Resumes `key` on the pinned runtime from `resumePath`'s staging root, with `store` attached as the
26
+ * session store, and returns once the generation has ended.
27
+ */
28
+ freshProcessResume(args: {
29
+ home: string;
30
+ stagingRoot: string;
31
+ key: SessionKey;
32
+ shared: SharedSessionStore;
33
+ }): Promise<void>;
34
+ /** A short human name for the bed, recorded in the probe evidence. */
35
+ readonly label: string;
36
+ }
37
+ export interface MaterializedResumeDeps {
38
+ /** The one shared store. Omitted = resolved on first use from the peer (see `lazySharedSessionStore`). */
39
+ shared?: SharedSessionStore | (() => SharedSessionStore);
40
+ /** A report recorded on a previous run — `docs/probes/materialized-resume.md`'s content. */
41
+ report?: MaterializedResumeProbeReport;
42
+ /** The pinned runtime's legs, when a bed can supply them. */
43
+ runtimeLegs?: PinnedRuntimeProbeLegs;
44
+ /** Fields for the note when the transcript has no entry to read them from. */
45
+ anchor?: {
46
+ cwd: string;
47
+ version: string;
48
+ };
49
+ now?: () => Date;
50
+ }
51
+ /**
52
+ * The pinned result, widened with the entry the FALLBACK door owes the canonical file.
53
+ *
54
+ * THE DECORATOR NEVER WRITES TO THE CANONICAL STORE (review r1, F1/F2). Under FALLBACK the note still
55
+ * belongs there — §8.2 calls it "the sole case where injected handoff content enters the canonical
56
+ * file" — but WHEN it goes in is the barrier's business, and the answer is "after the destination
57
+ * confirms init". A note appended before that leaves a durable, model-visible sentence saying the
58
+ * conversation continues on a runtime that never started.
59
+ */
60
+ export interface StagedResumeResult extends MaterializedResumeResult {
61
+ /** Present only under FALLBACK: the labelled entry the barrier commits once step 8 confirms. */
62
+ note?: SessionStoreEntry;
63
+ }
64
+ export interface MaterializedResumeDecoratorHandle extends MaterializedResumeDecorator {
65
+ /** The last report — from `deps.report` until `probe()` runs. */
66
+ readonly report: MaterializedResumeProbeReport | undefined;
67
+ /**
68
+ * The store this decorator writes its registry into.
69
+ *
70
+ * EXPOSED so the barrier can ASSERT the two share one instance (review r1, F2): two stores over one
71
+ * home means two decoration registries, and a decoration registered in one is invisible to the
72
+ * other's canonical append gate — which is a wash-back caused by wiring rather than by the vendor.
73
+ */
74
+ readonly shared: SharedSessionStore;
75
+ decorate(input: MaterializedResumeInput): Promise<StagedResumeResult>;
76
+ }
77
+ /** WS-13 §8.2's doors. Lane C's implementation of the spine's seam. */
78
+ export declare function createMaterializedResumeDecorator(context: SeamContext, deps?: MaterializedResumeDeps): MaterializedResumeDecoratorHandle;
79
+ export interface MaterializedResumeProbeLeg {
80
+ name: string;
81
+ passed: boolean;
82
+ /** True when only the pinned official runtime can answer this leg. */
83
+ requiresPinnedRuntime: boolean;
84
+ evidence: string;
85
+ }
86
+ /** The pinned result shape, widened with the legs that produced it. */
87
+ export interface MaterializedResumeProbeDetail extends MaterializedResumeProbeResult {
88
+ legs: MaterializedResumeProbeLeg[];
89
+ }
90
+ /** WS-05 §13's write-ahead pairing, as a classification a collector can act on. */
91
+ export interface CrashPairClassification {
92
+ /** A sidecar record whose anchoring transcript entry never landed — garbage-collectable. */
93
+ collectable: string[];
94
+ /** A transcript entry whose sidecar record never landed — native resume degrades to summary level. */
95
+ degraded: string[];
96
+ }
97
+ export declare function classifyCrashPairs(args: {
98
+ entryUuids: readonly string[];
99
+ anchorUuids: readonly string[];
100
+ }): CrashPairClassification;
@@ -0,0 +1,17 @@
1
+ import type { MaterializedResumeProbeReport } from "../seams/materialized-resume.js";
2
+ /**
3
+ * The recorded verdict per pinned official-runtime version.
4
+ *
5
+ * The evidence sentences are the probes' own, trimmed to what a reader of a handoff outcome needs;
6
+ * `docs/probes/materialized-resume.md` carries the full run, both platforms, and the two withdrawn
7
+ * earlier readings.
8
+ */
9
+ export declare const MATERIALIZED_RESUME_PROBE_REPORTS: Readonly<Record<string, MaterializedResumeProbeReport>>;
10
+ /**
11
+ * The report for a pin, or `undefined` — which is what keeps a bump honest.
12
+ *
13
+ * `undefined` means `fallback`: the decorator opens PREFERRED only when it is GIVEN a passing report,
14
+ * so an unrecorded version, a host with no official peer at all, and a peer whose version could not be
15
+ * read all land on the same always-available door.
16
+ */
17
+ export declare function materializedResumeReportForPin(version: string | undefined): MaterializedResumeProbeReport | undefined;
@@ -0,0 +1,157 @@
1
+ import type { SessionKey, SessionStoreEntry } from "@yanlinglabs/winter-agent-sdk";
2
+ import { RuntimeSdkError } from "../errors.js";
3
+ import type { SharedSessionStore } from "./wiring.js";
4
+ /** One transcript found under a local-write root, with the store key it mirrors to. */
5
+ export interface LocalTranscript {
6
+ path: string;
7
+ key: SessionKey;
8
+ }
9
+ /**
10
+ * Every transcript under a recorded local-write root.
11
+ *
12
+ * NOT A GLOB AND NOT A RECURSIVE WALK: exactly two shapes are transcripts (the session's own file and
13
+ * its `subagents/` children), and everything else in that tree — the vendor's settings, its caches,
14
+ * its lock files — is out of scope by construction rather than by a filter someone has to maintain.
15
+ * A missing root is an empty list, not an error: a session that never spawned has nothing to mirror.
16
+ */
17
+ export declare function scanLocalWriteRoot(root: string): LocalTranscript[];
18
+ /** WS-05 §12 step 4's four possible answers. */
19
+ export type TailComparison = {
20
+ kind: "match";
21
+ lines: number;
22
+ } | {
23
+ kind: "canonical-behind";
24
+ lines: number;
25
+ missing: SessionStoreEntry[];
26
+ }
27
+ /** The canonical file disagrees with the local root at a line both of them have. */
28
+ | {
29
+ kind: "diverged";
30
+ atLine: number;
31
+ reason: string;
32
+ }
33
+ /** The canonical file has entries the local root does not — they are not the same history. */
34
+ | {
35
+ kind: "canonical-ahead";
36
+ extra: number;
37
+ };
38
+ /**
39
+ * WS-05 §12 step 4: "compare canonical tail vs the recorded local-write root".
40
+ *
41
+ * The canonical file MUST be a prefix of the local one. It is a mirror: the local write happens first
42
+ * (WS-14 §5), so the canonical side can only ever be equal or behind. Anything else is a statement
43
+ * that the two files are not the same history, and this function says so rather than repairing it.
44
+ */
45
+ export declare function compareTranscriptTail(args: {
46
+ localPath: string;
47
+ canonicalLines: readonly string[];
48
+ isDecoration?: (uuid: string) => boolean;
49
+ }): TailComparison;
50
+ export interface TranscriptReconcileOutcome {
51
+ key: SessionKey;
52
+ localPath: string;
53
+ comparison: TailComparison;
54
+ appended: number;
55
+ }
56
+ export interface ReconcileReport {
57
+ root: string;
58
+ /** `reconciled` — every transcript now matches. `diverged` — at least one cannot be, safely. */
59
+ status: "reconciled" | "nothing-to-do" | "diverged";
60
+ transcripts: TranscriptReconcileOutcome[];
61
+ appended: number;
62
+ /** The sessions whose health was cleared, i.e. those that were behind and are now level. */
63
+ cleared: SessionKey[];
64
+ }
65
+ /** A reconciliation that cannot proceed because the canonical store's own state forbids it. */
66
+ export declare class TranscriptReconcileError extends RuntimeSdkError {
67
+ readonly root: string;
68
+ constructor(root: string, reason: string);
69
+ }
70
+ export interface TranscriptReconcilerInput {
71
+ shared: SharedSessionStore;
72
+ /** Restricts reconciliation to one session; omitted = every transcript under the root. */
73
+ only?: {
74
+ projectKey: string;
75
+ sessionId: string;
76
+ };
77
+ }
78
+ /**
79
+ * Reconciles the canonical store against one local-write root.
80
+ *
81
+ * THE ORDER IS DELIBERATE: settle first (so the comparison is not racing the mirror's own pending
82
+ * batch), then compare, then append only the suffix, then settle again and re-read to confirm. A
83
+ * reconciliation that reported success without re-reading would be reporting its own intention.
84
+ */
85
+ export declare function reconcileLocalWriteRoot(root: string, input: TranscriptReconcilerInput): Promise<ReconcileReport>;
86
+ /**
87
+ * The shape of WS-14 §6 rule 3's reconcile hook, as `src/official/spawn-proxy.ts` declares it.
88
+ *
89
+ * DECLARED STRUCTURALLY HERE rather than imported, for the same reason the seams declare the official
90
+ * module structurally: this lane must not depend on that lane's module graph to be buildable.
91
+ *
92
+ * ASSIGNABLE IN ONE DIRECTION, AND THAT IS THE DIRECTION PRODUCTION USES (review r4, N14 — the
93
+ * previous sentence claimed "mutually assignable", which is measurably false). This hook accepts LESS
94
+ * than `SpawnObservation` carries, so Lane C's `createTranscriptReconciler().hook` drops straight into
95
+ * `createSupervisedSpawnProxy({ reconcile })`; the reverse fails, because a `TranscriptReconcile` is
96
+ * handed the whole observation (`processIdentity`, `command`, `args`) and this type does not promise
97
+ * to supply it. Nothing needs the reverse. `test/store/reconcile.test.ts` now actually pins the
98
+ * forward direction — the comment promised a test that did not exist, which is how a structural
99
+ * declaration drifts from the thing it mirrors without anything noticing.
100
+ */
101
+ export type TranscriptReconcileHook = (input: {
102
+ observation: {
103
+ root: {
104
+ configDir: string;
105
+ };
106
+ };
107
+ exit: {
108
+ code: number | null;
109
+ signal: string | null;
110
+ };
111
+ }) => Promise<void>;
112
+ export interface TranscriptReconciler {
113
+ /** Reconcile one recorded local-write root. */
114
+ reconcile(root: string, only?: {
115
+ projectKey: string;
116
+ sessionId: string;
117
+ }): Promise<ReconcileReport>;
118
+ /** The collaborator handed to `createSupervisedSpawnProxy({ reconcile })`. */
119
+ hook: TranscriptReconcileHook;
120
+ /** Every report this reconciler produced, newest last — the supervised-run evidence. */
121
+ readonly reports: readonly ReconcileReport[];
122
+ }
123
+ /**
124
+ * Builds the reconciler and its proxy hook.
125
+ *
126
+ * THE HOOK NEVER THROWS. WS-14 §6 rule 3 delays the synthetic `exit` until reconciliation completes;
127
+ * a hook that threw would either strand the process or (as Lane A's proxy actually does) be caught and
128
+ * the exit forwarded anyway — and in both readings the FAILURE would be the thing that disappears. So
129
+ * a failed reconciliation is recorded as a `diverged` report, which leaves the session's
130
+ * `repair-required` flag standing and the handoff blocked. That is the outcome §5 asks for.
131
+ */
132
+ export declare function createTranscriptReconciler(input: TranscriptReconcilerInput): TranscriptReconciler;
133
+ /**
134
+ * WS-14 §5's forbidden door, guarded.
135
+ *
136
+ * A HOST STILL HAS `importSessionToStore()` — it is the official SDK's own function, and the router
137
+ * cannot take it away. What the router CAN do is give the host one call site that refuses to run it
138
+ * while the mirror is unhealthy, so the ban is a mechanism rather than a sentence in a document.
139
+ */
140
+ export declare function guardedImportSessionToStore<T>(args: {
141
+ shared: SharedSessionStore;
142
+ key: SessionKey;
143
+ importer: () => Promise<T>;
144
+ }): Promise<T>;
145
+ /**
146
+ * The canonical transcript's own path under a Winter home.
147
+ *
148
+ * THE SAME SHAPE AS A LOCAL-WRITE ROOT'S, and that is not a coincidence: WS-05 §6's key mapping is the
149
+ * vendor's own `<configDir>/projects/<projectKey>/<sessionId>.jsonl` layout, which is what makes the
150
+ * canonical file and the local file comparable line for line in the first place.
151
+ */
152
+ export declare function canonicalTranscriptPath(winterHome: string, key: SessionKey): string;
153
+ /** The transcript file a local-write root holds for one session key, whether or not it exists. */
154
+ export declare function localTranscriptPath(root: string, key: SessionKey): string;
155
+ export declare function isTranscriptPath(path: string, kind?: "session" | "subagent"): boolean;
156
+ /** Whether a path exists and is a regular file — used by the probes' byte-for-byte assertions. */
157
+ export declare function isFile(path: string): boolean;
@@ -0,0 +1,92 @@
1
+ import { type BrandProfile } from "@yanlinglabs/winter-agent-sdk";
2
+ import { RuntimeSdkError } from "../errors.js";
3
+ import type { RuntimeKind } from "../selection/runtime-selection.js";
4
+ /** The official engine's own hard-coded directory prefix beneath the shared root (WS-05 §9, WS-14 §1). */
5
+ export declare const VENDOR_ENGINE_DIR_PREFIX = "claude-";
6
+ /** A temp layout that could not be established, or one whose destination failed re-validation. */
7
+ export declare class TempContinuityError extends RuntimeSdkError {
8
+ readonly path: string;
9
+ constructor(path: string, reason: string);
10
+ }
11
+ export interface EngineTempLayout {
12
+ /** `realpath(<PREFIX>TMPDIR | /tmp)`. */
13
+ base: string;
14
+ /** The shared per-user root — the value a spawned official child gets as `CLAUDE_CODE_TMPDIR`. */
15
+ sharedRoot: string;
16
+ winterEngineDir: string;
17
+ vendorEngineDir: string;
18
+ winterSessionDir: string;
19
+ vendorSessionDir: string;
20
+ uid: number;
21
+ tempProjectKey: string;
22
+ backendUuid: string;
23
+ }
24
+ export interface EngineTempLayoutInput {
25
+ brand: Pick<BrandProfile, "envPrefix" | "tempRootName">;
26
+ tempProjectKey: string;
27
+ backendUuid: string;
28
+ /** Injectable so a hermetic test never reads the real environment. */
29
+ env?: Record<string, string | undefined>;
30
+ /** The REAL process uid by default. A fixed suffix would lock every other user out of a 0700 root. */
31
+ uid?: number;
32
+ }
33
+ /**
34
+ * D18's paths, derived rather than spelled.
35
+ *
36
+ * THE BASE IS REALPATH'D ONCE, up front (macOS `/tmp` → `/private/tmp`), and no level beneath it ever
37
+ * is: resolving a deeper level would follow a symlink an attacker could have planted there, which is
38
+ * exactly what §9's "validate without following symlinks" forbids.
39
+ */
40
+ export declare function resolveEngineTempLayout(input: EngineTempLayoutInput): EngineTempLayout;
41
+ /** WS-05 §12 step 7 / §9.1's two directions, by DESTINATION runtime. */
42
+ export declare function tempContinuityModeFor(to: RuntimeKind): "adopt" | "clone-copy";
43
+ /** The canonical session temp dir for a runtime, under one layout. */
44
+ export declare function sessionTempDirFor(to: RuntimeKind, layout: EngineTempLayout): string;
45
+ export interface TempContinuityResult {
46
+ mode: "adopt" | "clone-copy";
47
+ /** `RuntimeSessionRecord.effectiveTempDir` after the handoff (WS-16 §4). */
48
+ effectiveTempDir: string;
49
+ /** The Winter-side dir a clone-copy superseded. Retained; named so retention can find it. */
50
+ supersededDir?: string;
51
+ filesCopied: number;
52
+ /** §9.1's "Round trips" row: the recorded dir already IS the destination's computed path. */
53
+ stabilized: boolean;
54
+ /** §9.1's last row: the recorded dir was gone, so a fresh one was created at the canonical path. */
55
+ recreated: boolean;
56
+ }
57
+ export interface TempContinuityInput {
58
+ to: RuntimeKind;
59
+ layout: EngineTempLayout;
60
+ /**
61
+ * `RuntimeSessionRecord.effectiveTempDir` — the dir the session is ACTUALLY using, whichever engine
62
+ * dir it lives under. Absent for a session that has no recorded dir yet.
63
+ */
64
+ recordedTempDir?: string;
65
+ }
66
+ /**
67
+ * WS-05 §12 step 7.
68
+ *
69
+ * EVERY BRANCH RE-VALIDATES THE DESTINATION (ownership, directory-ness, no symlink, `0700`) and
70
+ * refuses rather than repairing — the destination of a temp materialization is a directory another
71
+ * process could have planted, and §9's own rule is "validate the per-user root without following
72
+ * symlinks: ownership, directory-ness, traversal, permissions; reject substitution".
73
+ */
74
+ export declare function materializeTempContinuity(input: TempContinuityInput): TempContinuityResult;
75
+ /**
76
+ * What this module discloses about where a session's scratch actually lives (WS-17 row 15's "vendor
77
+ * temp roots reported honestly").
78
+ *
79
+ * The official adapter discloses the roots a SPAWN observes; this is the continuity half — the two
80
+ * engine dirs, which one the session is on now, and the one a clone-copy left behind. A report that
81
+ * named only the Winter dir would be the dishonest version: after one Claude generation the session's
82
+ * scratch is in the vendor's directory, and a user asking "where are my files" needs that answer.
83
+ */
84
+ export interface TempContinuityDisclosure {
85
+ sharedRoot: string;
86
+ winterEngineDir: string;
87
+ vendorEngineDir: string;
88
+ effectiveTempDir: string;
89
+ supersededDir?: string;
90
+ note: string;
91
+ }
92
+ export declare function tempContinuityDisclosure(layout: EngineTempLayout, result: TempContinuityResult): TempContinuityDisclosure;