@cotal-ai/workspace 0.39.1 → 0.41.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/dist/pid.d.ts CHANGED
@@ -92,4 +92,152 @@ export declare function readProcessCommand(pid: number): ProcessCommand;
92
92
  * direction is the safe one and is chosen deliberately.
93
93
  */
94
94
  export declare function commandIsCotalSupervisor(command: string): boolean;
95
+ /**
96
+ * A process identity record, as a pidfile's CONTENT. The pid alone is not a stable identity on any
97
+ * OS: PIDs are recycled (aggressively on Windows, eventually on POSIX), and a teardown that signals
98
+ * a recorded pid therefore signals "whatever holds that number now". The record pins the pid to the
99
+ * process START that claimed it, which is the strongest identity a fresh process can establish
100
+ * without holding a kernel capability (#969 design shape (a)): the start time of a pid is fixed for
101
+ * the life of that pid and changes the moment the number is reused.
102
+ *
103
+ * FORMAT: two whitespace-separated fields, `pid` and `token`. The token is opaque here (see
104
+ * {@link ProcessStartTokenReader} for what produces it); a record carrying only a pid is a LEGACY
105
+ * record (pre-identity build) and every consumer must treat it as such, loud.
106
+ */
107
+ export type ProcessIdentityRecord = {
108
+ pid: number;
109
+ token: string;
110
+ };
111
+ /** What a pidfile's content resolves to. `legacy` is a bare pid (a pre-identity build's record);
112
+ * `husk` is an empty pre-protocol file; `unattributable` is anything parsePid rejects — the same
113
+ * three-valued rule the rest of this module applies, so "cannot read the record" is never folded
114
+ * into "nothing recorded". */
115
+ export type ReadRecord = {
116
+ kind: "record";
117
+ record: ProcessIdentityRecord;
118
+ } | {
119
+ kind: "legacy";
120
+ pid: number;
121
+ } | {
122
+ kind: "husk";
123
+ } | {
124
+ kind: "unattributable";
125
+ raw: string;
126
+ };
127
+ export declare function parseRecord(raw: string): ReadRecord;
128
+ /** Serialize a record. Exactly the inverse of {@link parseRecord}; writing is centralized so a
129
+ * future format change is one edit, not a fifth copy of `String(pid)`. */
130
+ export declare function formatRecord(record: ProcessIdentityRecord): string;
131
+ /**
132
+ * The creation-identity token of a live pid: on Linux `/proc/<pid>/stat` field 22 (starttime —
133
+ * ticks since boot, fixed for the life of the pid); on macOS/BSD `ps -o lstart=`; on Windows
134
+ * `undefined`, because no cheap STABLE token exists there (see the Windows note in
135
+ * `advisory-lock.ts`'s `processStartToken` for why a `ps` on PATH is worse than none). A pid whose
136
+ * token is `undefined` can still be recorded — the record then carries no start pin, and teardown
137
+ * warns that it is proceeding without the identity check.
138
+ */
139
+ export type ProcessStartTokenReader = (pid: number) => string | undefined;
140
+ /** The start token for a pid THIS PROCESS is about to record. Prefer the reader passed by the
141
+ * caller (tests inject divergence), fall back to the same `/proc`/`ps` read the advisory lock
142
+ * uses, so the token written at launch and the token compared at teardown come from ONE
143
+ * implementation. `undefined` (Windows, or a ps-less host) records a pid-only LEGACY record —
144
+ * visible as such to every reader, never silently. */
145
+ export declare function identityRecord(pid: number, tokenAt?: ProcessStartTokenReader): ProcessIdentityRecord | {
146
+ pid: number;
147
+ };
148
+ /** The start-token reader shared with the advisory lock, so launch and teardown agree on the token
149
+ * for the same live pid. Imported here rather than reimplemented: two token readers that drift
150
+ * would make the SAME process look like a stranger at teardown. */
151
+ import { processStartToken as defaultStartToken } from "./advisory-lock.js";
152
+ export { defaultStartToken };
153
+ /** The verdict of {@link assertRecordIdentity}: does the live process behind `pid` still carry the
154
+ * start this record pinned? `unreadable` is deliberately NOT a variant: the reader signature
155
+ * returns `undefined` for "cannot look" and that folds into `unpinned` (refuse). This differs from
156
+ * a legacy record with no pin at all: legacy records warn and proceed for upgrade compatibility,
157
+ * while a pin that exists but cannot be checked is preserved rather than silently weakened. */
158
+ export type IdentityVerdict = {
159
+ kind: "match";
160
+ } | {
161
+ kind: "mismatch";
162
+ liveToken: string;
163
+ } | {
164
+ kind: "gone";
165
+ } | {
166
+ kind: "unpinned";
167
+ };
168
+ /**
169
+ * THE identity check a teardown must pass before signalling (#969). This is the "open, verify,
170
+ * terminate" step: verify BEFORE any signal. A record pins a start token; if the pid's CURRENT
171
+ * start differs, the pid was REUSED and the live process is a stranger — refuse, never signal.
172
+ *
173
+ * FAILS CLOSED ON THE CHECK ITSELF: `gone` (the pid is ESRCH-dead; the record is stale and
174
+ * clearable) and `unpinned` (no token could be established for a live pid - a platform that
175
+ * cannot pin, or the process died between the token read and the liveness probe) are both
176
+ * distinct from `match`, and it is the CALLER's rule which of them may clear a record. Only
177
+ * `gone` is ever a safe clear here, because only ESRCH-equivalent evidence proves the recorded
178
+ * process itself gone.
179
+ */
180
+ export declare function assertRecordIdentity(record: ProcessIdentityRecord, tokenAt?: ProcessStartTokenReader): IdentityVerdict;
181
+ /** One shared refusal message, so every teardown names the same rule in the same words (#969
182
+ * acceptance: manager, delivery, auth and broker teardown use the SAME identity rule). */
183
+ export declare function identityRefusal(label: string, path: string, record: ProcessIdentityRecord, live: string): Error;
184
+ /** Warning for the one deliberately reduced-guarantee path: a live pre-pin record. Upgrades must be
185
+ * able to stop the stack that was launched by the previous version. The next launch writes a pin,
186
+ * after which mismatch and torn-pin protection applies in full. */
187
+ export declare function identityLegacyWarning(label: string, pidfilePath: string): string;
188
+ /**
189
+ * The pidfile format stays a BARE PID. Every reader of `.cotal/*.pid` across the tree - status,
190
+ * clean, meshes, the web dashboard, smoke suites, and any operator script - keeps working, and a
191
+ * bare-pid pidfile remains a LEGACY record this change must handle with a loud warning, not break. The creation
192
+ * identity therefore lives in a SIBLING file `<pidfile>.identity` (the `manager.delivery-aware`
193
+ * marker pattern), holding the {@link formatRecord} two-field pin. Missing sibling = legacy record;
194
+ * present-but-garbled = torn write, refuse; pid mismatch inside the sibling = torn pairing, refuse.
195
+ */
196
+ export declare function identityPinPath(pidfilePath: string): string;
197
+ /** Write the sibling pin for a pid this launch just spawned. Called AFTER the pidfile write, with
198
+ * the same pid, so a reader that sees a pidfile with no pin yet is reading either a legacy record
199
+ * or a launch mid-pairing - both are visible below, never silent. `undefined` token (Windows,
200
+ * ps-less host) writes NO pin, which is the honest legacy shape for that platform rather than a
201
+ * pin that cannot be checked. */
202
+ export declare function writeIdentityPin(pidfilePath: string, pid: number, tokenAt?: ProcessStartTokenReader): void;
203
+ /** What {@link verifyIdentityPin} found for one pidfile. */
204
+ export type PinVerdict = {
205
+ kind: "legacy";
206
+ } | {
207
+ kind: "match";
208
+ } | {
209
+ kind: "gone";
210
+ } | {
211
+ kind: "mismatch";
212
+ record: ProcessIdentityRecord;
213
+ liveToken: string;
214
+ } | {
215
+ kind: "torn-pin";
216
+ raw: string;
217
+ } | {
218
+ kind: "torn-pairing";
219
+ pinPid: number;
220
+ } | {
221
+ kind: "unpinned";
222
+ };
223
+ /**
224
+ * THE open-verify step every teardown runs BEFORE signalling (#969). Read the pidfile's bare pid,
225
+ * then the sibling pin, and adjudicate the pairing:
226
+ * - legacy -> the caller warns and signals for upgrade compatibility
227
+ * - match -> safe to signal; the process behind the pid is the recorded one
228
+ * - gone -> ESRCH-proven dead; the caller may clear the record
229
+ * - mismatch -> PID REUSE: the pid now fronts a DIFFERENT start; NEVER signal
230
+ * - torn-pin/torn-pairing/unpinned -> cannot establish identity on a live pid; refuse, preserve
231
+ *
232
+ * The pidfile argument is the SIBLING'S base path (the pidfile itself), and the bare pid is read
233
+ * from it here so the pairing check cannot drift from however the caller read the pid.
234
+ */
235
+ export declare function verifyIdentityPin(pidfilePath: string, tokenAt?: ProcessStartTokenReader): PinVerdict;
236
+ /** Remove the sibling pin together with a pidfile whose process is PROVEN gone. Never called on a
237
+ * refused stop: the preserved record keeps its pin so the next attempt re-adjudicates. */
238
+ export declare function removeIdentityPin(pidfilePath: string): void;
239
+ /** The loud torn/unpinned refusal, in the same shape as {@link identityRefusal}: name the component,
240
+ * the file, what was found, and the operator's next step. Legacy records do not reach this helper:
241
+ * callers emit {@link identityLegacyWarning} and proceed. */
242
+ export declare function identityUncertaintyRefusal(label: string, pidfilePath: string, verdict: PinVerdict): Error;
95
243
  //# sourceMappingURL=pid.d.ts.map
