@code-yeongyu/senpi-agent-core 2026.10.27-2 → 2026.10.30-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 (65) hide show
  1. package/dist/conformance/index.d.ts +42 -0
  2. package/dist/conformance/index.js +23 -0
  3. package/dist/conformance/mapping.d.ts +24 -0
  4. package/dist/conformance/mapping.js +62 -0
  5. package/dist/conformance/registry.d.ts +15 -0
  6. package/dist/conformance/registry.js +75 -0
  7. package/dist/conformance/runner.d.ts +27 -0
  8. package/dist/conformance/runner.js +23 -0
  9. package/dist/conformance/suite-hash.d.ts +12 -0
  10. package/dist/conformance/suite-hash.js +20 -0
  11. package/dist/durable/store-admission.d.ts +7 -0
  12. package/dist/durable/store-admission.js +11 -0
  13. package/dist/durable/store-memory.d.ts +15 -0
  14. package/dist/durable/store-memory.js +140 -0
  15. package/dist/durable/store.d.ts +116 -0
  16. package/dist/durable/store.js +14 -0
  17. package/dist/durable/testing/kill-store.d.ts +13 -0
  18. package/dist/durable/testing/kill-store.js +77 -0
  19. package/dist/durable/testing/kill-wake.d.ts +12 -0
  20. package/dist/durable/testing/kill-wake.js +123 -0
  21. package/dist/durable/testing/manual-timers.d.ts +8 -0
  22. package/dist/durable/testing/manual-timers.js +31 -0
  23. package/dist/durable/testing/multi-host-cases.d.ts +6 -0
  24. package/dist/durable/testing/multi-host-cases.js +32 -0
  25. package/dist/durable/testing/store-cases.d.ts +21 -0
  26. package/dist/durable/testing/store-cases.js +168 -0
  27. package/dist/durable/testing/store-harness.d.ts +23 -0
  28. package/dist/durable/testing/store-harness.js +64 -0
  29. package/dist/durable/testing/wake-cases.d.ts +6 -0
  30. package/dist/durable/testing/wake-cases.js +100 -0
  31. package/dist/durable/testing/wake-harness.d.ts +64 -0
  32. package/dist/durable/testing/wake-harness.js +127 -0
  33. package/dist/durable/testing/wake-test-loop.d.ts +13 -0
  34. package/dist/durable/testing/wake-test-loop.js +40 -0
  35. package/dist/durable/wake-host-bun-timer.d.ts +31 -0
  36. package/dist/durable/wake-host-bun-timer.js +66 -0
  37. package/dist/durable/wake-host-store-alarm.d.ts +13 -0
  38. package/dist/durable/wake-host-store-alarm.js +20 -0
  39. package/dist/durable/wake-host.d.ts +68 -0
  40. package/dist/durable/wake-host.js +70 -0
  41. package/dist/harness/utils/read-folders/index.d.ts +1 -1
  42. package/dist/harness/utils/read-folders/index.js +1 -1
  43. package/dist/harness/utils/read-folders/tree-sitter/engine.d.ts +1 -1
  44. package/dist/harness/utils/read-folders/tree-sitter/engine.js +38 -19
  45. package/dist/testing/win32-sim.d.ts +76 -0
  46. package/dist/testing/win32-sim.js +197 -0
  47. package/dist/transport/in-process-carrier.d.ts +19 -0
  48. package/dist/transport/in-process-carrier.js +63 -0
  49. package/dist/transport/local-endpoint.d.ts +13 -0
  50. package/dist/transport/local-endpoint.js +20 -0
  51. package/dist/transport/reliable-transport.d.ts +40 -0
  52. package/dist/transport/reliable-transport.js +232 -0
  53. package/dist/transport/testing/fault-cases.d.ts +6 -0
  54. package/dist/transport/testing/fault-cases.js +30 -0
  55. package/dist/transport/testing/fault-transport.d.ts +44 -0
  56. package/dist/transport/testing/fault-transport.js +144 -0
  57. package/dist/transport/testing/multi-cases.d.ts +6 -0
  58. package/dist/transport/testing/multi-cases.js +113 -0
  59. package/dist/transport/testing/transport-cases.d.ts +6 -0
  60. package/dist/transport/testing/transport-cases.js +60 -0
  61. package/dist/transport/testing/transport-harness.d.ts +53 -0
  62. package/dist/transport/testing/transport-harness.js +127 -0
  63. package/dist/transport/transport.d.ts +60 -0
  64. package/dist/transport/transport.js +15 -0
  65. package/package.json +11 -3
