@crewhaus/continuity-store 0.3.0
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/README.md +98 -0
- package/dist/evidence.d.ts +73 -0
- package/dist/evidence.js +238 -0
- package/dist/handoff.d.ts +25 -0
- package/dist/handoff.js +100 -0
- package/dist/index.d.ts +118 -0
- package/dist/index.js +765 -0
- package/dist/lock.d.ts +35 -0
- package/dist/lock.js +43 -0
- package/dist/trash.d.ts +68 -0
- package/dist/trash.js +196 -0
- package/dist/types.d.ts +68 -0
- package/dist/types.js +1 -0
- package/package.json +43 -0
package/dist/lock.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory single-writer `.lock` for the update-in-place continuity stores
|
|
3
|
+
* (design §7.6): wait up to 2 s → steal a lock whose mtime is > 30 s stale
|
|
4
|
+
* (with a `lock_stolen` warning naming the dead holder) → fail with a
|
|
5
|
+
* `ContinuityLockError` naming the holder pid.
|
|
6
|
+
*
|
|
7
|
+
* The policy IMPLEMENTATION lives in `@crewhaus/infra-utils`
|
|
8
|
+
* (`acquireFileLock`/`withFileLock`) — unified there by the
|
|
9
|
+
* composition-root PR after continuity-store and wiki-store shipped
|
|
10
|
+
* byte-identical copies on parallel 0.3.0 branches. This module keeps the
|
|
11
|
+
* store's public lock surface (error identity, message prefix, option and
|
|
12
|
+
* handle types) exactly as its tests pin it.
|
|
13
|
+
*/
|
|
14
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
15
|
+
import { type FileLockHandle, type FileLockPolicy } from "@crewhaus/infra-utils";
|
|
16
|
+
export declare class ContinuityLockError extends CrewhausError {
|
|
17
|
+
readonly name = "ContinuityLockError";
|
|
18
|
+
constructor(message: string, cause?: unknown);
|
|
19
|
+
}
|
|
20
|
+
export type LockPolicy = FileLockPolicy;
|
|
21
|
+
export declare const DEFAULT_LOCK_POLICY: LockPolicy;
|
|
22
|
+
export type AcquireLockOptions = Partial<LockPolicy> & {
|
|
23
|
+
/** Receives the `lock_stolen` warning line. Default: `console.error`. */
|
|
24
|
+
readonly onWarn?: (message: string) => void;
|
|
25
|
+
};
|
|
26
|
+
export type LockHandle = FileLockHandle;
|
|
27
|
+
/**
|
|
28
|
+
* Acquire the advisory lock at `lockPath` under the §7.6 policy. Resolves to
|
|
29
|
+
* a handle whose `release()` unlinks the file; rejects with
|
|
30
|
+
* `ContinuityLockError` (naming the holder pid) when the lock stays held past
|
|
31
|
+
* `waitMs` without going stale.
|
|
32
|
+
*/
|
|
33
|
+
export declare function acquireLock(lockPath: string, opts?: AcquireLockOptions): Promise<LockHandle>;
|
|
34
|
+
/** Run `fn` while holding the lock at `lockPath`; always releases. */
|
|
35
|
+
export declare function withLock<T>(lockPath: string, fn: () => Promise<T>, opts?: AcquireLockOptions): Promise<T>;
|
package/dist/lock.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Advisory single-writer `.lock` for the update-in-place continuity stores
|
|
3
|
+
* (design §7.6): wait up to 2 s → steal a lock whose mtime is > 30 s stale
|
|
4
|
+
* (with a `lock_stolen` warning naming the dead holder) → fail with a
|
|
5
|
+
* `ContinuityLockError` naming the holder pid.
|
|
6
|
+
*
|
|
7
|
+
* The policy IMPLEMENTATION lives in `@crewhaus/infra-utils`
|
|
8
|
+
* (`acquireFileLock`/`withFileLock`) — unified there by the
|
|
9
|
+
* composition-root PR after continuity-store and wiki-store shipped
|
|
10
|
+
* byte-identical copies on parallel 0.3.0 branches. This module keeps the
|
|
11
|
+
* store's public lock surface (error identity, message prefix, option and
|
|
12
|
+
* handle types) exactly as its tests pin it.
|
|
13
|
+
*/
|
|
14
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
15
|
+
import { DEFAULT_FILE_LOCK_POLICY, acquireFileLock, withFileLock, } from "@crewhaus/infra-utils";
|
|
16
|
+
export class ContinuityLockError extends CrewhausError {
|
|
17
|
+
name = "ContinuityLockError";
|
|
18
|
+
constructor(message, cause) {
|
|
19
|
+
super("runtime", message, cause);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export const DEFAULT_LOCK_POLICY = DEFAULT_FILE_LOCK_POLICY;
|
|
23
|
+
const STORE_LABEL = "continuity-store";
|
|
24
|
+
function toFileLockOptions(opts) {
|
|
25
|
+
return {
|
|
26
|
+
...opts,
|
|
27
|
+
label: STORE_LABEL,
|
|
28
|
+
createError: (message) => new ContinuityLockError(message),
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Acquire the advisory lock at `lockPath` under the §7.6 policy. Resolves to
|
|
33
|
+
* a handle whose `release()` unlinks the file; rejects with
|
|
34
|
+
* `ContinuityLockError` (naming the holder pid) when the lock stays held past
|
|
35
|
+
* `waitMs` without going stale.
|
|
36
|
+
*/
|
|
37
|
+
export async function acquireLock(lockPath, opts = {}) {
|
|
38
|
+
return acquireFileLock(lockPath, toFileLockOptions(opts));
|
|
39
|
+
}
|
|
40
|
+
/** Run `fn` while holding the lock at `lockPath`; always releases. */
|
|
41
|
+
export async function withLock(lockPath, fn, opts = {}) {
|
|
42
|
+
return withFileLock(lockPath, fn, toFileLockOptions(opts));
|
|
43
|
+
}
|
package/dist/trash.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
2
|
+
export declare class TrashError extends CrewhausError {
|
|
3
|
+
readonly name = "TrashError";
|
|
4
|
+
constructor(message: string, cause?: unknown);
|
|
5
|
+
}
|
|
6
|
+
export declare const TRASH_DIR_NAME = "trash";
|
|
7
|
+
export type MoveToTrashResult = {
|
|
8
|
+
/** The snapshot directory everything moved into. */
|
|
9
|
+
readonly trashDir: string;
|
|
10
|
+
/** The snapshot timestamp — the `restore()` handle. */
|
|
11
|
+
readonly ts: string;
|
|
12
|
+
/** Paths that were moved, relative to the `.crewhaus` dir. */
|
|
13
|
+
readonly moved: readonly string[];
|
|
14
|
+
};
|
|
15
|
+
export type TrashSnapshot = {
|
|
16
|
+
readonly ts: string;
|
|
17
|
+
/** Files inside the snapshot, relative to the `.crewhaus` dir. */
|
|
18
|
+
readonly files: readonly string[];
|
|
19
|
+
};
|
|
20
|
+
export type RestoreResult = {
|
|
21
|
+
readonly ts: string;
|
|
22
|
+
/** Restored file paths, relative to the `.crewhaus` dir. */
|
|
23
|
+
readonly restored: readonly string[];
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Move `paths` (files or whole directories) into a fresh trash snapshot under
|
|
27
|
+
* `<crewhausDir>/trash/<ts>/`, preserving each path's location relative to
|
|
28
|
+
* `crewhausDir`. Missing paths are skipped (clearing an empty store is a
|
|
29
|
+
* no-op, not an error); paths outside `crewhausDir` — or inside the trash
|
|
30
|
+
* itself — fail closed. The snapshot directory is only created when at least
|
|
31
|
+
* one path actually moves.
|
|
32
|
+
*/
|
|
33
|
+
export declare function moveToTrash(paths: readonly string[], crewhausDir: string, opts?: {
|
|
34
|
+
readonly now?: () => Date;
|
|
35
|
+
}): Promise<MoveToTrashResult>;
|
|
36
|
+
/** List every trash snapshot under `<crewhausDir>/trash/`, oldest first. */
|
|
37
|
+
export declare function listTrash(crewhausDir: string): Promise<readonly TrashSnapshot[]>;
|
|
38
|
+
/**
|
|
39
|
+
* Move every file of snapshot `ts` back to its original location under
|
|
40
|
+
* `crewhausDir`. Fail-closed: if ANY destination already exists the restore
|
|
41
|
+
* throws before moving anything (a clear made after the snapshot must not be
|
|
42
|
+
* silently clobbered). The emptied snapshot directory is removed afterwards.
|
|
43
|
+
*/
|
|
44
|
+
export declare function restoreFromTrash(ts: string, crewhausDir: string): Promise<RestoreResult>;
|
|
45
|
+
/** Default purge window (design §2.6: "trash is purged after 7 days"). */
|
|
46
|
+
export declare const TRASH_PURGE_AFTER_MS: number;
|
|
47
|
+
export type PurgeTrashResult = {
|
|
48
|
+
/** Snapshot timestamps that were purged, oldest first. */
|
|
49
|
+
readonly purged: readonly string[];
|
|
50
|
+
/** Snapshots kept (younger than the window). */
|
|
51
|
+
readonly kept: number;
|
|
52
|
+
};
|
|
53
|
+
/** Parse a trash snapshot timestamp (`YYYY-MM-DDTHH-MM-SS`, produced from
|
|
54
|
+
* `toISOString()`) back to epoch ms. Collision suffixes (`-N`) share the
|
|
55
|
+
* base timestamp. Returns null for a non-matching name. */
|
|
56
|
+
export declare function parseTrashTimestamp(ts: string): number | null;
|
|
57
|
+
/**
|
|
58
|
+
* Hard-delete every trash snapshot STRICTLY older than `olderThanMs`
|
|
59
|
+
* (default {@link TRASH_PURGE_AFTER_MS} — the design's 7-day undo window).
|
|
60
|
+
* A snapshot exactly at the boundary is KEPT: the undo window is inclusive,
|
|
61
|
+
* so "purged after 7 days" never eats a restore attempted at 7 days sharp.
|
|
62
|
+
* This is the one sanctioned hard delete in the clearing story — everything
|
|
63
|
+
* in the trash already survived its undo window.
|
|
64
|
+
*/
|
|
65
|
+
export declare function purgeTrash(crewhausDir: string, opts?: {
|
|
66
|
+
readonly olderThanMs?: number;
|
|
67
|
+
readonly now?: () => Date;
|
|
68
|
+
}): Promise<PurgeTrashResult>;
|
package/dist/trash.js
ADDED
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Trash-and-undo clearing (design §2.6): user-facing "clear" NEVER
|
|
3
|
+
* hard-deletes. Files move to `.crewhaus/trash/<timestamp>/…` preserving their
|
|
4
|
+
* path relative to the `.crewhaus` directory, so `restore(<timestamp>)` can
|
|
5
|
+
* put every file back exactly where it came from. Trash purge (7-day window
|
|
6
|
+
* per the design) lives here as `purgeTrash` — SCHEDULING it is a
|
|
7
|
+
* janitor/dream concern (the dream engine's phase-1 pass calls it), but the
|
|
8
|
+
* layout knowledge stays in this one module.
|
|
9
|
+
*
|
|
10
|
+
* `moveToTrash` is exported as a reusable helper so other `.crewhaus` stores
|
|
11
|
+
* (wiki-store, memory-store's clear verbs) can adopt the identical clearing
|
|
12
|
+
* story later without re-deriving the layout.
|
|
13
|
+
*/
|
|
14
|
+
import { existsSync } from "node:fs";
|
|
15
|
+
import { lstat, mkdir, readdir, rename, rm } from "node:fs/promises";
|
|
16
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
17
|
+
import { CrewhausError } from "@crewhaus/errors";
|
|
18
|
+
export class TrashError extends CrewhausError {
|
|
19
|
+
name = "TrashError";
|
|
20
|
+
constructor(message, cause) {
|
|
21
|
+
super("config", message, cause);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
export const TRASH_DIR_NAME = "trash";
|
|
25
|
+
/** `2026-07-13T19-04-12` (+ optional `-N` collision suffix) — filesystem-safe
|
|
26
|
+
* ISO seconds, matching the design's clearing transcript. */
|
|
27
|
+
const TRASH_TS_REGEX = /^\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}(-\d+)?$/;
|
|
28
|
+
function trashTimestamp(now) {
|
|
29
|
+
return now().toISOString().slice(0, 19).replace(/:/g, "-");
|
|
30
|
+
}
|
|
31
|
+
function assertUnder(absPath, root, what) {
|
|
32
|
+
if (absPath !== root && !absPath.startsWith(`${root}/`)) {
|
|
33
|
+
throw new TrashError(`${what} "${absPath}" is outside the .crewhaus dir "${root}" — refusing to trash it`);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
async function walkFiles(dir, base) {
|
|
37
|
+
const out = [];
|
|
38
|
+
let entries;
|
|
39
|
+
try {
|
|
40
|
+
entries = await readdir(dir, { withFileTypes: true });
|
|
41
|
+
}
|
|
42
|
+
catch (err) {
|
|
43
|
+
if (err.code === "ENOENT")
|
|
44
|
+
return out;
|
|
45
|
+
throw err;
|
|
46
|
+
}
|
|
47
|
+
for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
|
|
48
|
+
const full = join(dir, entry.name);
|
|
49
|
+
if (entry.isDirectory()) {
|
|
50
|
+
out.push(...(await walkFiles(full, base)));
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
out.push(relative(base, full));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return out;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Move `paths` (files or whole directories) into a fresh trash snapshot under
|
|
60
|
+
* `<crewhausDir>/trash/<ts>/`, preserving each path's location relative to
|
|
61
|
+
* `crewhausDir`. Missing paths are skipped (clearing an empty store is a
|
|
62
|
+
* no-op, not an error); paths outside `crewhausDir` — or inside the trash
|
|
63
|
+
* itself — fail closed. The snapshot directory is only created when at least
|
|
64
|
+
* one path actually moves.
|
|
65
|
+
*/
|
|
66
|
+
export async function moveToTrash(paths, crewhausDir, opts = {}) {
|
|
67
|
+
const now = opts.now ?? (() => new Date());
|
|
68
|
+
const root = resolve(crewhausDir);
|
|
69
|
+
const trashRoot = join(root, TRASH_DIR_NAME);
|
|
70
|
+
let ts = trashTimestamp(now);
|
|
71
|
+
let trashDir = join(trashRoot, ts);
|
|
72
|
+
for (let suffix = 2; existsSync(trashDir); suffix++) {
|
|
73
|
+
ts = `${trashTimestamp(now)}-${suffix}`;
|
|
74
|
+
trashDir = join(trashRoot, ts);
|
|
75
|
+
}
|
|
76
|
+
const moved = [];
|
|
77
|
+
for (const path of paths) {
|
|
78
|
+
const abs = resolve(path);
|
|
79
|
+
assertUnder(abs, root, "path");
|
|
80
|
+
if (abs === trashRoot || abs.startsWith(`${trashRoot}/`)) {
|
|
81
|
+
throw new TrashError(`path "${abs}" is inside the trash — refusing to trash the trash`);
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await lstat(abs);
|
|
85
|
+
}
|
|
86
|
+
catch (err) {
|
|
87
|
+
if (err.code === "ENOENT")
|
|
88
|
+
continue;
|
|
89
|
+
throw err;
|
|
90
|
+
}
|
|
91
|
+
const rel = relative(root, abs);
|
|
92
|
+
const dest = join(trashDir, rel);
|
|
93
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
94
|
+
await rename(abs, dest);
|
|
95
|
+
moved.push(rel);
|
|
96
|
+
}
|
|
97
|
+
return { trashDir, ts, moved };
|
|
98
|
+
}
|
|
99
|
+
/** List every trash snapshot under `<crewhausDir>/trash/`, oldest first. */
|
|
100
|
+
export async function listTrash(crewhausDir) {
|
|
101
|
+
const trashRoot = join(resolve(crewhausDir), TRASH_DIR_NAME);
|
|
102
|
+
let entries;
|
|
103
|
+
try {
|
|
104
|
+
entries = await readdir(trashRoot);
|
|
105
|
+
}
|
|
106
|
+
catch (err) {
|
|
107
|
+
if (err.code === "ENOENT")
|
|
108
|
+
return [];
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
const snapshots = [];
|
|
112
|
+
for (const entry of entries.filter((e) => TRASH_TS_REGEX.test(e)).sort()) {
|
|
113
|
+
const snapshotDir = join(trashRoot, entry);
|
|
114
|
+
snapshots.push({ ts: entry, files: await walkFiles(snapshotDir, snapshotDir) });
|
|
115
|
+
}
|
|
116
|
+
return snapshots;
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Move every file of snapshot `ts` back to its original location under
|
|
120
|
+
* `crewhausDir`. Fail-closed: if ANY destination already exists the restore
|
|
121
|
+
* throws before moving anything (a clear made after the snapshot must not be
|
|
122
|
+
* silently clobbered). The emptied snapshot directory is removed afterwards.
|
|
123
|
+
*/
|
|
124
|
+
export async function restoreFromTrash(ts, crewhausDir) {
|
|
125
|
+
if (!TRASH_TS_REGEX.test(ts)) {
|
|
126
|
+
throw new TrashError(`invalid trash timestamp "${ts}" — expected YYYY-MM-DDTHH-MM-SS`);
|
|
127
|
+
}
|
|
128
|
+
const root = resolve(crewhausDir);
|
|
129
|
+
const trashRoot = join(root, TRASH_DIR_NAME);
|
|
130
|
+
const trashDir = join(trashRoot, ts);
|
|
131
|
+
if (!existsSync(trashDir)) {
|
|
132
|
+
throw new TrashError(`no trash snapshot "${ts}" under ${trashRoot}`);
|
|
133
|
+
}
|
|
134
|
+
const files = await walkFiles(trashDir, trashDir);
|
|
135
|
+
// Conflict pre-check before any move, so a failed restore changes nothing.
|
|
136
|
+
for (const rel of files) {
|
|
137
|
+
const dest = join(root, rel);
|
|
138
|
+
if (existsSync(dest)) {
|
|
139
|
+
throw new TrashError(`restore ${ts} would overwrite "${dest}" — move the current file aside first`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const restored = [];
|
|
143
|
+
for (const rel of files) {
|
|
144
|
+
const dest = join(root, rel);
|
|
145
|
+
await mkdir(dirname(dest), { recursive: true });
|
|
146
|
+
await rename(join(trashDir, rel), dest);
|
|
147
|
+
restored.push(rel);
|
|
148
|
+
}
|
|
149
|
+
await rm(trashDir, { recursive: true, force: true });
|
|
150
|
+
return { ts, restored };
|
|
151
|
+
}
|
|
152
|
+
/** Default purge window (design §2.6: "trash is purged after 7 days"). */
|
|
153
|
+
export const TRASH_PURGE_AFTER_MS = 7 * 24 * 60 * 60 * 1000;
|
|
154
|
+
/** Parse a trash snapshot timestamp (`YYYY-MM-DDTHH-MM-SS`, produced from
|
|
155
|
+
* `toISOString()`) back to epoch ms. Collision suffixes (`-N`) share the
|
|
156
|
+
* base timestamp. Returns null for a non-matching name. */
|
|
157
|
+
export function parseTrashTimestamp(ts) {
|
|
158
|
+
if (!TRASH_TS_REGEX.test(ts))
|
|
159
|
+
return null;
|
|
160
|
+
const base = ts.slice(0, 19); // strip any -N collision suffix
|
|
161
|
+
const iso = `${base.slice(0, 13)}:${base.slice(14, 16)}:${base.slice(17, 19)}Z`;
|
|
162
|
+
const parsed = Date.parse(iso);
|
|
163
|
+
return Number.isNaN(parsed) ? null : parsed;
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Hard-delete every trash snapshot STRICTLY older than `olderThanMs`
|
|
167
|
+
* (default {@link TRASH_PURGE_AFTER_MS} — the design's 7-day undo window).
|
|
168
|
+
* A snapshot exactly at the boundary is KEPT: the undo window is inclusive,
|
|
169
|
+
* so "purged after 7 days" never eats a restore attempted at 7 days sharp.
|
|
170
|
+
* This is the one sanctioned hard delete in the clearing story — everything
|
|
171
|
+
* in the trash already survived its undo window.
|
|
172
|
+
*/
|
|
173
|
+
export async function purgeTrash(crewhausDir, opts = {}) {
|
|
174
|
+
const olderThanMs = opts.olderThanMs ?? TRASH_PURGE_AFTER_MS;
|
|
175
|
+
const now = opts.now ?? (() => new Date());
|
|
176
|
+
const cutoff = now().getTime() - olderThanMs;
|
|
177
|
+
const trashRoot = join(resolve(crewhausDir), TRASH_DIR_NAME);
|
|
178
|
+
const snapshots = await listTrash(crewhausDir);
|
|
179
|
+
const purged = [];
|
|
180
|
+
let kept = 0;
|
|
181
|
+
for (const snapshot of snapshots) {
|
|
182
|
+
const ts = parseTrashTimestamp(snapshot.ts);
|
|
183
|
+
if (ts === null) {
|
|
184
|
+
kept += 1;
|
|
185
|
+
continue; // unrecognized name — never delete what we can't date
|
|
186
|
+
}
|
|
187
|
+
if (ts < cutoff) {
|
|
188
|
+
await rm(join(trashRoot, snapshot.ts), { recursive: true, force: true });
|
|
189
|
+
purged.push(snapshot.ts);
|
|
190
|
+
}
|
|
191
|
+
else {
|
|
192
|
+
kept += 1;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
return { purged, kept };
|
|
196
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared record types for the continuity store (design §2.2/§2.4).
|
|
3
|
+
* Split from index.ts so the pure handoff renderer can import them without a
|
|
4
|
+
* module cycle.
|
|
5
|
+
*/
|
|
6
|
+
import type { FrozenProof } from "./evidence";
|
|
7
|
+
/** The proof ladder (design §2.4). `claimed` is always free to record;
|
|
8
|
+
* `proven` is earned only through `verifyEvidence`. */
|
|
9
|
+
export type LadderStatus = "open" | "in_progress" | "claimed" | "proven";
|
|
10
|
+
/** Ladder statuses a caller may set WITHOUT evidence — everything below
|
|
11
|
+
* `proven`. */
|
|
12
|
+
export type ClaimableStatus = "open" | "in_progress" | "claimed";
|
|
13
|
+
export type RequirementStatus = "open" | "confirmed" | "dropped";
|
|
14
|
+
/** One `REQ-nnn` requirements-ledger entry (design §2.2/§2.3): the user's
|
|
15
|
+
* words VERBATIM — there is deliberately no paraphrase field — plus the
|
|
16
|
+
* session/turn attribution rendered as `(user, sess_…, turn N)`. */
|
|
17
|
+
export type Requirement = {
|
|
18
|
+
readonly id: string;
|
|
19
|
+
readonly text: string;
|
|
20
|
+
readonly status: RequirementStatus;
|
|
21
|
+
readonly source: {
|
|
22
|
+
readonly sessionId: string;
|
|
23
|
+
readonly turn: number;
|
|
24
|
+
};
|
|
25
|
+
};
|
|
26
|
+
export type PlanStep = {
|
|
27
|
+
/** 1-based position in the plan. */
|
|
28
|
+
readonly index: number;
|
|
29
|
+
readonly text: string;
|
|
30
|
+
readonly status: LadderStatus;
|
|
31
|
+
/** Frozen proof excerpts accumulated by `proven` transitions. Retained
|
|
32
|
+
* even if the step is later reopened — history, not status. */
|
|
33
|
+
readonly proofs: readonly FrozenProof[];
|
|
34
|
+
};
|
|
35
|
+
export type PlanRecord = {
|
|
36
|
+
/** `plan-NNNN`. */
|
|
37
|
+
readonly id: string;
|
|
38
|
+
readonly slug: string;
|
|
39
|
+
readonly title: string;
|
|
40
|
+
readonly createdAt: string;
|
|
41
|
+
readonly updatedAt: string;
|
|
42
|
+
readonly steps: readonly PlanStep[];
|
|
43
|
+
};
|
|
44
|
+
/** One `goals.yaml` entry — the local mirror of Thredz goals (design §2.2):
|
|
45
|
+
* `{id, title, status, target?, current?, unit?}` plus bookkeeping. */
|
|
46
|
+
export type Goal = {
|
|
47
|
+
/** `goal-NNNN`. */
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly title: string;
|
|
50
|
+
readonly status: LadderStatus;
|
|
51
|
+
readonly target?: number;
|
|
52
|
+
readonly current?: number;
|
|
53
|
+
readonly unit?: string;
|
|
54
|
+
readonly createdAt: string;
|
|
55
|
+
readonly updatedAt: string;
|
|
56
|
+
readonly proofs?: readonly FrozenProof[];
|
|
57
|
+
};
|
|
58
|
+
/** Parsed managed `focus.md` state. */
|
|
59
|
+
export type FocusState = {
|
|
60
|
+
/** The mutable focus body (may be empty). */
|
|
61
|
+
readonly body: string;
|
|
62
|
+
/** The active plan pointer, or null. */
|
|
63
|
+
readonly activePlanId: string | null;
|
|
64
|
+
readonly requirements: readonly Requirement[];
|
|
65
|
+
/** True when the requirements ledger evicted oldest entries to stay under
|
|
66
|
+
* its byte cap (a `[ledger truncated]` marker is rendered in the file). */
|
|
67
|
+
readonly ledgerTruncated: boolean;
|
|
68
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@crewhaus/continuity-store",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "v0.3.0 Goal 1 — focus/plans/goals/handoff stores under .crewhaus/state/ with the claimed→proven proof ladder verified against session event logs, trash/restore clearing, advisory locking, and tenant path fencing.",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"test": "bun test src"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@crewhaus/errors": "0.3.0",
|
|
19
|
+
"@crewhaus/event-log": "0.3.0",
|
|
20
|
+
"@crewhaus/infra-utils": "0.3.0",
|
|
21
|
+
"@crewhaus/tenancy": "0.3.0",
|
|
22
|
+
"yaml": "^2.6.0"
|
|
23
|
+
},
|
|
24
|
+
"license": "Apache-2.0",
|
|
25
|
+
"author": {
|
|
26
|
+
"name": "Max Meier",
|
|
27
|
+
"email": "max@crewhaus.ai",
|
|
28
|
+
"url": "https://crewhaus.ai"
|
|
29
|
+
},
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/crewhaus/factory.git",
|
|
33
|
+
"directory": "packages/continuity-store"
|
|
34
|
+
},
|
|
35
|
+
"homepage": "https://github.com/crewhaus/factory/tree/main/packages/continuity-store#readme",
|
|
36
|
+
"bugs": {
|
|
37
|
+
"url": "https://github.com/crewhaus/factory/issues"
|
|
38
|
+
},
|
|
39
|
+
"publishConfig": {
|
|
40
|
+
"access": "public"
|
|
41
|
+
},
|
|
42
|
+
"files": ["dist", "README.md", "LICENSE", "NOTICE"]
|
|
43
|
+
}
|