package/dist/pid.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pid.d.ts","sourceRoot":"","sources":["../src/pid.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AAKH;;;wCAGwC;AACxC,wBAAgB,QAAQ,CAAC,GAAG,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGxD;AAED;;;;;;;;sDAQsD;AACtD,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAIxF;AAED;;;;;;;2BAO2B;AAC3B;;;mGAGmG;AACnG,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,GAAG,MAAM,GAAG,SAAS,CAAC;AAE1E,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,GAAG,SAAS,CAOvE;AAWD;;gBAEgB;AAChB,MAAM,MAAM,cAAc;AACxB,gDAAgD;AAC9C;IAAE,IAAI,EAAE,SAAS,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE;AACtC,iGAAiG;GAC/F;IAAE,IAAI,EAAE,MAAM,CAAA;CAAE;AAClB,oFAAoF;GAClF;IAAE,IAAI,EAAE,YAAY,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC;AAExC;;6CAE6C;AAC7C,MAAM,MAAM,aAAa,GAAG,CAAC,GAAG,EAAE,MAAM,KAAK,cAAc,CAAC;AAE5D;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,CAkB9D;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,wBAAwB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEjE"}
1
+ {"version":3,"file":"pid.d.ts","sourceRoot":"","sources":["../src/pid.ts"],"names":[],"mappings":"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"}
package/dist/pid.js CHANGED
@@ -18,7 +18,7 @@
18
18
  * module they may depend on, never a core export.