@@ -0,0 +1,42 @@
1
+ export type { ConformanceManifest, ManifestCase, MappingProblem, MappingProblemCode } from "./mapping.ts";
2
+ export { buildManifest, findImplementationGaps, findMappingProblems } from "./mapping.ts";
3
+ export type { AdapterKind, RegisteredCase } from "./registry.ts";
4
+ export { ADAPTER_KINDS, CONFORMANCE_REGISTRY } from "./registry.ts";
5
+ export type { CaseImplementation, CaseResult, ConformanceHost, HostReport } from "./runner.ts";
6
+ export { runSuite } from "./runner.ts";
7
+ export type { SuiteHashEntry } from "./suite-hash.ts";
8
+ export { computeSuiteHash, sha256Hex } from "./suite-hash.ts";
9
+ export type { CommitResult, DurableStore, DurableStoreErrorCode, DurableStoreOptions, RecordScan, StoreDocument, StoreOperation, StoreRecord, StoreSnapshot, StoreValue, StoreWrite, StoreWritePoint, } from "../durable/store.ts";
10
+ export { DurableStoreError } from "../durable/store.ts";
11
+ export type { KillDeclaration } from "../durable/testing/kill-store.ts";
12
+ export { KILL_STORE_CASES, KILL_STORE_DECLARATIONS } from "../durable/testing/kill-store.ts";
13
+ export { STORE_CASES } from "../durable/testing/store-cases.ts";
14
+ export type { StoreCaseAdapters, StoreConformanceAdapter } from "../durable/testing/store-harness.ts";
15
+ export type { KillWakeDeclaration } from "../durable/testing/kill-wake.ts";
16
+ export { KILL_WAKE_CASES, KILL_WAKE_DECLARATIONS } from "../durable/testing/kill-wake.ts";
17
+ export type { ManualTimers } from "../durable/testing/manual-timers.ts";
18
+ export { createManualTimers } from "../durable/testing/manual-timers.ts";
19
+ export { MULTI_HOST_CASES } from "../durable/testing/multi-host-cases.ts";
20
+ export { WAKE_CASES } from "../durable/testing/wake-cases.ts";
21
+ export type { WakeAlarmSession, WakeCaseAdapters, WakeConformanceAdapter } from "../durable/testing/wake-harness.ts";
22
+ export type { Wake, WakeAlarm, WakeAlarmOptions, WakeAlarmPoint, WakeHost, WakeHostOptions, WakePoint, } from "../durable/wake-host.ts";
23
+ export { createWakeHost } from "../durable/wake-host.ts";
24
+ export type { BunTimerHost, BunTimerHostOptions, SetTimer } from "../durable/wake-host-bun-timer.ts";
25
+ export { createBunTimerHost } from "../durable/wake-host-bun-timer.ts";
26
+ export type { AlarmPersistence } from "../durable/wake-host-store-alarm.ts";
27
+ export { storeAlarmPersistence } from "../durable/wake-host-store-alarm.ts";
28
+ export type { CarrierLink, InProcessNetwork, InProcessNetworkOptions } from "../transport/in-process-carrier.ts";
29
+ export { createInProcessNetwork } from "../transport/in-process-carrier.ts";
30
+ export type { LocalEndpoint } from "../transport/local-endpoint.ts";
31
+ export { localEndpoint } from "../transport/local-endpoint.ts";
32
+ export type { ReferencePacket, ReliableTransportOptions } from "../transport/reliable-transport.ts";
33
+ export { createReliableTransport, decodeReferencePacket } from "../transport/reliable-transport.ts";
34
+ export { FAULT_TRANSPORT_CASES } from "../transport/testing/fault-cases.ts";
35
+ export type { FaultProfileName } from "../transport/testing/fault-transport.ts";
36
+ export { FAULT_PROFILE_NAMES } from "../transport/testing/fault-transport.ts";
37
+ export { MULTI_CASES } from "../transport/testing/multi-cases.ts";
38
+ export { TRANSPORT_CASES } from "../transport/testing/transport-cases.ts";
39
+ export type { CarrierFault, TransportCaseAdapters, TransportConformanceAdapter, WireView, } from "../transport/testing/transport-harness.ts";
40
+ export type { CarrierConnection, CarrierSide, FrameHandler, Transport, TransportClock, TransportFrame, TransportSide, } from "../transport/transport.ts";
41
+ export { TransportClosedError } from "../transport/transport.ts";
42
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,23 @@
1
+ export { buildManifest, findImplementationGaps, findMappingProblems } from "./mapping.js";
2
+ export { ADAPTER_KINDS, CONFORMANCE_REGISTRY } from "./registry.js";
3
+ export { runSuite } from "./runner.js";
4
+ export { computeSuiteHash, sha256Hex } from "./suite-hash.js";
5
+ export { DurableStoreError } from "../durable/store.js";
6
+ export { KILL_STORE_CASES, KILL_STORE_DECLARATIONS } from "../durable/testing/kill-store.js";
7
+ export { STORE_CASES } from "../durable/testing/store-cases.js";
8
+ export { KILL_WAKE_CASES, KILL_WAKE_DECLARATIONS } from "../durable/testing/kill-wake.js";
9
+ export { createManualTimers } from "../durable/testing/manual-timers.js";
10
+ export { MULTI_HOST_CASES } from "../durable/testing/multi-host-cases.js";
11
+ export { WAKE_CASES } from "../durable/testing/wake-cases.js";
12
+ export { createWakeHost } from "../durable/wake-host.js";
13
+ export { createBunTimerHost } from "../durable/wake-host-bun-timer.js";
14
+ export { storeAlarmPersistence } from "../durable/wake-host-store-alarm.js";
15
+ export { createInProcessNetwork } from "../transport/in-process-carrier.js";
16
+ export { localEndpoint } from "../transport/local-endpoint.js";
17
+ export { createReliableTransport, decodeReferencePacket } from "../transport/reliable-transport.js";
18
+ export { FAULT_TRANSPORT_CASES } from "../transport/testing/fault-cases.js";
19
+ export { FAULT_PROFILE_NAMES } from "../transport/testing/fault-transport.js";
20
+ export { MULTI_CASES } from "../transport/testing/multi-cases.js";
21
+ export { TRANSPORT_CASES } from "../transport/testing/transport-cases.js";
22
+ export { TransportClosedError } from "../transport/transport.js";
23
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,24 @@
1
+ import type { RegisteredCase } from "./registry.ts";
2
+ export type MappingProblemCode = "duplicate-id" | "malformed-id" | "malformed-impl" | "missing-intro" | "missing-adapters" | "duplicate-adapter" | "missing-expected";
3
+ export interface MappingProblem {
4
+ readonly id: string;
5
+ readonly code: MappingProblemCode;
6
+ }
7
+ export interface ManifestCase extends RegisteredCase {
8
+ readonly impl_hash: string;
9
+ }
10
+ /** The exported suite: what every host runs and every host report is compared against. */
11
+ export interface ConformanceManifest {
12
+ readonly suite_hash: string;
13
+ readonly cases: readonly ManifestCase[];
14
+ }
15
+ /** Every reason the registry is not a complete, unambiguous mapping; empty when it is. */
16
+ export declare function findMappingProblems(cases: readonly RegisteredCase[]): MappingProblem[];
17
+ /** Registered cases with no implementation, and implementations with no registered case. */
18
+ export declare function findImplementationGaps(cases: readonly Pick<RegisteredCase, "id">[], implementations: ReadonlyMap<string, unknown>): {
19
+ unimplemented: string[];
20
+ unregistered: string[];
21
+ };
22
+ /** Build the manifest export, hashing each implementation file through `readImpl` (repository-relative path). */
23
+ export declare function buildManifest(cases: readonly RegisteredCase[], readImpl: (impl: string) => Promise<Uint8Array>): Promise<ConformanceManifest>;
24
+ //# sourceMappingURL=mapping.d.ts.map
@@ -0,0 +1,62 @@
1
+ import { computeSuiteHash, sha256Hex } from "./suite-hash.js";
2
+ const CASE_ID = /^[A-Z][A-Z0-9]*(?:-[A-Z0-9]+)+$/;
3
+ const isRepoRelativeTs = (path) => path.endsWith(".ts") && !path.startsWith("/") && !path.includes("\\") && !path.split("/").includes("..");
4
+ function problemsOf(entry) {
5
+ const codes = [];
6
+ if (!CASE_ID.test(entry.id))
7
+ codes.push("malformed-id");
8
+ if (!isRepoRelativeTs(entry.impl))
9
+ codes.push("malformed-impl");
10
+ if (entry.intro.trim() === "")
11
+ codes.push("missing-intro");
12
+ if (entry.adapters.length === 0)
13
+ codes.push("missing-adapters");
14
+ if (new Set(entry.adapters).size !== entry.adapters.length)
15
+ codes.push("duplicate-adapter");
16
+ if (entry.expected.trim() === "")
17
+ codes.push("missing-expected");
18
+ return codes;
19
+ }
20
+ /** Every reason the registry is not a complete, unambiguous mapping; empty when it is. */
21
+ export function findMappingProblems(cases) {
22
+ const problems = [];
23
+ const seen = new Set();
24
+ for (const entry of cases) {
25
+ if (seen.has(entry.id))
26
+ problems.push({ id: entry.id, code: "duplicate-id" });
27
+ seen.add(entry.id);
28
+ for (const code of problemsOf(entry))
29
+ problems.push({ id: entry.id, code });
30
+ }
31
+ return problems;
32
+ }
33
+ /** Registered cases with no implementation, and implementations with no registered case. */
34
+ export function findImplementationGaps(cases, implementations) {
35
+ const registered = new Set(cases.map((entry) => entry.id));
36
+ return {
37
+ unimplemented: [...registered].filter((id) => !implementations.has(id)),
38
+ unregistered: [...implementations.keys()].filter((id) => !registered.has(id)),
39
+ };
40
+ }
41
+ /** Build the manifest export, hashing each implementation file through `readImpl` (repository-relative path). */
42
+ export async function buildManifest(cases, readImpl) {
43
+ const problems = findMappingProblems(cases);
44
+ if (problems.length > 0) {
45
+ throw new Error(`conformance registry mapping problems: ${problems.map((p) => `${p.id} ${p.code}`).join(", ")}`);
46
+ }
47
+ const manifestCases = [];
48
+ for (const entry of cases) {
49
+ let bytes;
50
+ try {
51
+ bytes = await readImpl(entry.impl);
52
+ }
53
+ catch (error) {
54
+ throw new Error(`conformance case ${entry.id}: cannot read its implementation ${entry.impl}`, {
55
+ cause: error,
56
+ });
57
+ }
58
+ manifestCases.push({ ...entry, impl_hash: await sha256Hex(bytes) });
59
+ }
60
+ return { suite_hash: await computeSuiteHash(manifestCases), cases: manifestCases };
61
+ }
62
+ //# sourceMappingURL=mapping.js.map
@@ -0,0 +1,15 @@
1
+ export declare const ADAPTER_KINDS: readonly ["store", "wake", "transport", "executor", "host"];
2
+ export type AdapterKind = (typeof ADAPTER_KINDS)[number];
3
+ /** One conformance case: where it is implemented, which change introduced it, what it needs, and what it proves. */
4
+ export interface RegisteredCase {
5
+ /** Unique upper-case ID, for example `STORE-01` or `KILL-STORE-01`. */
6
+ readonly id: string;
7
+ /** Repository-relative path of the TypeScript file that implements the case. */
8
+ readonly impl: string;
9
+ /** The change that introduced the case (a pull request such as `#4727`). */
10
+ readonly intro: string;
11
+ readonly adapters: readonly AdapterKind[];
12
+ readonly expected: string;
13
+ }
14
+ export declare const CONFORMANCE_REGISTRY: readonly RegisteredCase[];
15
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1,75 @@
1
+ export const ADAPTER_KINDS = ["store", "wake", "transport", "executor", "host"];
2
+ /**
3
+ * Every case of the durable-core conformance suite (packages/agent/docs/durable-core-adr.md). A change that adds cases
4
+ * registers each of them here, with its mapping, in the same change.
5
+ */
6
+ const STORE_CASES_IMPL = "packages/agent/src/durable/testing/store-cases.ts";
7
+ const KILL_STORE_IMPL = "packages/agent/src/durable/testing/kill-store.ts";
8
+ const WAKE_CASES_IMPL = "packages/agent/src/durable/testing/wake-cases.ts";
9
+ const MULTI_HOST_IMPL = "packages/agent/src/durable/testing/multi-host-cases.ts";
10
+ const KILL_WAKE_IMPL = "packages/agent/src/durable/testing/kill-wake.ts";
11
+ const TRANSPORT_CASES_IMPL = "packages/agent/src/transport/testing/transport-cases.ts";
12
+ const MULTI_CASES_IMPL = "packages/agent/src/transport/testing/multi-cases.ts";
13
+ const FAULT_CASES_IMPL = "packages/agent/src/transport/testing/fault-cases.ts";
14
+ const transportCase = (id, impl, expected) => ({
15
+ id,
16
+ impl,
17
+ intro: "#4741",
18
+ adapters: ["transport"],
19
+ expected,
20
+ });
21
+ const faultCase = (n, profile) => transportCase(`FAULT-TRANSPORT-0${n}`, FAULT_CASES_IMPL, `${profile} profile: lane order kept, committed frames delivered once, resume from the last ACK`);
22
+ const wakeCase = (id, impl, expected) => ({
23
+ id,
24
+ impl,
25
+ intro: "#4735",
26
+ adapters: ["store", "wake"],
27
+ expected,
28
+ });
29
+ const storeCase = (id, impl, expected) => ({
30
+ id,
31
+ impl,
32
+ intro: "#4730",
33
+ adapters: ["store"],
34
+ expected,
35
+ });
36
+ export const CONFORMANCE_REGISTRY = [
37
+ storeCase("STORE-01", STORE_CASES_IMPL, "a commit adopts once, after its batch is durable; every instance sees each batch whole"),
38
+ storeCase("STORE-02", STORE_CASES_IMPL, "a cut at every write point of a commit leaves nothing of it durable, with no adopt and no seq burned"),
39
+ storeCase("STORE-03", STORE_CASES_IMPL, "concurrent admissions run one at a time in admission order; a refused mutation releases the line"),
40
+ storeCase("STORE-04", STORE_CASES_IMPL, "an old owner's delayed commit or claim after the new owner's first commit is refused and changes nothing"),
41
+ storeCase("STORE-05", STORE_CASES_IMPL, "claim + import across hosts is whole, idempotent and tail-only; a diverged log is refused atomically"),
42
+ storeCase("STORE-06", STORE_CASES_IMPL, "minted IDs are unique; record and document scans, deletes and duplicate-ID refusal behave as specified"),
43
+ storeCase("STORE-07", STORE_CASES_IMPL, "a claim raises the epoch by exactly one; a claim that skips an epoch is refused and changes nothing"),
44
+ storeCase("KILL-STORE-01", KILL_STORE_IMPL, "everywhere: kill before a commit; after restart nothing is durable and the batch commits with the same IDs"),
45
+ storeCase("KILL-STORE-02", KILL_STORE_IMPL, "everywhere: kill after a commit is durable; after restart it is whole, stable IDs, and a retry applies nothing"),
46
+ storeCase("KILL-STORE-03", KILL_STORE_IMPL, "everywhere: kill before a claim; after restart the epoch is unchanged and the claim succeeds"),
47
+ storeCase("KILL-STORE-04", KILL_STORE_IMPL, "everywhere: kill after a claim is durable; after restart the new epoch holds and the old one is fenced"),
48
+ storeCase("KILL-STORE-05", KILL_STORE_IMPL, "everywhere: kill before an import; after restart nothing is imported and the import succeeds"),
49
+ storeCase("KILL-STORE-06", KILL_STORE_IMPL, "everywhere: kill after an import is durable; after restart it is whole and a re-import adds nothing"),
50
+ wakeCase("WAKE-01", WAKE_CASES_IMPL, "pre-arm: a recovery alarm is durable before admitted work starts and before a new instance resumes"),
51
+ wakeCase("WAKE-02", WAKE_CASES_IMPL, "update: after each commit the alarm is min(heartbeat if active, earliest wake), cleared only when idle"),
52
+ wakeCase("WAKE-03", WAKE_CASES_IMPL, "resume: a fire re-arms before resume, runs due work once, and an early fire is harmless"),
53
+ wakeCase("WAKE-RET-01", WAKE_CASES_IMPL, "retention: an alarm survives host restarts and reopened instances, fires once, and is cleared after the work"),
54
+ wakeCase("MULTI-HOST-01", MULTI_HOST_IMPL, "many sessions on one host instance: records and wakes stay per session"),
55
+ wakeCase("KILL-WAKE-01", KILL_WAKE_IMPL, "everywhere: kill during admit's alarm write; nothing is durable and a retried admission runs once"),
56
+ wakeCase("KILL-WAKE-02", KILL_WAKE_IMPL, "everywhere: kill after admit's arm, before work; the alarm fires harmlessly and ends cleared"),
57
+ wakeCase("KILL-WAKE-03", KILL_WAKE_IMPL, "everywhere: kill between a commit and its alarm update; the pre-armed heartbeat recovers the wake, run once"),
58
+ wakeCase("KILL-WAKE-04", KILL_WAKE_IMPL, "everywhere: eviction during the update's alarm write; the heartbeat recovers the wake, run once"),
59
+ wakeCase("KILL-WAKE-05", KILL_WAKE_IMPL, "everywhere: kill after a fire's re-arm, before resume; the next fire runs the due work once"),
60
+ wakeCase("KILL-WAKE-06", KILL_WAKE_IMPL, "everywhere: kill after a resumed task's commit is durable; the task never runs twice and the alarm clears"),
61
+ wakeCase("KILL-WAKE-07", KILL_WAKE_IMPL, "everywhere: eviction during the alarm clear after the work; the next fire is harmless and clears it"),
62
+ wakeCase("KILL-WAKE-08", KILL_WAKE_IMPL, "everywhere: a concurrent fire's updates cannot clear an uncommitted admission's heartbeat; a kill after its commit recovers the wake, run once"),
63
+ transportCase("TRANSPORT-01", TRANSPORT_CASES_IMPL, "order: frames on each lane are delivered in send order, each once"),
64
+ transportCase("TRANSPORT-02", TRANSPORT_CASES_IMPL, "resume: after the carrier drops and re-attaches, each lane continues from its last ACK; nothing lost or repeated"),
65
+ transportCase("TRANSPORT-03", TRANSPORT_CASES_IMPL, "backpressure: a stalled receiver bounds its lane's senders; sends resolve in order once it drains"),
66
+ transportCase("MULTI-LANE-01", MULTI_CASES_IMPL, "N scopes over one transport keep per-scope order and interleave"),
67
+ transportCase("MULTI-FAIR-01", MULTI_CASES_IMPL, "a stalled or long lane in A does not starve B: B is delivered within a bounded number of A's frames"),
68
+ transportCase("MULTI-FAIR-02", MULTI_CASES_IMPL, "a frame lost on one lane is resent on that lane's own deadline: another lane's ACK progress never postpones it"),
69
+ faultCase(1, "slow"),
70
+ faultCase(2, "lossy"),
71
+ faultCase(3, "laggy"),
72
+ faultCase(4, "flap"),
73
+ faultCase(5, "drop-reattach"),
74
+ ];
75
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +1,27 @@
1
+ import type { ConformanceManifest } from "./mapping.ts";
2
+ import type { AdapterKind } from "./registry.ts";
3
+ /** A host under test: its name in reports, the adapter kinds it provides, and the adapters handed to each case. */
4
+ export interface ConformanceHost<TAdapters> {
5
+ readonly name: string;
6
+ readonly provides: readonly AdapterKind[];
7
+ readonly adapters: TAdapters;
8
+ }
9
+ /** A case passes when it resolves and fails when it rejects. */
10
+ export type CaseImplementation<TAdapters> = (adapters: TAdapters) => Promise<void>;
11
+ export type CaseResult = {
12
+ readonly id: string;
13
+ readonly status: "pass";
14
+ } | {
15
+ readonly id: string;
16
+ readonly status: "fail";
17
+ readonly error: string;
18
+ };
19
+ /** One host's run of the manifest. There is no skip: a case that cannot run on the host fails. */
20
+ export interface HostReport {
21
+ readonly host: string;
22
+ readonly suite_hash: string;
23
+ readonly results: readonly CaseResult[];
24
+ }
25
+ /** Run every manifest case on `host`, one at a time in manifest order, and report under the manifest's suite hash. */
26
+ export declare function runSuite<TAdapters>(manifest: ConformanceManifest, host: ConformanceHost<TAdapters>, implementations: ReadonlyMap<string, CaseImplementation<TAdapters>>): Promise<HostReport>;
27
+ //# sourceMappingURL=runner.d.ts.map
@@ -0,0 +1,23 @@
1
+ async function runCase(id, adapters, host, implementation) {
2
+ if (implementation === undefined)
3
+ return { id, status: "fail", error: "no implementation is registered for this case" };
4
+ const missing = adapters.filter((kind) => !host.provides.includes(kind));
5
+ if (missing.length > 0)
6
+ return { id, status: "fail", error: `the host does not provide adapter: ${missing.join(", ")}` };
7
+ try {
8
+ await implementation(host.adapters);
9
+ return { id, status: "pass" };
10
+ }
11
+ catch (error) {
12
+ return { id, status: "fail", error: error instanceof Error ? error.message : String(error) };
13
+ }
14
+ }
15
+ /** Run every manifest case on `host`, one at a time in manifest order, and report under the manifest's suite hash. */
16
+ export async function runSuite(manifest, host, implementations) {
17
+ const results = [];
18
+ for (const entry of manifest.cases) {
19
+ results.push(await runCase(entry.id, entry.adapters, host, implementations.get(entry.id)));
20
+ }
21
+ return { host: host.name, suite_hash: manifest.suite_hash, results };
22
+ }
23
+ //# sourceMappingURL=runner.js.map
@@ -0,0 +1,12 @@
1
+ export interface SuiteHashEntry {
2
+ readonly id: string;
3
+ readonly impl_hash: string;
4
+ }
5
+ /** Lower-case hex SHA-256 through Web Crypto, so the same code runs on Bun, Node and a Durable Object. */
6
+ export declare function sha256Hex(bytes: Uint8Array): Promise<string>;
7
+ /**
8
+ * The suite hash: SHA-256 over one `id<TAB>impl_hash` line per case, sorted by ID. It changes when a case is added,
9
+ * removed or renamed, or when any case implementation changes, and never with registration order.
10
+ */
11
+ export declare function computeSuiteHash(entries: readonly SuiteHashEntry[]): Promise<string>;
12
+ //# sourceMappingURL=suite-hash.d.ts.map
@@ -0,0 +1,20 @@
1
+ /** Lower-case hex SHA-256 through Web Crypto, so the same code runs on Bun, Node and a Durable Object. */
2
+ export async function sha256Hex(bytes) {
3
+ const digest = new Uint8Array(await crypto.subtle.digest("SHA-256", new Uint8Array(bytes)));
4
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
5
+ }
6
+ /**
7
+ * The suite hash: SHA-256 over one `id<TAB>impl_hash` line per case, sorted by ID. It changes when a case is added,
8
+ * removed or renamed, or when any case implementation changes, and never with registration order.
9
+ */
10
+ export async function computeSuiteHash(entries) {
11
+ const ids = new Set();
12
+ for (const { id } of entries) {
13
+ if (ids.has(id))
14
+ throw new Error(`duplicate conformance case ID in the suite hash: ${id}`);
15
+ ids.add(id);
16
+ }
17
+ const lines = entries.map(({ id, impl_hash }) => `${id}\t${impl_hash}\n`).sort();
18
+ return sha256Hex(new TextEncoder().encode(lines.join("")));
19
+ }
20
+ //# sourceMappingURL=suite-hash.js.map
@@ -0,0 +1,7 @@
1
+ /** The single admission line above a DurableStore: mutations run one at a time, in admission order. */
2
+ export interface AdmissionLine {
3
+ /** Run `mutation` after every earlier admission settles. A rejected mutation releases the line. */
4
+ admit<T>(mutation: () => Promise<T>): Promise<T>;
5
+ }
6
+ export declare function createAdmissionLine(): AdmissionLine;
7
+ //# sourceMappingURL=store-admission.d.ts.map
@@ -0,0 +1,11 @@
1
+ export function createAdmissionLine() {
2
+ let tail = Promise.resolve();
3
+ return {
4
+ admit(mutation) {
5
+ const run = tail.then(() => mutation());
6
+ tail = run.then(() => undefined, () => undefined);
7
+ return run;
8
+ },
9
+ };
10
+ }
11
+ //# sourceMappingURL=store-admission.js.map
@@ -0,0 +1,15 @@
1
+ import { type DurableStore, type DurableStoreOptions, type StoreRecord, type StoreValue } from "./store.ts";
2
+ interface MediumState {
3
+ readonly epoch: number;
4
+ readonly records: readonly StoreRecord[];
5
+ readonly documents: ReadonlyMap<string, StoreValue>;
6
+ }
7
+ /** The memory backend's durable medium. It outlives every store opened on it, so a kill is dropping an instance. */
8
+ export interface MemoryStoreMedium {
9
+ current: MediumState;
10
+ }
11
+ export declare function createMemoryStoreMedium(): MemoryStoreMedium;
12
+ /** A DurableStore over `medium`. Each operation stages its writes and publishes them in one assignment: atomic. */
13
+ export declare function openMemoryStore(medium: MemoryStoreMedium, options?: DurableStoreOptions): DurableStore;
14
+ export {};
15
+ //# sourceMappingURL=store-memory.d.ts.map
@@ -0,0 +1,140 @@
1
+ import { DurableStoreError, } from "./store.js";
2
+ export function createMemoryStoreMedium() {
3
+ return { current: { epoch: 0, records: [], documents: new Map() } };
4
+ }
5
+ const copy = (value) => structuredClone(value);
6
+ const copyRecord = (record) => ({ ...record, data: copy(record.data) });
7
+ const isList = (value) => Array.isArray(value);
8
+ function sameValue(a, b) {
9
+ if (a === undefined || b === undefined || a === null || b === null)
10
+ return a === b;
11
+ if (typeof a !== "object" || typeof b !== "object")
12
+ return a === b;
13
+ if (isList(a) || isList(b)) {
14
+ return isList(a) && isList(b) && a.length === b.length && a.every((item, index) => sameValue(item, b[index]));
15
+ }
16
+ const keys = Object.keys(a);
17
+ return (keys.length === Object.keys(b).length && keys.every((key) => Object.hasOwn(b, key) && sameValue(a[key], b[key])));
18
+ }
19
+ const sameRecord = (a, b) => b !== undefined && a.seq === b.seq && a.id === b.id && a.kind === b.kind && sameValue(a.data, b.data);
20
+ function requireOwner(state, epoch) {
21
+ if (state.epoch === 0)
22
+ throw new DurableStoreError("unclaimed", "the store has no owner; claim it first");
23
+ if (epoch !== state.epoch) {
24
+ throw new DurableStoreError("stale-epoch", `epoch ${epoch} is not the owner epoch ${state.epoch}`);
25
+ }
26
+ }
27
+ function importedTail(state, snapshot) {
28
+ const ids = new Set();
29
+ const wellFormed = snapshot.head === snapshot.records.length &&
30
+ snapshot.records.every((record, index) => record.seq === index + 1 && !ids.has(record.id) && ids.add(record.id));
31
+ const extendsLog = state.records.length <= snapshot.records.length &&
32
+ state.records.every((record, index) => sameRecord(record, snapshot.records[index]));
33
+ if (!wellFormed || !extendsLog) {
34
+ throw new DurableStoreError("import-conflict", "the snapshot's log does not extend this store's log");
35
+ }
36
+ return snapshot.records.slice(state.records.length);
37
+ }
38
+ function scan(records, query) {
39
+ const matching = records.filter((record) => (query.fromSeq === undefined || record.seq >= query.fromSeq) &&
40
+ (query.toSeq === undefined || record.seq <= query.toSeq) &&
41
+ (query.kind === undefined || record.kind === query.kind));
42
+ const ordered = query.order === "desc" ? matching.reverse() : matching;
43
+ return (query.limit === undefined ? ordered : ordered.slice(0, query.limit)).map(copyRecord);
44
+ }
45
+ const documentsOf = (state, prefix) => [...state.documents]
46
+ .filter(([key]) => key.startsWith(prefix))
47
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
48
+ .map(([key, data]) => ({ key, data: copy(data) }));
49
+ /** A DurableStore over `medium`. Each operation stages its writes and publishes them in one assignment: atomic. */
50
+ export function openMemoryStore(medium, options = {}) {
51
+ const point = (at) => options.onWritePoint?.(at);
52
+ return {
53
+ readEpoch: async () => medium.current.epoch,
54
+ readHead: async () => medium.current.records.length,
55
+ async claim(expectedEpoch, nextEpoch) {
56
+ point({ op: "claim", at: "before" });
57
+ const state = medium.current;
58
+ if (!Number.isSafeInteger(nextEpoch) || nextEpoch !== expectedEpoch + 1) {
59
+ throw new DurableStoreError("invalid-claim", `a claim must raise the epoch to exactly ${expectedEpoch + 1}`);
60
+ }
61
+ if (state.epoch !== expectedEpoch) {
62
+ throw new DurableStoreError("stale-epoch", `expected epoch ${expectedEpoch}, the owner epoch is ${state.epoch}`);
63
+ }
64
+ medium.current = { ...state, epoch: nextEpoch };
65
+ point({ op: "claim", at: "durable" });
66
+ },
67
+ async commit(epoch, writes, adopt) {
68
+ point({ op: "commit", at: "before" });
69
+ const state = medium.current;
70
+ requireOwner(state, epoch);
71
+ const records = [...state.records];
72
+ const ids = new Set(records.map((record) => record.id));
73
+ const documents = new Map(state.documents);
74
+ const seqs = [];
75
+ writes.forEach((write, index) => {
76
+ point({ op: "commit", at: "write", index });
77
+ switch (write.op) {
78
+ case "append": {
79
+ if (ids.has(write.id))
80
+ throw new DurableStoreError("duplicate-record-id", `record ${write.id} exists`);
81
+ ids.add(write.id);
82
+ const seq = records.length + 1;
83
+ records.push({ seq, id: write.id, kind: write.kind, data: copy(write.data) });
84
+ seqs.push(seq);
85
+ break;
86
+ }
87
+ case "put":
88
+ documents.set(write.key, copy(write.data));
89
+ break;
90
+ case "delete":
91
+ documents.delete(write.key);
92
+ break;
93
+ }
94
+ });
95
+ medium.current = { epoch: state.epoch, records, documents };
96
+ point({ op: "commit", at: "durable" });
97
+ const result = { seqs, head: records.length };
98
+ adopt(result);
99
+ return result;
100
+ },
101
+ mintId: () => crypto.randomUUID(),
102
+ scanRecords: async (query = {}) => scan(medium.current.records, query),
103
+ getDocument: async (key) => {
104
+ const data = medium.current.documents.get(key);
105
+ return data === undefined ? undefined : copy(data);
106
+ },
107
+ scanDocuments: async (prefix) => documentsOf(medium.current, prefix),
108
+ exportSnapshot: async () => {
109
+ const state = medium.current;
110
+ return {
111
+ epoch: state.epoch,
112
+ head: state.records.length,
113
+ records: state.records.map(copyRecord),
114
+ documents: documentsOf(state, ""),
115
+ };
116
+ },
117
+ async importSnapshot(epoch, snapshot) {
118
+ point({ op: "import", at: "before" });
119
+ const state = medium.current;
120
+ requireOwner(state, epoch);
121
+ if (epoch <= snapshot.epoch) {
122
+ throw new DurableStoreError("invalid-claim", `epoch ${epoch} is not above the source epoch ${snapshot.epoch}`);
123
+ }
124
+ const records = [...state.records];
125
+ const documents = new Map();
126
+ let index = 0;
127
+ for (const record of importedTail(state, snapshot)) {
128
+ point({ op: "import", at: "write", index: index++ });
129
+ records.push(copyRecord(record));
130
+ }
131
+ for (const document of snapshot.documents) {
132
+ point({ op: "import", at: "write", index: index++ });
133
+ documents.set(document.key, copy(document.data));
134
+ }
135
+ medium.current = { epoch: state.epoch, records, documents };
136
+ point({ op: "import", at: "durable" });
137
+ },
138
+ };
139
+ }
140
+ //# sourceMappingURL=store-memory.js.map
@@ -0,0 +1,116 @@
1
+ /**
2
+ * The DurableStore contract of the durable core (packages/agent/docs/durable-core-adr.md). Every backend (memory, Bun
3
+ * SQLite, Durable Object SQLite) implements it and passes the STORE and KILL-STORE conformance cases.
4
+ *
5
+ * The store does not serialize concurrent writers: a single admission line above it (store-admission.ts) does.
6
+ */
7
+ /** A JSON value as the store persists it. */
8
+ export type StoreValue = null | boolean | number | string | readonly StoreValue[] | {
9
+ readonly [key: string]: StoreValue;
10
+ };
11
+ /** One log record. `seq` is assigned by the store: gapless from 1, in commit order. `id` is unique in the log. */
12
+ export interface StoreRecord {
13
+ readonly seq: number;
14
+ readonly id: string;
15
+ readonly kind: string;
16
+ readonly data: StoreValue;
17
+ }
18
+ export interface StoreDocument {
19
+ readonly key: string;
20
+ readonly data: StoreValue;
21
+ }
22
+ export type StoreWrite = {
23
+ readonly op: "append";
24
+ readonly id: string;
25
+ readonly kind: string;
26
+ readonly data: StoreValue;
27
+ } | {
28
+ readonly op: "put";
29
+ readonly key: string;
30
+ readonly data: StoreValue;
31
+ } | {
32
+ readonly op: "delete";
33
+ readonly key: string;
34
+ };
35
+ export interface CommitResult {
36
+ /** The sequence numbers of the batch's appends, in write order. */
37
+ readonly seqs: readonly number[];
38
+ /** The last committed sequence number after this batch (0 for an empty log). */
39
+ readonly head: number;
40
+ }
41
+ /** Everything a store holds; the unit of claim + import between hosts. */
42
+ export interface StoreSnapshot {
43
+ readonly epoch: number;
44
+ readonly head: number;
45
+ /** Every record, ascending by `seq`. */
46
+ readonly records: readonly StoreRecord[];
47
+ /** Every document, ascending by `key`. */
48
+ readonly documents: readonly StoreDocument[];
49
+ }
50
+ export interface RecordScan {
51
+ readonly fromSeq?: number;
52
+ readonly toSeq?: number;
53
+ readonly kind?: string;
54
+ readonly order?: "asc" | "desc";
55
+ readonly limit?: number;
56
+ }
57
+ export type StoreOperation = "commit" | "claim" | "import";
58
+ /**
59
+ * A point inside a durable write. `before`: nothing of the operation is durable yet. `write`: inside the atomic unit,
60
+ * before write `index` is staged. `durable`: the operation is durable, before `adopt` runs and before it resolves.
61
+ */
62
+ export type StoreWritePoint = {
63
+ readonly op: StoreOperation;
64
+ readonly at: "before";
65
+ } | {
66
+ readonly op: "commit" | "import";
67
+ readonly at: "write";
68
+ readonly index: number;
69
+ } | {
70
+ readonly op: StoreOperation;
71
+ readonly at: "durable";
72
+ };
73
+ export interface DurableStoreOptions {
74
+ /**
75
+ * Test seam for cuts and kills: called at every write point. A throw at `before` or `write` leaves nothing of the
76
+ * operation durable; a throw at `durable` leaves all of it durable and skips `adopt`.
77
+ */
78
+ readonly onWritePoint?: (point: StoreWritePoint) => void;
79
+ }
80
+ export type DurableStoreErrorCode = "unclaimed" | "stale-epoch" | "invalid-claim" | "duplicate-record-id" | "import-conflict";
81
+ export declare class DurableStoreError extends Error {
82
+ readonly code: DurableStoreErrorCode;
83
+ constructor(code: DurableStoreErrorCode, message: string);
84
+ }
85
+ export interface DurableStore {
86
+ /** The current owner epoch; 0 when the store was never claimed. */
87
+ readEpoch(): Promise<number>;
88
+ /** The last committed sequence number; 0 for an empty log. */
89
+ readHead(): Promise<number>;
90
+ /**
91
+ * Compare-and-set the owner epoch from `expectedEpoch` to `nextEpoch`, which must be exactly `expectedEpoch + 1`; any
92
+ * other `nextEpoch` (equal, lower, or skipping an epoch) is refused (`invalid-claim`) and changes nothing. A claimant
93
+ * whose expected epoch is no longer current is refused (`stale-epoch`), so an old owner can never take the store back.
94
+ */
95
+ claim(expectedEpoch: number, nextEpoch: number): Promise<void>;
96
+ /**
97
+ * Commit a batch atomically under the owner `epoch`: all of it becomes durable, or none of it. `adopt` runs exactly
98
+ * once, after the batch is durable and before the commit resolves, and never for a batch that did not become
99
+ * durable. A commit from any epoch but the current one is refused (`stale-epoch`) and leaves the store unchanged.
100
+ */
101
+ commit(epoch: number, writes: readonly StoreWrite[], adopt: (result: CommitResult) => void): Promise<CommitResult>;
102
+ /** A new ID, unique across instances, reopens and hosts. */
103
+ mintId(): string;
104
+ scanRecords(query?: RecordScan): Promise<StoreRecord[]>;
105
+ getDocument(key: string): Promise<StoreValue | undefined>;
106
+ /** Documents whose key starts with `prefix`, ascending by key. */
107
+ scanDocuments(prefix: string): Promise<StoreDocument[]>;
108
+ exportSnapshot(): Promise<StoreSnapshot>;
109
+ /**
110
+ * Import another host's snapshot under this store's current owner `epoch`, which must be above the snapshot's.
111
+ * Atomic. The store's records must be a prefix of the snapshot's: records past the store's head are imported once,
112
+ * in order, with their seqs and IDs; documents become the snapshot's. Importing the same snapshot again is a no-op.
113
+ */
114
+ importSnapshot(epoch: number, snapshot: StoreSnapshot): Promise<void>;
115
+ }
116
+ //# sourceMappingURL=store.d.ts.map
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The DurableStore contract of the durable core (packages/agent/docs/durable-core-adr.md). Every backend (memory, Bun
3
+ * SQLite, Durable Object SQLite) implements it and passes the STORE and KILL-STORE conformance cases.
4
+ *
5
+ * The store does not serialize concurrent writers: a single admission line above it (store-admission.ts) does.
6
+ */
7
+ export class DurableStoreError extends Error {
8
+ constructor(code, message) {
9
+ super(message);
10
+ this.name = "DurableStoreError";
11
+ this.code = code;
12
+ }
13
+ }
14
+ //# sourceMappingURL=store.js.map