@cotal-ai/cli 0.11.3 → 0.11.4

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 (72) hide show
  1. package/dist/command.d.ts.map +1 -1
  2. package/dist/command.js +58 -0
  3. package/dist/command.js.map +1 -1
  4. package/dist/commands/ext.d.ts +6 -0
  5. package/dist/commands/ext.d.ts.map +1 -1
  6. package/dist/commands/ext.js +50 -47
  7. package/dist/commands/ext.js.map +1 -1
  8. package/dist/commands/models.d.ts.map +1 -1
  9. package/dist/commands/models.js +3 -2
  10. package/dist/commands/models.js.map +1 -1
  11. package/dist/commands/setup.d.ts.map +1 -1
  12. package/dist/commands/setup.js +24 -7
  13. package/dist/commands/setup.js.map +1 -1
  14. package/dist/commands/spawn-manifest.js +1 -1
  15. package/dist/commands/spawn-manifest.js.map +1 -1
  16. package/dist/commands/spawn.d.ts +7 -1
  17. package/dist/commands/spawn.d.ts.map +1 -1
  18. package/dist/commands/spawn.js +11 -0
  19. package/dist/commands/spawn.js.map +1 -1
  20. package/dist/commands/up.d.ts +2 -0
  21. package/dist/commands/up.d.ts.map +1 -1
  22. package/dist/commands/up.js +14 -5
  23. package/dist/commands/up.js.map +1 -1
  24. package/dist/ext-loader.d.ts +3 -1
  25. package/dist/ext-loader.d.ts.map +1 -1
  26. package/dist/ext-loader.js +13 -62
  27. package/dist/ext-loader.js.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +9 -3
  30. package/dist/index.js.map +1 -1
  31. package/dist/lib/auth-proc.d.ts.map +1 -1
  32. package/dist/lib/auth-proc.js +3 -0
  33. package/dist/lib/auth-proc.js.map +1 -1
  34. package/dist/lib/delivery-proc.d.ts.map +1 -1
  35. package/dist/lib/delivery-proc.js +3 -1
  36. package/dist/lib/delivery-proc.js.map +1 -1
  37. package/dist/lib/ext-mutation.d.ts +17 -0
  38. package/dist/lib/ext-mutation.d.ts.map +1 -0
  39. package/dist/lib/ext-mutation.js +23 -0
  40. package/dist/lib/ext-mutation.js.map +1 -0
  41. package/dist/lib/manager-proc.d.ts.map +1 -1
  42. package/dist/lib/manager-proc.js +3 -1
  43. package/dist/lib/manager-proc.js.map +1 -1
  44. package/dist/lib/manifest/apply.d.ts +4 -3
  45. package/dist/lib/manifest/apply.d.ts.map +1 -1
  46. package/dist/lib/manifest/apply.js +9 -7
  47. package/dist/lib/manifest/apply.js.map +1 -1
  48. package/dist/lib/up-report.d.ts +9 -0
  49. package/dist/lib/up-report.d.ts.map +1 -0
  50. package/dist/lib/up-report.js +12 -0
  51. package/dist/lib/up-report.js.map +1 -0
  52. package/dist/seed/authority.d.ts +54 -0
  53. package/dist/seed/authority.d.ts.map +1 -0
  54. package/dist/seed/authority.js +94 -0
  55. package/dist/seed/authority.js.map +1 -0
  56. package/dist/seed/lock.d.ts +109 -0
  57. package/dist/seed/lock.d.ts.map +1 -0
  58. package/dist/seed/lock.js +168 -0
  59. package/dist/seed/lock.js.map +1 -0
  60. package/dist/seed/paths.d.ts +66 -0
  61. package/dist/seed/paths.d.ts.map +1 -0
  62. package/dist/seed/paths.js +149 -0
  63. package/dist/seed/paths.js.map +1 -0
  64. package/dist/seed/reconcile.d.ts +16 -0
  65. package/dist/seed/reconcile.d.ts.map +1 -0
  66. package/dist/seed/reconcile.js +431 -0
  67. package/dist/seed/reconcile.js.map +1 -0
  68. package/dist/seed/store.d.ts +18 -0
  69. package/dist/seed/store.d.ts.map +1 -0
  70. package/dist/seed/store.js +58 -0
  71. package/dist/seed/store.js.map +1 -0
  72. package/package.json +3 -3