19
19
  */
20
20
  import { spawnSync } from "node:child_process";
21
- import { readFileSync } from "node:fs";
21
+ import { readFileSync, rmSync, writeFileSync } from "node:fs";
22
22
  /** A Node/POSIX-signalable pid: a positive INTEGER within the signed 32-bit range `process.kill`
23
23
  * accepts (it throws `ERR_INVALID_ARG_TYPE`/`ERR_OUT_OF_RANGE` outside it). Anything else -
24
24
  * fractional, non-numeric, non-positive, oversized - is undefined: UNATTRIBUTABLE, never a pid to
@@ -102,4 +102,171 @@ export function readProcessCommand(pid) {
102
102
  export function commandIsCotalSupervisor(command) {
103
103
  return /(^|\s)supervise(\s|$)/.test(command);
104
104
  }
105
+ export function parseRecord(raw) {
106
+ const trimmed = raw.trim();
107
+ if (trimmed === "")
108
+ return { kind: "husk" };
109
+ // FIELD SPLIT, not a substring: the token may itself contain digits (it is a timestamp), so
110
+ // anything other than exactly two fields cannot name both a pid and its start.
111
+ const fields = trimmed.split(/\s+/);
112
+ if (fields.length === 1) {
113
+ const pid = parsePid(fields[0]);
114
+ return pid === undefined ? { kind: "unattributable", raw: trimmed } : { kind: "legacy", pid };
115
+ }
116
+ if (fields.length === 2 && parsePid(fields[0]) !== undefined && fields[1] !== "")
117
+ return { kind: "record", record: { pid: parsePid(fields[0]), token: fields[1] } };
118
+ return { kind: "unattributable", raw: trimmed };
119
+ }
120
+ /** Serialize a record. Exactly the inverse of {@link parseRecord}; writing is centralized so a
121
+ * future format change is one edit, not a fifth copy of `String(pid)`. */
122
+ export function formatRecord(record) {
123
+ return `${record.pid} ${record.token}`;
124
+ }
125
+ /** The start token for a pid THIS PROCESS is about to record. Prefer the reader passed by the
126
+ * caller (tests inject divergence), fall back to the same `/proc`/`ps` read the advisory lock
127
+ * uses, so the token written at launch and the token compared at teardown come from ONE
128
+ * implementation. `undefined` (Windows, or a ps-less host) records a pid-only LEGACY record —
129
+ * visible as such to every reader, never silently. */
130
+ export function identityRecord(pid, tokenAt = defaultStartToken) {
131
+ const token = tokenAt(pid);
132
+ return token === undefined ? { pid } : { pid, token };
133
+ }
134
+ /** The start-token reader shared with the advisory lock, so launch and teardown agree on the token
135
+ * for the same live pid. Imported here rather than reimplemented: two token readers that drift
136
+ * would make the SAME process look like a stranger at teardown. */
137
+ import { processStartToken as defaultStartToken } from "./advisory-lock.js";
138
+ export { defaultStartToken };
139
+ /**
140
+ * THE identity check a teardown must pass before signalling (#969). This is the "open, verify,
141
+ * terminate" step: verify BEFORE any signal. A record pins a start token; if the pid's CURRENT
142
+ * start differs, the pid was REUSED and the live process is a stranger — refuse, never signal.
143
+ *
144
+ * FAILS CLOSED ON THE CHECK ITSELF: `gone` (the pid is ESRCH-dead; the record is stale and
145
+ * clearable) and `unpinned` (no token could be established for a live pid - a platform that
146
+ * cannot pin, or the process died between the token read and the liveness probe) are both
147
+ * distinct from `match`, and it is the CALLER's rule which of them may clear a record. Only
148
+ * `gone` is ever a safe clear here, because only ESRCH-equivalent evidence proves the recorded
149
+ * process itself gone.
150
+ */
151
+ export function assertRecordIdentity(record, tokenAt = defaultStartToken) {
152
+ const live = tokenAt(record.pid);
153
+ if (live === undefined) {
154
+ // Distinguish "cannot look" from "looked and it is gone": the token readers return undefined
155
+ // BOTH when the platform cannot answer and when the process does not exist, and those must not
156
+ // share a verdict — a platform that cannot pin must refuse, a dead pid may be cleared.
157
+ const liveness = probeLiveness(record.pid);
158
+ if (liveness === "dead")
159
+ return { kind: "gone" };
160
+ return { kind: "unpinned" };
161
+ }
162
+ return live === record.token ? { kind: "match" } : { kind: "mismatch", liveToken: live };
163
+ }
164
+ /** One shared refusal message, so every teardown names the same rule in the same words (#969
165
+ * acceptance: manager, delivery, auth and broker teardown use the SAME identity rule). */
166
+ export function identityRefusal(label, path, record, live) {
167
+ return new Error(`refusing to stop ${label} (pid ${record.pid}) at ${path}: the pid has been reused (recorded start ${record.token}, the live process started ${live}). Signalling it would kill an unrelated process. The record is preserved.\n` +
168
+ `NEXT: inspect pid ${record.pid} with \`ps -p ${record.pid}\`. If that process should be stopped, stop it, then rerun this command; once the pid is dead the stale record clears automatically.`);
169
+ }
170
+ /** Warning for the one deliberately reduced-guarantee path: a live pre-pin record. Upgrades must be
171
+ * able to stop the stack that was launched by the previous version. The next launch writes a pin,
172
+ * after which mismatch and torn-pin protection applies in full. */
173
+ export function identityLegacyWarning(label, pidfilePath) {
174
+ return `! ${label} at ${pidfilePath} predates process identity pinning; signalling it without an identity check. A relaunch will pin the process identity.`;
175
+ }
176
+ // ---- THE SIBLING IDENTITY PIN: launch writes it, teardown verifies it (#969) ---------------------
177
+ /**
178
+ * The pidfile format stays a BARE PID. Every reader of `.cotal/*.pid` across the tree - status,
179
+ * clean, meshes, the web dashboard, smoke suites, and any operator script - keeps working, and a
180
+ * bare-pid pidfile remains a LEGACY record this change must handle with a loud warning, not break. The creation
181
+ * identity therefore lives in a SIBLING file `<pidfile>.identity` (the `manager.delivery-aware`
182
+ * marker pattern), holding the {@link formatRecord} two-field pin. Missing sibling = legacy record;
183
+ * present-but-garbled = torn write, refuse; pid mismatch inside the sibling = torn pairing, refuse.
184
+ */
185
+ export function identityPinPath(pidfilePath) {
186
+ return `${pidfilePath}.identity`;
187
+ }
188
+ /** Write the sibling pin for a pid this launch just spawned. Called AFTER the pidfile write, with
189
+ * the same pid, so a reader that sees a pidfile with no pin yet is reading either a legacy record
190
+ * or a launch mid-pairing - both are visible below, never silent. `undefined` token (Windows,
191
+ * ps-less host) writes NO pin, which is the honest legacy shape for that platform rather than a
192
+ * pin that cannot be checked. */
193
+ export function writeIdentityPin(pidfilePath, pid, tokenAt = defaultStartToken) {
194
+ const rec = identityRecord(pid, tokenAt);
195
+ if (!("token" in rec))
196
+ return; // platform cannot pin: leave the legacy bare-pid shape, loud
197
+ writeFileSync(identityPinPath(pidfilePath), formatRecord(rec));
198
+ }
199
+ /**
200
+ * THE open-verify step every teardown runs BEFORE signalling (#969). Read the pidfile's bare pid,
201
+ * then the sibling pin, and adjudicate the pairing:
202
+ * - legacy -> the caller warns and signals for upgrade compatibility
203
+ * - match -> safe to signal; the process behind the pid is the recorded one
204
+ * - gone -> ESRCH-proven dead; the caller may clear the record
205
+ * - mismatch -> PID REUSE: the pid now fronts a DIFFERENT start; NEVER signal
206
+ * - torn-pin/torn-pairing/unpinned -> cannot establish identity on a live pid; refuse, preserve
207
+ *
208
+ * The pidfile argument is the SIBLING'S base path (the pidfile itself), and the bare pid is read
209
+ * from it here so the pairing check cannot drift from however the caller read the pid.
210
+ */
211
+ export function verifyIdentityPin(pidfilePath, tokenAt = defaultStartToken) {
212
+ let rawPid;
213
+ try {
214
+ rawPid = readFileSync(pidfilePath, "utf8");
215
+ }
216
+ catch {
217
+ return { kind: "legacy" }; // no pidfile at all: nothing is pinned; callers treat as absent
218
+ }
219
+ const pidRead = parsePid(rawPid);
220
+ if (pidRead === undefined)
221
+ return { kind: "legacy" }; // husk/unattributable: existing callers own it
222
+ let rawPin;
223
+ try {
224
+ rawPin = readFileSync(identityPinPath(pidfilePath), "utf8");
225
+ }
226
+ catch {
227
+ // No pin: a LEGACY record. A live one warns and is signalable for upgrade compatibility; a pid
228
+ // that is ESRCH-dead needs no identity proof, so it reports `gone` and the caller clears the
229
+ // stale record. kill(pid, 0) only: this probe signals nothing.
230
+ return probeLiveness(pidRead) === "dead" ? { kind: "gone" } : { kind: "legacy" };
231
+ }
232
+ // The TORN shapes below refuse when the pid is live or unknown - identity cannot be proven.
233
+ // When the pid is ESRCH-dead there is nothing to signal, so the record is clearable whatever
234
+ // state the pin is in: a torn pin must not wedge the cleanup of a process that no longer exists.
235
+ const dead = probeLiveness(pidRead) === "dead";
236
+ const parsed = parseRecord(rawPin);
237
+ if (parsed.kind !== "record")
238
+ return dead ? { kind: "gone" } : { kind: "torn-pin", raw: parsed.kind === "unattributable" ? parsed.raw : "" };
239
+ if (parsed.record.pid !== pidRead)
240
+ return dead ? { kind: "gone" } : { kind: "torn-pairing", pinPid: parsed.record.pid };
241
+ const verdict = assertRecordIdentity(parsed.record, tokenAt);
242
+ if (verdict.kind === "match")
243
+ return { kind: "match" };
244
+ if (verdict.kind === "gone")
245
+ return { kind: "gone" };
246
+ if (verdict.kind === "mismatch")
247
+ return { kind: "mismatch", record: parsed.record, liveToken: verdict.liveToken };
248
+ return { kind: "unpinned" };
249
+ }
250
+ /** Remove the sibling pin together with a pidfile whose process is PROVEN gone. Never called on a
251
+ * refused stop: the preserved record keeps its pin so the next attempt re-adjudicates. */
252
+ export function removeIdentityPin(pidfilePath) {
253
+ rmSync(identityPinPath(pidfilePath), { force: true });
254
+ }
255
+ /** The loud torn/unpinned refusal, in the same shape as {@link identityRefusal}: name the component,
256
+ * the file, what was found, and the operator's next step. Legacy records do not reach this helper:
257
+ * callers emit {@link identityLegacyWarning} and proceed. */
258
+ export function identityUncertaintyRefusal(label, pidfilePath, verdict) {
259
+ const pin = identityPinPath(pidfilePath);
260
+ if (verdict.kind === "torn-pin")
261
+ return new Error(`refusing to stop ${label} at ${pidfilePath}: its identity pin ${pin} holds unattributable content ${JSON.stringify(verdict.raw)} - a torn or tampered write. The record is preserved.\n` +
262
+ `NEXT: inspect the recorded process with \`ps\`. If it should be stopped, stop it, then rerun this command; once the pid is dead the stale record clears automatically.`);
263
+ if (verdict.kind === "torn-pairing")
264
+ return new Error(`refusing to stop ${label} at ${pidfilePath}: its identity pin ${pin} names pid ${verdict.pinPid}, not the pidfile's pid. The record is preserved.\n` +
265
+ `NEXT: inspect the recorded process with \`ps\`. If it should be stopped, stop it, then rerun this command; once the pid is dead the stale record clears automatically.`);
266
+ if (verdict.kind === "unpinned")
267
+ return new Error(`refusing to stop ${label} at ${pidfilePath}: its process is live but no creation identity could be established for it, so target identity cannot be proven. The record is preserved.\n` +
268
+ `NEXT: stop the process, then rerun this command; once the pid is dead the stale record clears automatically.`);
269
+ return new Error(`refusing to stop ${label} at ${pidfilePath}: the identity state is unsupported (${verdict.kind}). The record is preserved.\n` +
270
+ `NEXT: stop the process, then rerun this command; once the pid is dead the stale record clears automatically.`);
271
+ }
105
272
  //# sourceMappingURL=pid.js.map
package/dist/pid.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"pid.js","sourceRoot":"","sources":["../src/pid.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"pid.js","sourceRoot":"","sources":["../src/pid.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@cotal-ai/workspace",
3
3
  "description": "Cotal machine-local workstation layer: the ~/.cotal mesh registry, target resolution, preflight, on-disk auth-path I/O, and the command-copy renderer — operator concerns over a local checkout, kept separate from the wire protocol in @cotal-ai/core.",
4
- "version": "0.39.1",
4
+ "version": "0.41.0",
5
5
  "license": "Apache-2.0",
6
6
  "repository": {
7
7
  "type": "git",
@@ -18,7 +18,7 @@
18
18
  }
19
19
  },
20
20
  "dependencies": {
21
- "@cotal-ai/core": "0.39.1"
21
+ "@cotal-ai/core": "0.41.0"
22
22
  },
23
23
  "files": [
24
24
  "dist"