@@ -0,0 +1,109 @@
1
+ /**
2
+ * The reconcile-wide lock and the crash cursor.
3
+ *
4
+ * ONE lock guards a whole reconcile (not each `ext add` child) so an operator `ext add`/`remove`
5
+ * cannot interleave between seed children and strand a half-applied refresh decision. It rides the
6
+ * shared crash-safe advisory-lock primitive (atomic link publish so a racer never sees a canonical
7
+ * lock before its owner record; serialized reclaim so no inspect→remove targets a later generation;
8
+ * PID + process-start liveness so a recycled PID isn't mistaken for the holder; a bounded wait on a
9
+ * live holder rather than a false "interrupted"; nonce-guarded release).
10
+ *
11
+ * The cursor is the SIGKILL floor: before each connector mutation the reconcile journals
12
+ * `{nonce, package, phase}`, and clears it only after the stamp commits. A crash mid-reconcile leaves
13
+ * the cursor behind; the next boot distinguishes a LIVE reconcile (lock still actively held → wait)
14
+ * from a DEAD one (lock stale + cursor present → fail loud, `--repair`).
15
+ */
16
+ export interface ReconcileLock {
17
+ readonly nonce: string;
18
+ release(): void;
19
+ }
20
+ /** Acquire the reconcile lock, waiting (bounded) on a live reconcile and reclaiming a dead owner's.
21
+ * A reconcile installs four connectors via npm, so the wait bound is generous. */
22
+ export declare function acquireReconcileLock(): ReconcileLock;
23
+ /** True while a LIVE process holds the reconcile lock (distinguishes an in-flight reconcile from a
24
+ * crashed one when a cursor is present). */
25
+ export declare function reconcileLockActive(): boolean;
26
+ /**
27
+ * True iff THIS process is a genuine seed child: it carries `COTAL_EXT_SEEDING=<nonce>` +
28
+ * `COTAL_EXT_SEEDING_PARENT=<pid>`, and a LIVE reconcile actually holds the lock under exactly that
29
+ * PID and nonce. A user cannot forge this to skip the mutation lock for an arbitrary `ext add` — the
30
+ * bare-`=1` bypass is gone; the marker must match the live lock the parent published.
31
+ */
32
+ export declare function isAuthenticSeedChild(): boolean;
33
+ /** The mid-reconcile journal: which package is being mutated, in which phase, under which lock nonce. */
34
+ export interface ReconcileCursor {
35
+ readonly nonce: string;
36
+ readonly package: string;
37
+ readonly phase: "copy" | "add";
38
+ }
39
+ export declare function writeCursor(cursor: ReconcileCursor): void;
40
+ /** Read + SCHEMA-validate the cursor: a torn write, or a parsed-but-malformed one (missing/invalid
41
+ * nonce, a non-official package, an unknown phase — e.g. `{}`) is corrupt, never a silent "no target"
42
+ * that lets a repair clear the journal and report success while a connector is still torn. */
43
+ export declare function readCursor(): ReconcileCursor | undefined;
44
+ export declare function clearCursor(): void;
45
+ /**
46
+ * A seed child's liveness marker. The PARENT records `pending` BEFORE it spawns (so the window never
47
+ * opens ownerless); the CHILD upgrades it to `live` with its own PID + start token as its first act;
48
+ * the parent clears it after the child exits. A `pending` marker whose parent has died is the ambiguous
49
+ * window — the child may or may not have started — and is resolved fail-loud, never by guessing.
50
+ */
51
+ export type ChildMarker = {
52
+ readonly state: "pending";
53
+ readonly parentPid: number;
54
+ readonly nonce: string;
55
+ readonly ts: number;
56
+ } | {
57
+ readonly state: "live";
58
+ readonly childPid: number;
59
+ readonly childStart?: string;
60
+ readonly parentPid: number;
61
+ readonly nonce: string;
62
+ readonly ts: number;
63
+ };
64
+ /** Parent: record intent to spawn a seed child before `spawnSync`, so a crash never leaves a gap a
65
+ * concurrent repair could race through unaware. */
66
+ export declare function writePendingChildMarker(nonce: string): void;
67
+ /** Child: upgrade the parent's pending marker to a live one carrying this child's identity. */
68
+ export declare function markSeedChildLive(nonce: string, parentPid: number): void;
69
+ export declare function clearChildMarker(): void;
70
+ export type SeedChildStatus = {
71
+ readonly kind: "none";
72
+ } | {
73
+ readonly kind: "live";
74
+ readonly pid: number;
75
+ } | {
76
+ readonly kind: "ambiguous";
77
+ readonly path: string;
78
+ };
79
+ /** The state of any recorded seed child, for the orphan-vs-repair decision. A `live` child (its parent
80
+ * SIGKILL'd mid-install → orphan) must not be raced. A `pending` marker whose parent is gone, or a
81
+ * corrupt marker, is `ambiguous` (cannot tell "never spawned" from "child delayed") and is resolved
82
+ * fail-loud. A dead/recycled child is `none` (safe to proceed). */
83
+ export declare function seedChildStatus(): SeedChildStatus;
84
+ /**
85
+ * The durable maintenance-recovery obligation. A `--repair`/`--reset` writes it BEFORE it
86
+ * destructively quarantines a corrupt manifest/cursor, and clears it only at the final commit — so a
87
+ * SIGKILL after the quarantine but before the reinstalls is not forgotten: the next boot fails loud
88
+ * and the next repair re-derives the same obligation (rebuild the built-ins from on-disk truth, and/or
89
+ * reinstall+verify every seeded built-in) instead of stamping success over missing connectors.
90
+ */
91
+ export interface RecoveryObligation {
92
+ readonly rebuildFromDisk?: boolean;
93
+ readonly repairAllSeeded?: boolean;
94
+ }
95
+ export declare function writeRecovery(obligation: RecoveryObligation): void;
96
+ /** Read + SCHEMA-validate the recovery obligation. A parsed-but-malformed marker (`{}`, `null`, an
97
+ * array, non-boolean fields, or an all-false obligation the writer never emits) is CORRUPT, not an
98
+ * empty valid obligation — it must throw so `recoveryPending()` stays true and maintenance recovers
99
+ * conservatively, never silently clearing the journal over still-damaged state. */
100
+ export declare function readRecovery(): RecoveryObligation | undefined;
101
+ /** True iff a recovery obligation is outstanding (any read failure counts — never silently ignored). */
102
+ export declare function recoveryPending(): boolean;
103
+ export declare function clearRecovery(): void;
104
+ /** Quarantine a CORRUPT (unparseable) crash CURSOR aside, so a `--repair`/`--reset` read of it can't
105
+ * wedge. The cursor is a journal, not a liveness signal, so moving it aside is safe; a corrupt CHILD
106
+ * MARKER is deliberately NOT touched here — it is resolved by {@link seedChildStatus}'s fail-loud
107
+ * (a marker we can't read might mean an installer is running). Returns the quarantine path(s). */
108
+ export declare function sanitizeCorruptCrashState(): string[];
109
+ //# sourceMappingURL=lock.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.d.ts","sourceRoot":"","sources":["../../src/seed/lock.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;GAcG;AAEH,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,OAAO,IAAI,IAAI,CAAC;CACjB;AAED;mFACmF;AACnF,wBAAgB,oBAAoB,IAAI,aAAa,CAUpD;AAED;6CAC6C;AAC7C,wBAAgB,mBAAmB,IAAI,OAAO,CAE7C;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,IAAI,OAAO,CAM9C;AAED,yGAAyG;AACzG,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;CAChC;AAED,wBAAgB,WAAW,CAAC,MAAM,EAAE,eAAe,GAAG,IAAI,CAEzD;AAED;;+FAE+F;AAC/F,wBAAgB,UAAU,IAAI,eAAe,GAAG,SAAS,CAWxD;AAED,wBAAgB,WAAW,IAAI,IAAI,CAElC;AAED;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GACnB;IAAE,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,GACtG;IAAE,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CAAE,CAAC;AAEjK;oDACoD;AACpD,wBAAgB,uBAAuB,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,CAE3D;AAED,+FAA+F;AAC/F,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,CASxE;AAED,wBAAgB,gBAAgB,IAAI,IAAI,CAEvC;AAED,MAAM,MAAM,eAAe,GACvB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACzB;IAAE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IAAC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CAAE,GAC/C;IAAE,QAAQ,CAAC,IAAI,EAAE,WAAW,CAAC;IAAC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CAAE,CAAC;AAE1D;;;oEAGoE;AACpE,wBAAgB,eAAe,IAAI,eAAe,CAmBjD;AAgBD;;;;;;GAMG;AACH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;IACnC,QAAQ,CAAC,eAAe,CAAC,EAAE,OAAO,CAAC;CACpC;AAED,wBAAgB,aAAa,CAAC,UAAU,EAAE,kBAAkB,GAAG,IAAI,CAElE;AAED;;;oFAGoF;AACpF,wBAAgB,YAAY,IAAI,kBAAkB,GAAG,SAAS,CAe7D;AAED,wGAAwG;AACxG,wBAAgB,eAAe,IAAI,OAAO,CAMzC;AAED,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED;;;mGAGmG;AACnG,wBAAgB,yBAAyB,IAAI,MAAM,EAAE,CAapD"}
@@ -0,0 +1,168 @@
1
+ import { existsSync, renameSync, rmSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { acquireLock, inspectLock, processStartToken } from "@cotal-ai/workspace";
4
+ import { SEED_BUILTINS, reconcileChildPath, reconcileCursorPath, reconcileLockPath, reconcileRecoveryPath, readJsonFile, writeJsonAtomic } from "./paths.js";
5
+ /** Acquire the reconcile lock, waiting (bounded) on a live reconcile and reclaiming a dead owner's.
6
+ * A reconcile installs four connectors via npm, so the wait bound is generous. */
7
+ export function acquireReconcileLock() {
8
+ const held = acquireLock(reconcileLockPath(), {
9
+ label: "a connector reconcile",
10
+ waitMs: 300000,
11
+ onTimeout: (owner) => new Error(`another connector reconcile has held the lock for over 5 minutes (pid ${owner.pid}) - retry once it finishes, or \`cotal ext seed --repair\` if it is wedged`),
12
+ });
13
+ return { nonce: held.nonce, release: () => held.release() };
14
+ }
15
+ /** True while a LIVE process holds the reconcile lock (distinguishes an in-flight reconcile from a
16
+ * crashed one when a cursor is present). */
17
+ export function reconcileLockActive() {
18
+ return inspectLock(reconcileLockPath()).state === "active";
19
+ }
20
+ /**
21
+ * True iff THIS process is a genuine seed child: it carries `COTAL_EXT_SEEDING=<nonce>` +
22
+ * `COTAL_EXT_SEEDING_PARENT=<pid>`, and a LIVE reconcile actually holds the lock under exactly that
23
+ * PID and nonce. A user cannot forge this to skip the mutation lock for an arbitrary `ext add` — the
24
+ * bare-`=1` bypass is gone; the marker must match the live lock the parent published.
25
+ */
26
+ export function isAuthenticSeedChild() {
27
+ const nonce = process.env.COTAL_EXT_SEEDING;
28
+ const parent = Number(process.env.COTAL_EXT_SEEDING_PARENT);
29
+ if (!nonce || nonce === "1" || !Number.isInteger(parent) || parent <= 0)
30
+ return false;
31
+ const found = inspectLock(reconcileLockPath());
32
+ return found.state === "active" && found.owner.pid === parent && found.owner.nonce === nonce;
33
+ }
34
+ export function writeCursor(cursor) {
35
+ writeJsonAtomic(reconcileCursorPath(), cursor);
36
+ }
37
+ /** Read + SCHEMA-validate the cursor: a torn write, or a parsed-but-malformed one (missing/invalid
38
+ * nonce, a non-official package, an unknown phase — e.g. `{}`) is corrupt, never a silent "no target"
39
+ * that lets a repair clear the journal and report success while a connector is still torn. */
40
+ export function readCursor() {
41
+ const cursor = readJsonFile(reconcileCursorPath());
42
+ if (cursor === undefined)
43
+ return undefined;
44
+ if (typeof cursor.nonce !== "string" ||
45
+ !SEED_BUILTINS.includes(cursor.package) ||
46
+ (cursor.phase !== "copy" && cursor.phase !== "add")) {
47
+ throw new Error(`corrupt seed cursor ${reconcileCursorPath()}: unexpected shape - repair with \`cotal ext seed --repair\` (or --reset)`);
48
+ }
49
+ return cursor;
50
+ }
51
+ export function clearCursor() {
52
+ rmSync(reconcileCursorPath(), { force: true });
53
+ }
54
+ /** Parent: record intent to spawn a seed child before `spawnSync`, so a crash never leaves a gap a
55
+ * concurrent repair could race through unaware. */
56
+ export function writePendingChildMarker(nonce) {
57
+ writeJsonAtomic(reconcileChildPath(), { state: "pending", parentPid: process.pid, nonce, ts: Date.now() });
58
+ }
59
+ /** Child: upgrade the parent's pending marker to a live one carrying this child's identity. */
60
+ export function markSeedChildLive(nonce, parentPid) {
61
+ writeJsonAtomic(reconcileChildPath(), {
62
+ state: "live",
63
+ childPid: process.pid,
64
+ childStart: processStartToken(process.pid),
65
+ parentPid,
66
+ nonce,
67
+ ts: Date.now(),
68
+ });
69
+ }
70
+ export function clearChildMarker() {
71
+ rmSync(reconcileChildPath(), { force: true });
72
+ }
73
+ /** The state of any recorded seed child, for the orphan-vs-repair decision. A `live` child (its parent
74
+ * SIGKILL'd mid-install → orphan) must not be raced. A `pending` marker whose parent is gone, or a
75
+ * corrupt marker, is `ambiguous` (cannot tell "never spawned" from "child delayed") and is resolved
76
+ * fail-loud. A dead/recycled child is `none` (safe to proceed). */
77
+ export function seedChildStatus() {
78
+ const path = reconcileChildPath();
79
+ let marker;
80
+ try {
81
+ marker = readJsonFile(path);
82
+ }
83
+ catch {
84
+ return { kind: "ambiguous", path }; // corrupt marker — never silently ignore
85
+ }
86
+ if (!marker)
87
+ return { kind: "none" };
88
+ if (marker.state === "live") {
89
+ if (typeof marker.childPid !== "number")
90
+ return { kind: "ambiguous", path };
91
+ return pidAliveMatching(marker.childPid, marker.childStart) ? { kind: "live", pid: marker.childPid } : { kind: "none" };
92
+ }
93
+ if (marker.state === "pending" && typeof marker.parentPid === "number") {
94
+ // Parent still alive ⇒ a reconcile is starting (the caller waits on the lock); parent gone ⇒ the
95
+ // ambiguous SIGKILL/delayed-start window.
96
+ return pidAliveMatching(marker.parentPid, undefined) ? { kind: "live", pid: marker.parentPid } : { kind: "ambiguous", path };
97
+ }
98
+ return { kind: "ambiguous", path };
99
+ }
100
+ function pidAliveMatching(pid, start) {
101
+ try {
102
+ process.kill(pid, 0);
103
+ }
104
+ catch (e) {
105
+ if (e.code === "ESRCH")
106
+ return false;
107
+ // EPERM ⇒ alive under another user; fall through to the start-token check.
108
+ }
109
+ if (start !== undefined) {
110
+ const now = processStartToken(pid);
111
+ if (now !== undefined && now !== start)
112
+ return false; // recycled PID
113
+ }
114
+ return true;
115
+ }
116
+ export function writeRecovery(obligation) {
117
+ writeJsonAtomic(reconcileRecoveryPath(), obligation);
118
+ }
119
+ /** Read + SCHEMA-validate the recovery obligation. A parsed-but-malformed marker (`{}`, `null`, an
120
+ * array, non-boolean fields, or an all-false obligation the writer never emits) is CORRUPT, not an
121
+ * empty valid obligation — it must throw so `recoveryPending()` stays true and maintenance recovers
122
+ * conservatively, never silently clearing the journal over still-damaged state. */
123
+ export function readRecovery() {
124
+ const recovery = readJsonFile(reconcileRecoveryPath());
125
+ if (recovery === undefined)
126
+ return undefined;
127
+ const shapeOk = typeof recovery === "object" &&
128
+ recovery !== null &&
129
+ !Array.isArray(recovery) &&
130
+ (recovery.rebuildFromDisk === undefined || typeof recovery.rebuildFromDisk === "boolean") &&
131
+ (recovery.repairAllSeeded === undefined || typeof recovery.repairAllSeeded === "boolean");
132
+ if (!shapeOk || !(recovery.rebuildFromDisk === true || recovery.repairAllSeeded === true)) {
133
+ throw new Error(`corrupt recovery journal ${reconcileRecoveryPath()}: expected { rebuildFromDisk?/repairAllSeeded?: boolean, at least one true } - repair with \`cotal ext seed --repair\` (or --reset)`);
134
+ }
135
+ return recovery;
136
+ }
137
+ /** True iff a recovery obligation is outstanding (any read failure counts — never silently ignored). */
138
+ export function recoveryPending() {
139
+ try {
140
+ return readRecovery() !== undefined;
141
+ }
142
+ catch {
143
+ return true;
144
+ }
145
+ }
146
+ export function clearRecovery() {
147
+ rmSync(reconcileRecoveryPath(), { force: true });
148
+ }
149
+ /** Quarantine a CORRUPT (unparseable) crash CURSOR aside, so a `--repair`/`--reset` read of it can't
150
+ * wedge. The cursor is a journal, not a liveness signal, so moving it aside is safe; a corrupt CHILD
151
+ * MARKER is deliberately NOT touched here — it is resolved by {@link seedChildStatus}'s fail-loud
152
+ * (a marker we can't read might mean an installer is running). Returns the quarantine path(s). */
153
+ export function sanitizeCorruptCrashState() {
154
+ const aside = [];
155
+ const path = reconcileCursorPath();
156
+ if (existsSync(path)) {
157
+ try {
158
+ readCursor(); // schema-validating read: catches torn JSON AND parsed-but-malformed (e.g. `{}`)
159
+ }
160
+ catch {
161
+ const dest = `${path}.corrupt.${randomBytes(4).toString("hex")}`;
162
+ renameSync(path, dest);
163
+ aside.push(dest);
164
+ }
165
+ }
166
+ return aside;
167
+ }
168
+ //# sourceMappingURL=lock.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lock.js","sourceRoot":"","sources":["../../src/seed/lock.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * Filesystem layout + version identity for the connector seeding engine. Everything the reconcile
3
+ * writes lives under `<config>/cotal/seed/`, a sibling of the `extensions/` npm prefix — the prefix
4
+ * is npm-owned (a stray dir there would confuse `npm install`), the seed state is ours.
5
+ *
6
+ * The seed store is DURABLE by design: `npm install --install-links <dir>` normalizes a `file:` dep
7
+ * pointing at `<dir>`, so a volatile source (an `npx` `_npx` cache) would later fail to reify. We
8
+ * copy each shipped connector payload into `store/<generation>/<name>` and `ext add` from THAT stable
9
+ * path, keyed by the binary's version so a `cotal-ai` upgrade lands a fresh generation.
10
+ */
11
+ /** The four first-party connectors, in a stable seed order. Keys of {@link OFFICIAL_CONNECTORS}. */
12
+ export declare const SEED_BUILTINS: readonly string[];
13
+ /** `<config>/cotal/seed` — the seed engine's own state root (NOT inside the npm prefix). */
14
+ export declare function seedDir(): string;
15
+ /** The monotonic ever-seeded authority: which built-ins were ever seeded (never shrinks). */
16
+ export declare function authorityPath(): string;
17
+ /** The durable append-only backup of the authority — the ONLY source `ext seed --repair` can
18
+ * recover a lost {@link authorityPath} from (a witness alone can't tell removed from never-seeded). */
19
+ export declare function authorityBackupPath(): string;
20
+ /** The initialization witness: proof the seeder ever ran here (distinguishes a pristine prefix from
21
+ * one whose authority was lost). Written after the authority, before the stamp. */
22
+ export declare function witnessPath(): string;
23
+ /** The version stamp, written LAST: the generation the last reconcile completed for. A boot whose
24
+ * stamp equals the current generation short-circuits (after the health preamble). */
25
+ export declare function stampPath(): string;
26
+ /** The reconcile-wide lock DIRECTORY (shared advisory-lock primitive: atomic `mkdir` publish, PID +
27
+ * process-start liveness, bounded wait, dead-owner reclaim). */
28
+ export declare function reconcileLockPath(): string;
29
+ /** The durable reconcile cursor `{nonce, package, phase}`: a partial-reconcile journal a crashed
30
+ * (SIGKILL) run leaves behind, so the next boot fails loud instead of silently proceeding. */
31
+ export declare function reconcileCursorPath(): string;
32
+ /** The seed-child liveness marker `{pid, start, ts}`: a validated `ext add` seed child writes it
33
+ * before it mutates the prefix and clears it on exit, so a reclaim/repair after a parent SIGKILL can
34
+ * refuse to race an orphaned child that is still installing. */
35
+ export declare function reconcileChildPath(): string;
36
+ /** The durable maintenance-recovery obligation `{rebuildFromDisk?, repairAllSeeded?}`: a `--repair`/
37
+ * `--reset` writes it BEFORE it destructively quarantines a corrupt manifest/cursor, so a SIGKILL
38
+ * after the quarantine but before the reinstalls doesn't forget that the built-ins must be rebuilt
39
+ * from disk (or all reinstalled). Cleared only at the final commit. */
40
+ export declare function reconcileRecoveryPath(): string;
41
+ /** The durable seed store root: `<config>/cotal/seed/store`. */
42
+ export declare function seedStoreDir(): string;
43
+ /** A seeded connector's stable copy for one generation: `store/<generation>/<name>`. */
44
+ export declare function seedStorePath(generation: string, name: string): string;
45
+ /**
46
+ * The seed generation: this `cotal-ai` binary's version. Ties a reconcile-refresh to the shipped
47
+ * payload changing — including the bundled `connector-core`, so a `cotal-ai` upgrade re-seeds the
48
+ * connectors that carry version-coupled behavior (e.g. the v0.4 lifecycle parse). Read from the
49
+ * nearest `package.json` above the entry script (`cotal-ai` published, or `bin/package.json` in a
50
+ * dev `tsx bin/cotal.ts` run). Fails loud if unreadable — a generation-less seed can't gate refresh.
51
+ */
52
+ export declare function seedGeneration(): string;
53
+ /**
54
+ * The shipped payload directory for one built-in connector — the source the reconcile copies into the
55
+ * durable store. Dev resolves `extensions/<pkg-dir>` from this module's location (NEVER `cwd`);
56
+ * published resolves `<cotal-ai>/seeded-connectors/<name>` beside the entry (the `bin` prepublish
57
+ * copy). Fails loud if neither carries a `package.json` — a build missing its seeded connectors.
58
+ */
59
+ export declare function shippedSourceDir(name: string): string;
60
+ /** Atomically write JSON (temp-then-rename, exclusive temp — never follows a pre-planted symlink),
61
+ * the same durability `writeLaunchSpec`/manifest writes use. Creates the parent dir. */
62
+ export declare function writeJsonAtomic(path: string, value: unknown, mode?: number): void;
63
+ /** Read + parse a JSON state file. Returns undefined when absent; throws (loud) on corrupt JSON — a
64
+ * seed-state file is never silently treated as missing (that would resurrect removed connectors). */
65
+ export declare function readJsonFile<T>(path: string): T | undefined;
66
+ //# sourceMappingURL=paths.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.d.ts","sourceRoot":"","sources":["../../src/seed/paths.ts"],"names":[],"mappings":"AAMA;;;;;;;;;GASG;AAEH,oGAAoG;AACpG,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAAqC,CAAC;AAEjF,4FAA4F;AAC5F,wBAAgB,OAAO,IAAI,MAAM,CAEhC;AAED,6FAA6F;AAC7F,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;wGACwG;AACxG,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;oFACoF;AACpF,wBAAgB,WAAW,IAAI,MAAM,CAEpC;AAED;sFACsF;AACtF,wBAAgB,SAAS,IAAI,MAAM,CAElC;AAED;iEACiE;AACjE,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED;+FAC+F;AAC/F,wBAAgB,mBAAmB,IAAI,MAAM,CAE5C;AAED;;iEAEiE;AACjE,wBAAgB,kBAAkB,IAAI,MAAM,CAE3C;AAED;;;wEAGwE;AACxE,wBAAgB,qBAAqB,IAAI,MAAM,CAE9C;AAED,gEAAgE;AAChE,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,wFAAwF;AACxF,wBAAgB,aAAa,CAAC,UAAU,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAEtE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,IAAI,MAAM,CAcvC;AASD;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAUrD;AAED;yFACyF;AACzF,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,SAAQ,GAAG,IAAI,CAMhF;AAED;sGACsG;AACtG,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,GAAG,CAAC,GAAG,SAAS,CAa3D"}
@@ -0,0 +1,149 @@
1
+ import { existsSync, mkdirSync, readFileSync, renameSync, writeFileSync } from "node:fs";
2
+ import { randomBytes } from "node:crypto";
3
+ import { basename, dirname, join } from "node:path";
4
+ import { globalConfigDir } from "@cotal-ai/core";
5
+ import { OFFICIAL_CONNECTORS } from "@cotal-ai/workspace";
6
+ /**
7
+ * Filesystem layout + version identity for the connector seeding engine. Everything the reconcile
8
+ * writes lives under `<config>/cotal/seed/`, a sibling of the `extensions/` npm prefix — the prefix
9
+ * is npm-owned (a stray dir there would confuse `npm install`), the seed state is ours.
10
+ *
11
+ * The seed store is DURABLE by design: `npm install --install-links <dir>` normalizes a `file:` dep
12
+ * pointing at `<dir>`, so a volatile source (an `npx` `_npx` cache) would later fail to reify. We
13
+ * copy each shipped connector payload into `store/<generation>/<name>` and `ext add` from THAT stable
14
+ * path, keyed by the binary's version so a `cotal-ai` upgrade lands a fresh generation.
15
+ */
16
+ /** The four first-party connectors, in a stable seed order. Keys of {@link OFFICIAL_CONNECTORS}. */
17
+ export const SEED_BUILTINS = Object.keys(OFFICIAL_CONNECTORS);
18
+ /** `<config>/cotal/seed` — the seed engine's own state root (NOT inside the npm prefix). */
19
+ export function seedDir() {
20
+ return join(globalConfigDir(), "seed");
21
+ }
22
+ /** The monotonic ever-seeded authority: which built-ins were ever seeded (never shrinks). */
23
+ export function authorityPath() {
24
+ return join(seedDir(), "authority.json");
25
+ }
26
+ /** The durable append-only backup of the authority — the ONLY source `ext seed --repair` can
27
+ * recover a lost {@link authorityPath} from (a witness alone can't tell removed from never-seeded). */
28
+ export function authorityBackupPath() {
29
+ return join(seedDir(), "authority.bak.json");
30
+ }
31
+ /** The initialization witness: proof the seeder ever ran here (distinguishes a pristine prefix from
32
+ * one whose authority was lost). Written after the authority, before the stamp. */
33
+ export function witnessPath() {
34
+ return join(seedDir(), "witness.json");
35
+ }
36
+ /** The version stamp, written LAST: the generation the last reconcile completed for. A boot whose
37
+ * stamp equals the current generation short-circuits (after the health preamble). */
38
+ export function stampPath() {
39
+ return join(seedDir(), "stamp.json");
40
+ }
41
+ /** The reconcile-wide lock DIRECTORY (shared advisory-lock primitive: atomic `mkdir` publish, PID +
42
+ * process-start liveness, bounded wait, dead-owner reclaim). */
43
+ export function reconcileLockPath() {
44
+ return join(seedDir(), "reconcile.lock");
45
+ }
46
+ /** The durable reconcile cursor `{nonce, package, phase}`: a partial-reconcile journal a crashed
47
+ * (SIGKILL) run leaves behind, so the next boot fails loud instead of silently proceeding. */
48
+ export function reconcileCursorPath() {
49
+ return join(seedDir(), "reconcile.cursor.json");
50
+ }
51
+ /** The seed-child liveness marker `{pid, start, ts}`: a validated `ext add` seed child writes it
52
+ * before it mutates the prefix and clears it on exit, so a reclaim/repair after a parent SIGKILL can
53
+ * refuse to race an orphaned child that is still installing. */
54
+ export function reconcileChildPath() {
55
+ return join(seedDir(), "reconcile.child.json");
56
+ }
57
+ /** The durable maintenance-recovery obligation `{rebuildFromDisk?, repairAllSeeded?}`: a `--repair`/
58
+ * `--reset` writes it BEFORE it destructively quarantines a corrupt manifest/cursor, so a SIGKILL
59
+ * after the quarantine but before the reinstalls doesn't forget that the built-ins must be rebuilt
60
+ * from disk (or all reinstalled). Cleared only at the final commit. */
61
+ export function reconcileRecoveryPath() {
62
+ return join(seedDir(), "reconcile.recovery.json");
63
+ }
64
+ /** The durable seed store root: `<config>/cotal/seed/store`. */
65
+ export function seedStoreDir() {
66
+ return join(seedDir(), "store");
67
+ }
68
+ /** A seeded connector's stable copy for one generation: `store/<generation>/<name>`. */
69
+ export function seedStorePath(generation, name) {
70
+ return join(seedStoreDir(), generation, name);
71
+ }
72
+ /**
73
+ * The seed generation: this `cotal-ai` binary's version. Ties a reconcile-refresh to the shipped
74
+ * payload changing — including the bundled `connector-core`, so a `cotal-ai` upgrade re-seeds the
75
+ * connectors that carry version-coupled behavior (e.g. the v0.4 lifecycle parse). Read from the
76
+ * nearest `package.json` above the entry script (`cotal-ai` published, or `bin/package.json` in a
77
+ * dev `tsx bin/cotal.ts` run). Fails loud if unreadable — a generation-less seed can't gate refresh.
78
+ */
79
+ export function seedGeneration() {
80
+ const entry = process.argv[1];
81
+ if (!entry)
82
+ throw new Error("cannot determine the seed generation: no entry script (process.argv[1])");
83
+ let dir = dirname(entry);
84
+ for (;;) {
85
+ const pj = join(dir, "package.json");
86
+ if (existsSync(pj)) {
87
+ const version = JSON.parse(readFileSync(pj, "utf8")).version;
88
+ if (typeof version === "string" && version)
89
+ return version;
90
+ }
91
+ const parent = dirname(dir);
92
+ if (parent === dir)
93
+ throw new Error(`cannot determine the seed generation: no versioned package.json above ${entry}`);
94
+ dir = parent;
95
+ }
96
+ }
97
+ /** This module's repo root in a source checkout: `implementations/cli/{src,dist}/seed` → up 4. Only
98
+ * meaningful in dev; a published `@cotal-ai/cli` resolves to `node_modules`, where the `extensions/`
99
+ * probe below simply misses and the published `seeded-connectors/` path wins. */
100
+ function repoRootFromHere() {
101
+ return join(import.meta.dirname, "..", "..", "..", "..");
102
+ }
103
+ /**
104
+ * The shipped payload directory for one built-in connector — the source the reconcile copies into the
105
+ * durable store. Dev resolves `extensions/<pkg-dir>` from this module's location (NEVER `cwd`);
106
+ * published resolves `<cotal-ai>/seeded-connectors/<name>` beside the entry (the `bin` prepublish
107
+ * copy). Fails loud if neither carries a `package.json` — a build missing its seeded connectors.
108
+ */
109
+ export function shippedSourceDir(name) {
110
+ const pkg = OFFICIAL_CONNECTORS[name];
111
+ if (!pkg)
112
+ throw new Error(`"${name}" is not a first-party connector; only ${SEED_BUILTINS.join(", ")} are seeded`);
113
+ const devDir = join(repoRootFromHere(), "extensions", pkg.split("/")[1]);
114
+ if (existsSync(join(devDir, "package.json")))
115
+ return devDir;
116
+ const pubDir = join(dirname(process.argv[1] ?? ""), "..", "seeded-connectors", name);
117
+ if (existsSync(join(pubDir, "package.json")))
118
+ return pubDir;
119
+ throw new Error(`no shipped payload for connector "${name}" (looked in ${devDir} and ${pubDir}) - this cotal-ai build is missing its seeded connectors`);
120
+ }
121
+ /** Atomically write JSON (temp-then-rename, exclusive temp — never follows a pre-planted symlink),
122
+ * the same durability `writeLaunchSpec`/manifest writes use. Creates the parent dir. */
123
+ export function writeJsonAtomic(path, value, mode = 0o600) {
124
+ const dir = dirname(path);
125
+ mkdirSync(dir, { recursive: true });
126
+ const tmp = join(dir, `.${basename(path)}.${randomBytes(6).toString("hex")}.tmp`);
127
+ writeFileSync(tmp, `${JSON.stringify(value, null, 2)}\n`, { mode, flag: "wx" });
128
+ renameSync(tmp, path);
129
+ }
130
+ /** Read + parse a JSON state file. Returns undefined when absent; throws (loud) on corrupt JSON — a
131
+ * seed-state file is never silently treated as missing (that would resurrect removed connectors). */
132
+ export function readJsonFile(path) {
133
+ let raw;
134
+ try {
135
+ raw = readFileSync(path, "utf8");
136
+ }
137
+ catch (e) {
138
+ if (e.code === "ENOENT")
139
+ return undefined;
140
+ throw e;
141
+ }
142
+ try {
143
+ return JSON.parse(raw);
144
+ }
145
+ catch (e) {
146
+ throw new Error(`corrupt seed state ${path}: ${e.message} - repair with \`cotal ext seed --repair\` (or --reset)`);
147
+ }
148
+ }
149
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../src/seed/paths.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ export interface SeedFlags {
2
+ readonly repair?: boolean;
3
+ readonly reset?: boolean;
4
+ readonly force?: boolean;
5
+ }
6
+ export interface ReconcileResult {
7
+ readonly noop: boolean;
8
+ readonly seeded: string[];
9
+ readonly refreshed: string[];
10
+ readonly removedKept: string[];
11
+ }
12
+ /** Auto reconcile for the boot gate: silent no-op when the stamp is current and healthy. */
13
+ export declare function reconcileSeededConnectors(): Promise<void>;
14
+ /** `cotal ext seed [--repair|--reset|--force]`: the maintenance entry. Prints a summary. */
15
+ export declare function runSeed(flags: SeedFlags): Promise<void>;
16
+ //# sourceMappingURL=reconcile.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"reconcile.d.ts","sourceRoot":"","sources":["../../src/seed/reconcile.ts"],"names":[],"mappings":"AAoEA,MAAM,WAAW,SAAS;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,OAAO,CAAC;IAC1B,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;IACzB,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,SAAS,EAAE,MAAM,EAAE,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,MAAM,EAAE,CAAC;CAChC;AAED,4FAA4F;AAC5F,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAE/D;AAED,4FAA4F;AAC5F,wBAAsB,OAAO,CAAC,KAAK,EAAE,SAAS,GAAG,OAAO,CAAC,IAAI,CAAC,CAY7D"}