mancode 0.6.3 → 0.6.5
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.en.md +87 -10
- package/README.md +60 -10
- package/dist/{chunk-NG72XZ3R.js → chunk-E2K22WYH.js} +2323 -1008
- package/dist/chunk-E2K22WYH.js.map +1 -0
- package/dist/chunk-GI24QVXE.js +1099 -0
- package/dist/chunk-GI24QVXE.js.map +1 -0
- package/dist/chunk-IRZQYMHD.js +334 -0
- package/dist/chunk-IRZQYMHD.js.map +1 -0
- package/dist/chunk-THOE33LU.js +1124 -0
- package/dist/chunk-THOE33LU.js.map +1 -0
- package/dist/{chunk-RDPFQODS.js → chunk-WJ6WARGG.js} +495 -250
- package/dist/chunk-WJ6WARGG.js.map +1 -0
- package/dist/chunk-WRBNOPFA.js +1235 -0
- package/dist/chunk-WRBNOPFA.js.map +1 -0
- package/dist/cli.js +22917 -21503
- package/dist/cli.js.map +1 -1
- package/dist/gateway/worker.d.ts +2 -0
- package/dist/gateway/worker.js +181 -0
- package/dist/gateway/worker.js.map +1 -0
- package/dist/privacy-gateway-Q4GD2JXF.js +20 -0
- package/dist/store-GSLSLZ7D.js +11 -0
- package/dist/{v3-adapter-7KIOYYWS.js → v3-adapter-T3IKK3LU.js} +4 -2
- package/dist/v3-adapter-T3IKK3LU.js.map +1 -0
- package/docs/README.md +34 -0
- package/docs/privacy-guide.md +76 -0
- package/docs/privacy-implementation-plan.md +101 -0
- package/docs/privacy-rule-sources.md +22 -0
- package/docs/privacy-upstream-license.txt +661 -0
- package/package.json +8 -3
- package/dist/chunk-NG72XZ3R.js.map +0 -1
- package/dist/chunk-RDPFQODS.js.map +0 -1
- package/dist/store-C3G3HN3N.js +0 -9
- /package/dist/{store-C3G3HN3N.js.map → privacy-gateway-Q4GD2JXF.js.map} +0 -0
- /package/dist/{v3-adapter-7KIOYYWS.js.map → store-GSLSLZ7D.js.map} +0 -0
|
@@ -1,239 +1,40 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
}
|
|
28
|
-
const timestamp = encodeBase32(BigInt(now), 10);
|
|
29
|
-
const random = encodeBase32(
|
|
30
|
-
BigInt(`0x${Buffer.from(entropy).toString("hex")}`),
|
|
31
|
-
16
|
|
32
|
-
);
|
|
33
|
-
return `${timestamp}${random}`;
|
|
34
|
-
}
|
|
35
|
-
function encodeBase32(value, length) {
|
|
36
|
-
let remaining = value;
|
|
37
|
-
let encoded = "";
|
|
38
|
-
for (let index = 0; index < length; index += 1) {
|
|
39
|
-
encoded = `${CROCKFORD_BASE32[Number(remaining & 31n)]}${encoded}`;
|
|
40
|
-
remaining >>= 5n;
|
|
41
|
-
}
|
|
42
|
-
if (remaining !== 0n) {
|
|
43
|
-
throw new Error("value does not fit in requested base32 length");
|
|
44
|
-
}
|
|
45
|
-
return encoded;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// src/context/validation.ts
|
|
49
|
-
function isRecord(value) {
|
|
50
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
51
|
-
return false;
|
|
52
|
-
}
|
|
53
|
-
const prototype = Object.getPrototypeOf(value);
|
|
54
|
-
return prototype === Object.prototype || prototype === null;
|
|
55
|
-
}
|
|
56
|
-
function assertRecord(value, label) {
|
|
57
|
-
if (!isRecord(value)) {
|
|
58
|
-
throw new Error(`${label} must be an object`);
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
function assertKnownKeys(value, allowedKeys, label) {
|
|
62
|
-
const allowed = new Set(allowedKeys);
|
|
63
|
-
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
64
|
-
if (unknown.length > 0) {
|
|
65
|
-
throw new Error(
|
|
66
|
-
`${label} contains unknown field(s): ${unknown.join(", ")}`
|
|
67
|
-
);
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
// src/context/task-ref.ts
|
|
72
|
-
var TASK_REF_PATTERN = /^(local|shared):([0-7][0-9A-HJKMNPQRSTVWXYZ]{25})$/;
|
|
73
|
-
function formatTaskRef(taskRef) {
|
|
74
|
-
assertTaskRef(taskRef);
|
|
75
|
-
return `${taskRef.namespace}:${taskRef.taskId}`;
|
|
76
|
-
}
|
|
77
|
-
function parseTaskRef(input) {
|
|
78
|
-
if (typeof input !== "string") {
|
|
79
|
-
throw new Error("TaskRef must be a string in namespace:ULID form");
|
|
80
|
-
}
|
|
81
|
-
const match = TASK_REF_PATTERN.exec(input);
|
|
82
|
-
if (!match) {
|
|
83
|
-
throw new Error("TaskRef must use local:<ULID> or shared:<ULID>");
|
|
84
|
-
}
|
|
85
|
-
return {
|
|
86
|
-
namespace: match[1],
|
|
87
|
-
taskId: match[2]
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
function parseTaskRefValue(value) {
|
|
91
|
-
assertRecord(value, "TaskRef");
|
|
92
|
-
assertKnownKeys(value, ["namespace", "taskId"], "TaskRef");
|
|
93
|
-
if (value.namespace !== "local" && value.namespace !== "shared") {
|
|
94
|
-
throw new Error("TaskRef namespace must be local or shared");
|
|
95
|
-
}
|
|
96
|
-
assertUlid(value.taskId, "TaskRef taskId");
|
|
97
|
-
return {
|
|
98
|
-
namespace: value.namespace,
|
|
99
|
-
taskId: value.taskId
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function assertTaskRef(value) {
|
|
103
|
-
parseTaskRefValue(value);
|
|
104
|
-
}
|
|
105
|
-
function sameTaskRef(left, right) {
|
|
106
|
-
return left.namespace === right.namespace && left.taskId === right.taskId;
|
|
107
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
assertKnownKeys,
|
|
3
|
+
assertRecord,
|
|
4
|
+
assertUlid,
|
|
5
|
+
claimDirectory,
|
|
6
|
+
createUlid,
|
|
7
|
+
digestCanonicalJson,
|
|
8
|
+
handoffDirectory,
|
|
9
|
+
lockDirectory,
|
|
10
|
+
operationDirectory,
|
|
11
|
+
parseProjectConfig,
|
|
12
|
+
parseTaskRef,
|
|
13
|
+
parseTaskRefValue,
|
|
14
|
+
parseTeamPolicy,
|
|
15
|
+
readProjectRuntimeContext,
|
|
16
|
+
reservationDirectory,
|
|
17
|
+
resolveLocalEntityHomeStore,
|
|
18
|
+
sameTaskRef,
|
|
19
|
+
sortUtf8StringSet,
|
|
20
|
+
taskHeadDirectory
|
|
21
|
+
} from "./chunk-WRBNOPFA.js";
|
|
22
|
+
import {
|
|
23
|
+
DEFAULT_RULE_IDS,
|
|
24
|
+
RULESET_VERSION,
|
|
25
|
+
scanSensitiveText
|
|
26
|
+
} from "./chunk-IRZQYMHD.js";
|
|
108
27
|
|
|
109
|
-
// src/
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
const task = parseTaskRefValue(taskRef);
|
|
113
|
-
return task.namespace === "local" ? localTaskHomeStore(normalized) : coordinationEntityHomeStore(normalized);
|
|
114
|
-
}
|
|
115
|
-
function resolveLocalEntityHomeStore(context) {
|
|
116
|
-
return localTaskHomeStore(normalizeContext(context));
|
|
117
|
-
}
|
|
118
|
-
function resolveCoordinationEntityHomeStore(context) {
|
|
119
|
-
return coordinationEntityHomeStore(normalizeContext(context));
|
|
120
|
-
}
|
|
121
|
-
function operationDirectory(store) {
|
|
122
|
-
return path.join(store.root, "operations");
|
|
123
|
-
}
|
|
124
|
-
function reservationDirectory(store) {
|
|
125
|
-
return path.join(store.root, "reservations");
|
|
126
|
-
}
|
|
127
|
-
function lockDirectory(store) {
|
|
128
|
-
return path.join(store.root, "locks");
|
|
129
|
-
}
|
|
130
|
-
function claimDirectory(store) {
|
|
131
|
-
assertCoordinationStore(store, "claim");
|
|
132
|
-
return path.join(store.root, "claims");
|
|
133
|
-
}
|
|
134
|
-
function handoffDirectory(store) {
|
|
135
|
-
assertCoordinationStore(store, "handoff");
|
|
136
|
-
return path.join(store.root, "handoffs");
|
|
137
|
-
}
|
|
138
|
-
function taskHeadDirectory(store) {
|
|
139
|
-
assertCoordinationStore(store, "task head fence");
|
|
140
|
-
return path.join(store.root, "task-heads");
|
|
141
|
-
}
|
|
142
|
-
function normalizeContext(context) {
|
|
143
|
-
assertUlid(context.workspaceId, "entity home store workspaceId");
|
|
144
|
-
assertUlid(context.checkoutId, "entity home store checkoutId");
|
|
145
|
-
if (typeof context.projectRoot !== "string" || !context.projectRoot.trim()) {
|
|
146
|
-
throw new Error("entity home store projectRoot is required");
|
|
147
|
-
}
|
|
148
|
-
if (context.gitCommonDir === null) {
|
|
149
|
-
if (context.repositoryBindingId !== null) {
|
|
150
|
-
assertUlid(
|
|
151
|
-
context.repositoryBindingId,
|
|
152
|
-
"entity home store repositoryBindingId"
|
|
153
|
-
);
|
|
154
|
-
}
|
|
155
|
-
return {
|
|
156
|
-
...context,
|
|
157
|
-
projectRoot: path.resolve(context.projectRoot),
|
|
158
|
-
gitCommonDir: null
|
|
159
|
-
};
|
|
160
|
-
}
|
|
161
|
-
if (typeof context.gitCommonDir !== "string" || !context.gitCommonDir.trim()) {
|
|
162
|
-
throw new Error("entity home store gitCommonDir must be a path or null");
|
|
163
|
-
}
|
|
164
|
-
if (context.repositoryBindingId === null) {
|
|
165
|
-
throw new Error(
|
|
166
|
-
"git coordination requires an entity home store repositoryBindingId"
|
|
167
|
-
);
|
|
168
|
-
}
|
|
169
|
-
assertUlid(
|
|
170
|
-
context.repositoryBindingId,
|
|
171
|
-
"entity home store repositoryBindingId"
|
|
172
|
-
);
|
|
173
|
-
return {
|
|
174
|
-
...context,
|
|
175
|
-
projectRoot: path.resolve(context.projectRoot),
|
|
176
|
-
gitCommonDir: path.resolve(context.gitCommonDir)
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
function localTaskHomeStore(context) {
|
|
180
|
-
return {
|
|
181
|
-
kind: "checkout_local",
|
|
182
|
-
storeId: `checkout:${context.checkoutId}:${context.workspaceId}`,
|
|
183
|
-
root: path.join(context.projectRoot, ".mancode", "local", "runtime"),
|
|
184
|
-
workspaceId: context.workspaceId,
|
|
185
|
-
checkoutId: context.checkoutId,
|
|
186
|
-
repositoryBindingId: context.repositoryBindingId
|
|
187
|
-
};
|
|
188
|
-
}
|
|
189
|
-
function coordinationEntityHomeStore(context) {
|
|
190
|
-
if (context.gitCommonDir === null) {
|
|
191
|
-
return {
|
|
192
|
-
kind: "non_git_shared",
|
|
193
|
-
storeId: `non-git:${context.workspaceId}`,
|
|
194
|
-
root: path.join(
|
|
195
|
-
context.projectRoot,
|
|
196
|
-
".mancode",
|
|
197
|
-
"runtime",
|
|
198
|
-
"non-git",
|
|
199
|
-
context.workspaceId
|
|
200
|
-
),
|
|
201
|
-
workspaceId: context.workspaceId,
|
|
202
|
-
checkoutId: null,
|
|
203
|
-
repositoryBindingId: context.repositoryBindingId
|
|
204
|
-
};
|
|
205
|
-
}
|
|
206
|
-
if (context.repositoryBindingId === null) {
|
|
207
|
-
throw new Error("git coordination requires a repositoryBindingId");
|
|
208
|
-
}
|
|
209
|
-
return {
|
|
210
|
-
kind: "workspace_common_dir",
|
|
211
|
-
storeId: `workspace:${context.repositoryBindingId}:${context.workspaceId}`,
|
|
212
|
-
root: path.join(
|
|
213
|
-
context.gitCommonDir,
|
|
214
|
-
"mancode",
|
|
215
|
-
"workspaces",
|
|
216
|
-
context.workspaceId
|
|
217
|
-
),
|
|
218
|
-
workspaceId: context.workspaceId,
|
|
219
|
-
checkoutId: null,
|
|
220
|
-
repositoryBindingId: context.repositoryBindingId
|
|
221
|
-
};
|
|
222
|
-
}
|
|
223
|
-
function assertCoordinationStore(store, entity) {
|
|
224
|
-
if (store.kind === "checkout_local") {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`${entity} requires a shared coordination entity home store`
|
|
227
|
-
);
|
|
228
|
-
}
|
|
229
|
-
}
|
|
28
|
+
// src/context/store.ts
|
|
29
|
+
import { lstat as lstat6, readFile as readFile10, readdir as readdir7 } from "fs/promises";
|
|
30
|
+
import path12 from "path";
|
|
230
31
|
|
|
231
32
|
// src/runtime/git-ref-workflow-repair-store.ts
|
|
232
33
|
import { lstat, readFile, readdir } from "fs/promises";
|
|
233
|
-
import
|
|
34
|
+
import path from "path";
|
|
234
35
|
function gitRefWorkflowRepairJournalDirectory(projectRoot) {
|
|
235
|
-
return
|
|
236
|
-
|
|
36
|
+
return path.join(
|
|
37
|
+
path.resolve(projectRoot),
|
|
237
38
|
".mancode",
|
|
238
39
|
"local",
|
|
239
40
|
"journals",
|
|
@@ -242,7 +43,7 @@ function gitRefWorkflowRepairJournalDirectory(projectRoot) {
|
|
|
242
43
|
}
|
|
243
44
|
function gitRefWorkflowRepairJournalPath(projectRoot, operationId) {
|
|
244
45
|
assertUlid(operationId, "git-ref workflow repair operationId");
|
|
245
|
-
return
|
|
46
|
+
return path.join(
|
|
246
47
|
gitRefWorkflowRepairJournalDirectory(projectRoot),
|
|
247
48
|
`${operationId}.json`
|
|
248
49
|
);
|
|
@@ -253,7 +54,7 @@ async function listGitRefWorkflowRepairJournalSummaries(projectRoot, options = {
|
|
|
253
54
|
const summaries = [];
|
|
254
55
|
for (const entry of entries) {
|
|
255
56
|
if (!entry.endsWith(".json")) continue;
|
|
256
|
-
const journalPath =
|
|
57
|
+
const journalPath = path.join(directory, entry);
|
|
257
58
|
let summary;
|
|
258
59
|
try {
|
|
259
60
|
summary = parseSummary(
|
|
@@ -372,109 +173,8 @@ function isNotFound(error) {
|
|
|
372
173
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
373
174
|
}
|
|
374
175
|
|
|
375
|
-
// src/context/canonical.ts
|
|
376
|
-
import { createHash } from "crypto";
|
|
377
|
-
function canonicalizeJson(value, options = {}) {
|
|
378
|
-
return canonicalizeValue(value, options.numberPolicy ?? "safe-integer");
|
|
379
|
-
}
|
|
380
|
-
function digestCanonicalJson(value, options = {}) {
|
|
381
|
-
const canonical = canonicalizeJson(value, options);
|
|
382
|
-
return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
|
|
383
|
-
}
|
|
384
|
-
function sortUtf8StringSet(values) {
|
|
385
|
-
const unique = /* @__PURE__ */ new Set();
|
|
386
|
-
for (const value of values) {
|
|
387
|
-
assertCanonicalString(value, "set item");
|
|
388
|
-
unique.add(value);
|
|
389
|
-
}
|
|
390
|
-
return [...unique].sort(
|
|
391
|
-
(left, right) => Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"))
|
|
392
|
-
);
|
|
393
|
-
}
|
|
394
|
-
function canonicalizeValue(value, numberPolicy) {
|
|
395
|
-
if (value === null) return "null";
|
|
396
|
-
if (typeof value === "string") {
|
|
397
|
-
assertCanonicalString(value, "string");
|
|
398
|
-
return JSON.stringify(value);
|
|
399
|
-
}
|
|
400
|
-
if (typeof value === "boolean") return value ? "true" : "false";
|
|
401
|
-
if (typeof value === "number") {
|
|
402
|
-
if (!Number.isFinite(value) || Object.is(value, -0)) {
|
|
403
|
-
throw new Error(
|
|
404
|
-
"canonical JSON numbers must be finite and must not be negative zero"
|
|
405
|
-
);
|
|
406
|
-
}
|
|
407
|
-
if (numberPolicy === "safe-integer" && !Number.isSafeInteger(value)) {
|
|
408
|
-
throw new Error(
|
|
409
|
-
"canonical JSON numbers must be safe integers for this schema"
|
|
410
|
-
);
|
|
411
|
-
}
|
|
412
|
-
return JSON.stringify(value);
|
|
413
|
-
}
|
|
414
|
-
if (Array.isArray(value)) {
|
|
415
|
-
assertCanonicalArray(value);
|
|
416
|
-
return `[${value.map((item) => canonicalizeValue(item, numberPolicy)).join(",")}]`;
|
|
417
|
-
}
|
|
418
|
-
if (isPlainObject(value)) {
|
|
419
|
-
assertCanonicalObject(value);
|
|
420
|
-
const keys = Object.keys(value).sort();
|
|
421
|
-
return `{${keys.map((key) => {
|
|
422
|
-
assertCanonicalString(key, "object key");
|
|
423
|
-
return `${JSON.stringify(key)}:${canonicalizeValue(value[key], numberPolicy)}`;
|
|
424
|
-
}).join(",")}}`;
|
|
425
|
-
}
|
|
426
|
-
throw new Error("canonical JSON only accepts plain JSON values");
|
|
427
|
-
}
|
|
428
|
-
function assertCanonicalArray(value) {
|
|
429
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
430
|
-
if (!Object.hasOwn(value, index)) {
|
|
431
|
-
throw new Error("canonical JSON arrays must not be sparse");
|
|
432
|
-
}
|
|
433
|
-
}
|
|
434
|
-
const ownKeys = Object.keys(value);
|
|
435
|
-
if (ownKeys.some((key) => !/^(0|[1-9]\d*)$/.test(key))) {
|
|
436
|
-
throw new Error("canonical JSON arrays must not have non-index properties");
|
|
437
|
-
}
|
|
438
|
-
if (Object.getOwnPropertyNames(value).some(
|
|
439
|
-
(key) => key !== "length" && !ownKeys.includes(key)
|
|
440
|
-
) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
441
|
-
throw new Error("canonical JSON arrays must not have hidden properties");
|
|
442
|
-
}
|
|
443
|
-
}
|
|
444
|
-
function assertCanonicalObject(value) {
|
|
445
|
-
const ownKeys = Object.keys(value);
|
|
446
|
-
if (Object.getOwnPropertyNames(value).some((key) => !ownKeys.includes(key)) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
447
|
-
throw new Error("canonical JSON objects must not have hidden properties");
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
function isPlainObject(value) {
|
|
451
|
-
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
452
|
-
return false;
|
|
453
|
-
}
|
|
454
|
-
const prototype = Object.getPrototypeOf(value);
|
|
455
|
-
return prototype === Object.prototype || prototype === null;
|
|
456
|
-
}
|
|
457
|
-
function assertCanonicalString(value, label) {
|
|
458
|
-
if (value.includes("\0")) {
|
|
459
|
-
throw new Error(`canonical JSON ${label} must not contain NUL`);
|
|
460
|
-
}
|
|
461
|
-
for (let index = 0; index < value.length; index += 1) {
|
|
462
|
-
const codeUnit = value.charCodeAt(index);
|
|
463
|
-
if (codeUnit < 55296 || codeUnit > 57343) continue;
|
|
464
|
-
const next = value.charCodeAt(index + 1);
|
|
465
|
-
const isHigh = codeUnit <= 56319;
|
|
466
|
-
const isLowNext = next >= 56320 && next <= 57343;
|
|
467
|
-
if (!isHigh || !isLowNext) {
|
|
468
|
-
throw new Error(
|
|
469
|
-
`canonical JSON ${label} must not contain a lone surrogate`
|
|
470
|
-
);
|
|
471
|
-
}
|
|
472
|
-
index += 1;
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
176
|
// src/context/privacy.ts
|
|
477
|
-
import
|
|
177
|
+
import path2 from "path";
|
|
478
178
|
var FINDING_PATTERNS = [
|
|
479
179
|
{
|
|
480
180
|
kind: "private_key",
|
|
@@ -523,7 +223,7 @@ function assertSharedTextSafe(value, label) {
|
|
|
523
223
|
}
|
|
524
224
|
}
|
|
525
225
|
function assertSafeSharedRelativePath(value) {
|
|
526
|
-
if (typeof value !== "string" || !value || value.includes("\0") ||
|
|
226
|
+
if (typeof value !== "string" || !value || value.includes("\0") || path2.isAbsolute(value) || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) {
|
|
527
227
|
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
528
228
|
}
|
|
529
229
|
const normalized = value.split(/[\\/]/);
|
|
@@ -1077,6 +777,7 @@ var OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
1077
777
|
"greenfield_initialize",
|
|
1078
778
|
"adapter_upgrade",
|
|
1079
779
|
"project_policy_upgrade",
|
|
780
|
+
"privacy_policy_update",
|
|
1080
781
|
"v3_activate"
|
|
1081
782
|
]);
|
|
1082
783
|
var OPERATION_STATES = /* @__PURE__ */ new Set([
|
|
@@ -1275,14 +976,14 @@ function parseSecondaryReservations(value) {
|
|
|
1275
976
|
function parseExpectedRevisions(value) {
|
|
1276
977
|
assertRecord(value, "operation expectedRevisions");
|
|
1277
978
|
const parsed = {};
|
|
1278
|
-
for (const [entityKey,
|
|
979
|
+
for (const [entityKey, revision2] of Object.entries(value)) {
|
|
1279
980
|
assertEntityKey(entityKey, "operation expected revision key");
|
|
1280
|
-
if (typeof
|
|
981
|
+
if (typeof revision2 !== "number" || !Number.isSafeInteger(revision2) || revision2 < 0) {
|
|
1281
982
|
throw new Error(
|
|
1282
983
|
"operation expected revisions must be non-negative integers"
|
|
1283
984
|
);
|
|
1284
985
|
}
|
|
1285
|
-
parsed[entityKey] =
|
|
986
|
+
parsed[entityKey] = revision2;
|
|
1286
987
|
}
|
|
1287
988
|
return parsed;
|
|
1288
989
|
}
|
|
@@ -1365,9 +1066,9 @@ function assertReframeCheckpointReplacement(previous, next, replacement) {
|
|
|
1365
1066
|
throw new Error("MANCODE_REFRAME_CHECKPOINT_REPLACEMENT_INVALID");
|
|
1366
1067
|
}
|
|
1367
1068
|
const expectedRevisions = Object.fromEntries(
|
|
1368
|
-
Object.entries(previous.expectedRevisions).map(([key,
|
|
1069
|
+
Object.entries(previous.expectedRevisions).map(([key, revision2]) => [
|
|
1369
1070
|
key === fromKey ? toKey : key,
|
|
1370
|
-
|
|
1071
|
+
revision2
|
|
1371
1072
|
])
|
|
1372
1073
|
);
|
|
1373
1074
|
if (JSON.stringify(next.expectedRevisions) !== JSON.stringify(expectedRevisions)) {
|
|
@@ -1426,7 +1127,7 @@ function allowedOperationTransitions(from) {
|
|
|
1426
1127
|
|
|
1427
1128
|
// src/runtime/operation-reservation.ts
|
|
1428
1129
|
import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
|
|
1429
|
-
import
|
|
1130
|
+
import path3 from "path";
|
|
1430
1131
|
var DIGEST_PATTERN3 = /^sha256:[a-f0-9]{64}$/;
|
|
1431
1132
|
var ENTITY_KEY_PATTERN2 = /^[a-z][a-z0-9_-]*:[^\0/\\]+$/;
|
|
1432
1133
|
function parseOperationReservation(value) {
|
|
@@ -1545,7 +1246,7 @@ async function removeOperationReservation(store, operationId, primaryStoreId) {
|
|
|
1545
1246
|
}
|
|
1546
1247
|
function reservationPath(store, operationId) {
|
|
1547
1248
|
assertUlid(operationId, "operation reservation operationId");
|
|
1548
|
-
return
|
|
1249
|
+
return path3.join(reservationDirectory(store), `${operationId}.json`);
|
|
1549
1250
|
}
|
|
1550
1251
|
function parseEntityKeySet2(value, label) {
|
|
1551
1252
|
if (!Array.isArray(value) || value.length === 0) {
|
|
@@ -4143,16 +3844,16 @@ function parseImplementationScope(value) {
|
|
|
4143
3844
|
"workflow metadata implementationScope modules"
|
|
4144
3845
|
)
|
|
4145
3846
|
};
|
|
4146
|
-
const
|
|
3847
|
+
const digest2 = parseDigest8(
|
|
4147
3848
|
value.digest,
|
|
4148
3849
|
"workflow metadata implementationScope digest"
|
|
4149
3850
|
);
|
|
4150
|
-
if (
|
|
3851
|
+
if (digest2 !== digestCanonicalJson(scope)) {
|
|
4151
3852
|
throw new Error(
|
|
4152
3853
|
"workflow metadata implementationScope digest does not match scope"
|
|
4153
3854
|
);
|
|
4154
3855
|
}
|
|
4155
|
-
return { ...scope, digest };
|
|
3856
|
+
return { ...scope, digest: digest2 };
|
|
4156
3857
|
}
|
|
4157
3858
|
function parseGovernance2(value) {
|
|
4158
3859
|
assertRecord(value, "workflow metadata governance");
|
|
@@ -5590,228 +5291,2086 @@ function allowedHandoffTransitions(from) {
|
|
|
5590
5291
|
}
|
|
5591
5292
|
}
|
|
5592
5293
|
|
|
5593
|
-
// src/
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5294
|
+
// src/context/confirmed-decision.ts
|
|
5295
|
+
import { lstat as lstat4, mkdir as mkdir4, readFile as readFile6, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
5296
|
+
import path8 from "path";
|
|
5297
|
+
|
|
5298
|
+
// src/context/privacy-guard.ts
|
|
5299
|
+
import { lstat as lstat3, readdir as readdir4 } from "fs/promises";
|
|
5300
|
+
import path7 from "path";
|
|
5301
|
+
import { performance } from "perf_hooks";
|
|
5302
|
+
|
|
5303
|
+
// src/runtime/local-lock.ts
|
|
5304
|
+
import { createHash } from "crypto";
|
|
5305
|
+
import { mkdir as mkdir2, readFile as readFile3, rename, rm, writeFile as writeFile2 } from "fs/promises";
|
|
5306
|
+
import path4 from "path";
|
|
5307
|
+
var ENTITY_LOCK_KEY_PATTERN = /^[a-z][a-z0-9_-]*:[^\0/\\]+$/;
|
|
5308
|
+
var DEFAULT_LOCK_LEASE_MS = 3e4;
|
|
5309
|
+
var MINIMUM_LOCK_LEASE_MS = 1e3;
|
|
5310
|
+
var MAXIMUM_LOCK_LEASE_MS = 5 * 6e4;
|
|
5311
|
+
async function acquireLocalLock(store, input) {
|
|
5312
|
+
const owner = createLockOwner(store, input);
|
|
5313
|
+
const directory = lockPath(store, owner.entityLockKey);
|
|
5314
|
+
await mkdir2(lockDirectory(store), { recursive: true });
|
|
5315
|
+
let acquiredDirectory = false;
|
|
5316
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
5317
|
+
try {
|
|
5318
|
+
await mkdir2(directory);
|
|
5319
|
+
acquiredDirectory = true;
|
|
5320
|
+
break;
|
|
5321
|
+
} catch (error) {
|
|
5322
|
+
if (!isAlreadyExists2(error)) throw error;
|
|
5323
|
+
if (!await reclaimExpiredDeadLock(store, owner.entityLockKey, owner)) {
|
|
5324
|
+
throw new Error("MANCODE_LOCK_HELD");
|
|
5325
|
+
}
|
|
5326
|
+
}
|
|
5327
|
+
}
|
|
5328
|
+
if (!acquiredDirectory) throw new Error("MANCODE_LOCK_HELD");
|
|
5329
|
+
try {
|
|
5330
|
+
await writeFile2(
|
|
5331
|
+
path4.join(directory, "owner.json"),
|
|
5332
|
+
`${JSON.stringify(owner, null, 2)}
|
|
5333
|
+
`,
|
|
5334
|
+
{ encoding: "utf8", flag: "wx" }
|
|
5335
|
+
);
|
|
5336
|
+
} catch (error) {
|
|
5337
|
+
await rm(directory, { recursive: true, force: true });
|
|
5338
|
+
throw error;
|
|
5611
5339
|
}
|
|
5612
|
-
|
|
5340
|
+
let released = false;
|
|
5341
|
+
let currentOwner = owner;
|
|
5613
5342
|
return {
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5343
|
+
storeId: store.storeId,
|
|
5344
|
+
entityLockKey: owner.entityLockKey,
|
|
5345
|
+
get owner() {
|
|
5346
|
+
return currentOwner;
|
|
5347
|
+
},
|
|
5348
|
+
async renew(now = /* @__PURE__ */ new Date()) {
|
|
5349
|
+
if (released) throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5350
|
+
const next = renewLockOwner(currentOwner, now);
|
|
5351
|
+
const current = await readLocalLock(store, currentOwner.entityLockKey);
|
|
5352
|
+
if (!sameLockOwner(current, currentOwner)) {
|
|
5353
|
+
throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5354
|
+
}
|
|
5355
|
+
await atomicWriteLockOwner(directory, next);
|
|
5356
|
+
currentOwner = next;
|
|
5357
|
+
},
|
|
5358
|
+
async release() {
|
|
5359
|
+
if (released) return;
|
|
5360
|
+
const current = await readLocalLock(store, currentOwner.entityLockKey);
|
|
5361
|
+
if (!sameLockOwner(current, currentOwner)) {
|
|
5362
|
+
throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5363
|
+
}
|
|
5364
|
+
await rm(directory, { recursive: true, force: false });
|
|
5365
|
+
released = true;
|
|
5366
|
+
}
|
|
5623
5367
|
};
|
|
5624
5368
|
}
|
|
5625
|
-
function
|
|
5626
|
-
|
|
5369
|
+
async function acquireEntityLocks(store, operationId, entityLockKeys, options = {}) {
|
|
5370
|
+
assertUlid(operationId, "local lock operationId");
|
|
5371
|
+
const orderedKeys = normalizeEntityLockKeys(entityLockKeys);
|
|
5372
|
+
const locks = [];
|
|
5373
|
+
try {
|
|
5374
|
+
for (const entityLockKey of orderedKeys) {
|
|
5375
|
+
locks.push(
|
|
5376
|
+
await acquireLocalLock(store, {
|
|
5377
|
+
operationId,
|
|
5378
|
+
entityLockKey,
|
|
5379
|
+
processId: options.processId,
|
|
5380
|
+
now: options.now,
|
|
5381
|
+
leaseMs: options.leaseMs
|
|
5382
|
+
})
|
|
5383
|
+
);
|
|
5384
|
+
}
|
|
5385
|
+
return locks;
|
|
5386
|
+
} catch (error) {
|
|
5387
|
+
await Promise.allSettled(
|
|
5388
|
+
[...locks].reverse().map((lock) => lock.release())
|
|
5389
|
+
);
|
|
5390
|
+
throw error;
|
|
5391
|
+
}
|
|
5392
|
+
}
|
|
5393
|
+
async function acquireOperationEntityLocks(operationId, targets, options = {}) {
|
|
5394
|
+
assertUlid(operationId, "operation lock operationId");
|
|
5395
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
5396
|
+
throw new Error("operation lock targets must not be empty");
|
|
5397
|
+
}
|
|
5398
|
+
const byStoreId = /* @__PURE__ */ new Map();
|
|
5399
|
+
for (const target of targets) {
|
|
5400
|
+
const normalizedKeys = normalizeEntityLockKeys(target.entityLockKeys);
|
|
5401
|
+
const existing = byStoreId.get(target.store.storeId);
|
|
5402
|
+
if (existing !== void 0) {
|
|
5403
|
+
throw new Error("operation lock targets must not repeat a store");
|
|
5404
|
+
}
|
|
5405
|
+
byStoreId.set(target.store.storeId, {
|
|
5406
|
+
store: target.store,
|
|
5407
|
+
entityLockKeys: normalizedKeys
|
|
5408
|
+
});
|
|
5409
|
+
}
|
|
5410
|
+
const locks = [];
|
|
5411
|
+
try {
|
|
5412
|
+
for (const target of [...byStoreId.values()].sort(
|
|
5413
|
+
(left, right) => compareUtf82(left.store.storeId, right.store.storeId)
|
|
5414
|
+
)) {
|
|
5415
|
+
locks.push(
|
|
5416
|
+
...await acquireEntityLocks(
|
|
5417
|
+
target.store,
|
|
5418
|
+
operationId,
|
|
5419
|
+
target.entityLockKeys,
|
|
5420
|
+
options
|
|
5421
|
+
)
|
|
5422
|
+
);
|
|
5423
|
+
}
|
|
5424
|
+
return locks;
|
|
5425
|
+
} catch (error) {
|
|
5426
|
+
await Promise.allSettled(
|
|
5427
|
+
[...locks].reverse().map((lock) => lock.release())
|
|
5428
|
+
);
|
|
5429
|
+
throw error;
|
|
5430
|
+
}
|
|
5431
|
+
}
|
|
5432
|
+
async function readLocalLock(store, entityLockKey) {
|
|
5433
|
+
assertEntityLockKey(entityLockKey);
|
|
5434
|
+
try {
|
|
5435
|
+
const raw = await readFile3(
|
|
5436
|
+
path4.join(lockPath(store, entityLockKey), "owner.json"),
|
|
5437
|
+
"utf8"
|
|
5438
|
+
);
|
|
5439
|
+
const owner = parseLocalLockOwner(JSON.parse(raw));
|
|
5440
|
+
if (owner.storeId !== store.storeId || owner.entityLockKey !== entityLockKey) {
|
|
5441
|
+
throw new Error("MANCODE_LOCK_CORRUPT");
|
|
5442
|
+
}
|
|
5443
|
+
return owner;
|
|
5444
|
+
} catch (error) {
|
|
5445
|
+
if (isNotFound3(error)) return null;
|
|
5446
|
+
if (error instanceof SyntaxError) throw new Error("MANCODE_LOCK_CORRUPT");
|
|
5447
|
+
throw error;
|
|
5448
|
+
}
|
|
5449
|
+
}
|
|
5450
|
+
function parseLocalLockOwner(value) {
|
|
5451
|
+
assertRecord(value, "local lock owner");
|
|
5627
5452
|
assertKnownKeys(
|
|
5628
5453
|
value,
|
|
5629
5454
|
[
|
|
5630
5455
|
"schemaVersion",
|
|
5631
|
-
"
|
|
5632
|
-
"
|
|
5633
|
-
"
|
|
5634
|
-
"
|
|
5635
|
-
"
|
|
5636
|
-
"
|
|
5637
|
-
"retention",
|
|
5638
|
-
"lastOperationId",
|
|
5639
|
-
"updatedAt"
|
|
5456
|
+
"operationId",
|
|
5457
|
+
"processId",
|
|
5458
|
+
"storeId",
|
|
5459
|
+
"entityLockKey",
|
|
5460
|
+
"acquiredAt",
|
|
5461
|
+
"leaseExpiresAt"
|
|
5640
5462
|
],
|
|
5641
|
-
"
|
|
5463
|
+
"local lock owner"
|
|
5642
5464
|
);
|
|
5643
5465
|
if (value.schemaVersion !== 1) {
|
|
5644
|
-
throw new Error("
|
|
5466
|
+
throw new Error("local lock owner schemaVersion must be 1");
|
|
5645
5467
|
}
|
|
5646
|
-
assertUlid(value.
|
|
5647
|
-
|
|
5648
|
-
|
|
5468
|
+
assertUlid(value.operationId, "local lock operationId");
|
|
5469
|
+
assertEntityLockKey(value.entityLockKey);
|
|
5470
|
+
if (typeof value.processId !== "number" || !Number.isSafeInteger(value.processId) || value.processId < 1) {
|
|
5471
|
+
throw new Error("local lock processId must be a positive integer");
|
|
5649
5472
|
}
|
|
5650
|
-
if (value.
|
|
5651
|
-
throw new Error("
|
|
5652
|
-
}
|
|
5653
|
-
if (typeof value.shareConfirmedDecisions !== "boolean") {
|
|
5654
|
-
throw new Error("team policy shareConfirmedDecisions must be boolean");
|
|
5473
|
+
if (typeof value.storeId !== "string" || !value.storeId.trim()) {
|
|
5474
|
+
throw new Error("local lock storeId is required");
|
|
5655
5475
|
}
|
|
5656
5476
|
return {
|
|
5657
5477
|
schemaVersion: 1,
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5661
|
-
|
|
5662
|
-
|
|
5663
|
-
|
|
5664
|
-
),
|
|
5665
|
-
defaultVisibility: value.defaultVisibility,
|
|
5666
|
-
shareConfirmedDecisions: value.shareConfirmedDecisions,
|
|
5667
|
-
retention: parseRetention(value.retention),
|
|
5668
|
-
lastOperationId: parseUlidOrNull8(
|
|
5669
|
-
value.lastOperationId,
|
|
5670
|
-
"team policy lastOperationId"
|
|
5671
|
-
),
|
|
5672
|
-
updatedAt: parseTimestamp12(value.updatedAt, "team policy updatedAt")
|
|
5478
|
+
operationId: value.operationId,
|
|
5479
|
+
processId: value.processId,
|
|
5480
|
+
storeId: value.storeId,
|
|
5481
|
+
entityLockKey: value.entityLockKey,
|
|
5482
|
+
acquiredAt: parseTimestamp12(value.acquiredAt, "local lock acquiredAt"),
|
|
5483
|
+
leaseExpiresAt: value.leaseExpiresAt === void 0 ? null : value.leaseExpiresAt === null ? null : parseTimestamp12(value.leaseExpiresAt, "local lock leaseExpiresAt")
|
|
5673
5484
|
};
|
|
5674
5485
|
}
|
|
5675
|
-
function
|
|
5676
|
-
|
|
5677
|
-
|
|
5678
|
-
configSchemaVersion: config.schemaVersion,
|
|
5679
|
-
layoutVersion: V3_LAYOUT_VERSION
|
|
5680
|
-
});
|
|
5681
|
-
}
|
|
5682
|
-
function projectConfigDigest(config) {
|
|
5683
|
-
return digestCanonicalJson(parseProjectConfig(config));
|
|
5684
|
-
}
|
|
5685
|
-
function assertConfigPolicyConsistency(config, policy) {
|
|
5686
|
-
if (config.workspaceId !== policy.workspaceId) {
|
|
5687
|
-
throw new Error("project config and team policy workspaceId must match");
|
|
5486
|
+
function normalizeEntityLockKeys(entityLockKeys) {
|
|
5487
|
+
if (!Array.isArray(entityLockKeys) || entityLockKeys.length === 0) {
|
|
5488
|
+
throw new Error("entity lock keys must be a non-empty array");
|
|
5688
5489
|
}
|
|
5490
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5491
|
+
for (const key of entityLockKeys) {
|
|
5492
|
+
assertEntityLockKey(key);
|
|
5493
|
+
if (keys.has(key)) throw new Error("entity lock keys must not repeat");
|
|
5494
|
+
keys.add(key);
|
|
5495
|
+
}
|
|
5496
|
+
return [...keys].sort(compareUtf82);
|
|
5497
|
+
}
|
|
5498
|
+
function createLockOwner(store, input) {
|
|
5499
|
+
assertUlid(input.operationId, "local lock operationId");
|
|
5500
|
+
assertEntityLockKey(input.entityLockKey);
|
|
5501
|
+
const processId = input.processId ?? process.pid;
|
|
5502
|
+
if (!Number.isSafeInteger(processId) || processId < 1) {
|
|
5503
|
+
throw new Error("local lock processId must be a positive integer");
|
|
5504
|
+
}
|
|
5505
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
5506
|
+
const leaseMs = parseLeaseMs(input.leaseMs);
|
|
5507
|
+
return {
|
|
5508
|
+
schemaVersion: 1,
|
|
5509
|
+
operationId: input.operationId,
|
|
5510
|
+
processId,
|
|
5511
|
+
storeId: store.storeId,
|
|
5512
|
+
entityLockKey: input.entityLockKey,
|
|
5513
|
+
acquiredAt: now.toISOString(),
|
|
5514
|
+
leaseExpiresAt: new Date(now.getTime() + leaseMs).toISOString()
|
|
5515
|
+
};
|
|
5689
5516
|
}
|
|
5690
|
-
function
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
previous.revision,
|
|
5694
|
-
next.revision,
|
|
5695
|
-
"project config revision"
|
|
5696
|
-
);
|
|
5697
|
-
const transportChanged = previous.transport.mode !== next.transport.mode || previous.transport.remote !== next.transport.remote || previous.transport.epoch !== next.transport.epoch;
|
|
5698
|
-
if (transportChanged && kind === "ordinary") {
|
|
5699
|
-
throw new Error(
|
|
5700
|
-
"project config transport may only change through transport_set or transport_migrate"
|
|
5701
|
-
);
|
|
5517
|
+
function renewLockOwner(owner, now) {
|
|
5518
|
+
if (owner.leaseExpiresAt === null) {
|
|
5519
|
+
throw new Error("MANCODE_LOCK_LEASE_UNAVAILABLE");
|
|
5702
5520
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5521
|
+
return {
|
|
5522
|
+
...owner,
|
|
5523
|
+
leaseExpiresAt: new Date(now.getTime() + lockLeaseMs(owner)).toISOString()
|
|
5524
|
+
};
|
|
5525
|
+
}
|
|
5526
|
+
async function reclaimExpiredDeadLock(store, entityLockKey, contender) {
|
|
5527
|
+
const existing = await readLocalLock(store, entityLockKey);
|
|
5528
|
+
if (existing === null || existing.leaseExpiresAt === null || Date.parse(existing.leaseExpiresAt) >= Date.parse(contender.acquiredAt) || processIsAlive(existing.processId)) {
|
|
5529
|
+
return false;
|
|
5707
5530
|
}
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
);
|
|
5531
|
+
const directory = lockPath(store, entityLockKey);
|
|
5532
|
+
const staleDirectory = `${directory}.stale.${process.pid}.${Date.now()}`;
|
|
5533
|
+
try {
|
|
5534
|
+
await rename(directory, staleDirectory);
|
|
5535
|
+
} catch (error) {
|
|
5536
|
+
if (isNotFound3(error) || isAlreadyExists2(error)) return true;
|
|
5537
|
+
throw error;
|
|
5712
5538
|
}
|
|
5539
|
+
await rm(staleDirectory, { recursive: true, force: true });
|
|
5540
|
+
return true;
|
|
5713
5541
|
}
|
|
5714
|
-
function
|
|
5715
|
-
if (
|
|
5716
|
-
|
|
5542
|
+
function processIsAlive(processId) {
|
|
5543
|
+
if (processId === process.pid) return true;
|
|
5544
|
+
try {
|
|
5545
|
+
process.kill(processId, 0);
|
|
5546
|
+
return true;
|
|
5547
|
+
} catch (error) {
|
|
5548
|
+
return !(typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH");
|
|
5717
5549
|
}
|
|
5718
|
-
assertRevisionIncrease(
|
|
5719
|
-
previous.revision,
|
|
5720
|
-
next.revision,
|
|
5721
|
-
"team policy revision"
|
|
5722
|
-
);
|
|
5723
5550
|
}
|
|
5724
|
-
function
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5729
|
-
"project config transport"
|
|
5730
|
-
);
|
|
5731
|
-
if (value.mode !== "local" && value.mode !== "git-ref") {
|
|
5732
|
-
throw new Error("project config transport mode is invalid");
|
|
5733
|
-
}
|
|
5734
|
-
const remote = parseNonEmptyStringOrNull5(
|
|
5735
|
-
value.remote,
|
|
5736
|
-
"project config transport remote"
|
|
5551
|
+
async function atomicWriteLockOwner(directory, owner) {
|
|
5552
|
+
const target = path4.join(directory, "owner.json");
|
|
5553
|
+
const temporary = path4.join(
|
|
5554
|
+
directory,
|
|
5555
|
+
`.owner.${process.pid}.${Date.now()}.tmp`
|
|
5737
5556
|
);
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5557
|
+
await writeFile2(temporary, `${JSON.stringify(owner, null, 2)}
|
|
5558
|
+
`, {
|
|
5559
|
+
encoding: "utf8",
|
|
5560
|
+
flag: "wx"
|
|
5561
|
+
});
|
|
5562
|
+
await rename(temporary, target);
|
|
5563
|
+
}
|
|
5564
|
+
function parseLeaseMs(value) {
|
|
5565
|
+
if (value === void 0) return DEFAULT_LOCK_LEASE_MS;
|
|
5566
|
+
if (!Number.isSafeInteger(value) || value < MINIMUM_LOCK_LEASE_MS || value > MAXIMUM_LOCK_LEASE_MS) {
|
|
5567
|
+
throw new Error("local lock leaseMs is invalid");
|
|
5743
5568
|
}
|
|
5744
|
-
|
|
5745
|
-
return { mode: value.mode, remote, epoch };
|
|
5569
|
+
return value;
|
|
5746
5570
|
}
|
|
5747
|
-
function
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
["localRawArtifactDays", "localCacheDays", "completedSessionDays"],
|
|
5752
|
-
"team policy retention"
|
|
5753
|
-
);
|
|
5754
|
-
return {
|
|
5755
|
-
localRawArtifactDays: parseNonNegativeInteger9(
|
|
5756
|
-
value.localRawArtifactDays,
|
|
5757
|
-
"team policy retention localRawArtifactDays"
|
|
5758
|
-
),
|
|
5759
|
-
localCacheDays: parseNonNegativeInteger9(
|
|
5760
|
-
value.localCacheDays,
|
|
5761
|
-
"team policy retention localCacheDays"
|
|
5762
|
-
),
|
|
5763
|
-
completedSessionDays: parseNonNegativeInteger9(
|
|
5764
|
-
value.completedSessionDays,
|
|
5765
|
-
"team policy retention completedSessionDays"
|
|
5766
|
-
)
|
|
5767
|
-
};
|
|
5571
|
+
function lockLeaseMs(owner) {
|
|
5572
|
+
if (owner.leaseExpiresAt === null) return DEFAULT_LOCK_LEASE_MS;
|
|
5573
|
+
const duration = Date.parse(owner.leaseExpiresAt) - Date.parse(owner.acquiredAt);
|
|
5574
|
+
return duration >= MINIMUM_LOCK_LEASE_MS && duration <= MAXIMUM_LOCK_LEASE_MS ? duration : DEFAULT_LOCK_LEASE_MS;
|
|
5768
5575
|
}
|
|
5769
|
-
function
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
);
|
|
5774
|
-
}
|
|
5576
|
+
function lockPath(store, entityLockKey) {
|
|
5577
|
+
assertEntityLockKey(entityLockKey);
|
|
5578
|
+
const fileName = createHash("sha256").update(entityLockKey, "utf8").digest("hex");
|
|
5579
|
+
return path4.join(lockDirectory(store), `${fileName}.lock`);
|
|
5775
5580
|
}
|
|
5776
|
-
function
|
|
5777
|
-
if (
|
|
5778
|
-
throw new Error(
|
|
5581
|
+
function assertEntityLockKey(value) {
|
|
5582
|
+
if (typeof value !== "string" || !ENTITY_LOCK_KEY_PATTERN.test(value) || value.includes("..")) {
|
|
5583
|
+
throw new Error("entity lock key is invalid");
|
|
5779
5584
|
}
|
|
5780
5585
|
}
|
|
5781
|
-
function
|
|
5782
|
-
|
|
5783
|
-
throw new Error(`${label} must be a positive integer`);
|
|
5784
|
-
}
|
|
5785
|
-
return value;
|
|
5586
|
+
function sameLockOwner(current, expected) {
|
|
5587
|
+
return current !== null && current.operationId === expected.operationId && current.processId === expected.processId && current.storeId === expected.storeId && current.entityLockKey === expected.entityLockKey && current.acquiredAt === expected.acquiredAt && current.leaseExpiresAt === expected.leaseExpiresAt;
|
|
5786
5588
|
}
|
|
5787
|
-
function
|
|
5788
|
-
|
|
5789
|
-
|
|
5589
|
+
function compareUtf82(left, right) {
|
|
5590
|
+
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
5591
|
+
}
|
|
5592
|
+
function parseTimestamp12(value, label) {
|
|
5593
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
5594
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
5790
5595
|
}
|
|
5791
5596
|
return value;
|
|
5792
5597
|
}
|
|
5598
|
+
function isAlreadyExists2(error) {
|
|
5599
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
5600
|
+
}
|
|
5601
|
+
function isNotFound3(error) {
|
|
5602
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5603
|
+
}
|
|
5604
|
+
|
|
5605
|
+
// src/runtime/operation-store.ts
|
|
5606
|
+
import { mkdir as mkdir3, readFile as readFile4, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
|
|
5607
|
+
import path5 from "path";
|
|
5608
|
+
|
|
5609
|
+
// src/runtime/atomic-file.ts
|
|
5610
|
+
import { rename as rename2 } from "fs/promises";
|
|
5611
|
+
var RETRIABLE_WINDOWS_RENAME_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
5612
|
+
async function replaceFileAtomically(temporary, target, options = {}) {
|
|
5613
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
5614
|
+
const retryDelayMs = options.retryDelayMs ?? 25;
|
|
5615
|
+
if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
|
|
5616
|
+
throw new Error("MANCODE_ATOMIC_REPLACE_ATTEMPTS_INVALID");
|
|
5617
|
+
}
|
|
5618
|
+
if (!Number.isSafeInteger(retryDelayMs) || retryDelayMs < 0) {
|
|
5619
|
+
throw new Error("MANCODE_ATOMIC_REPLACE_DELAY_INVALID");
|
|
5620
|
+
}
|
|
5621
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
5622
|
+
try {
|
|
5623
|
+
await rename2(temporary, target);
|
|
5624
|
+
return;
|
|
5625
|
+
} catch (error) {
|
|
5626
|
+
if (process.platform !== "win32" || !isRetriableWindowsRenameError(error) || attempt === maxAttempts) {
|
|
5627
|
+
throw error;
|
|
5628
|
+
}
|
|
5629
|
+
await delay(retryDelayMs * attempt);
|
|
5630
|
+
}
|
|
5631
|
+
}
|
|
5632
|
+
}
|
|
5633
|
+
function isRetriableWindowsRenameError(error) {
|
|
5634
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRIABLE_WINDOWS_RENAME_CODES.has(
|
|
5635
|
+
error.code ?? ""
|
|
5636
|
+
);
|
|
5637
|
+
}
|
|
5638
|
+
async function delay(milliseconds) {
|
|
5639
|
+
await new Promise((resolve) => {
|
|
5640
|
+
setTimeout(resolve, milliseconds);
|
|
5641
|
+
});
|
|
5642
|
+
}
|
|
5643
|
+
|
|
5644
|
+
// src/runtime/operation-definition.ts
|
|
5645
|
+
var OPERATION_AUTHORIZATION_ACTIONS = {
|
|
5646
|
+
workflow_create: ["local_workflow_mutation", "shared_create_publish_promote"],
|
|
5647
|
+
workflow_update: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5648
|
+
requirements_draft: [
|
|
5649
|
+
"local_workflow_mutation",
|
|
5650
|
+
"shared_metadata_plan_mutation"
|
|
5651
|
+
],
|
|
5652
|
+
requirements_finalize: [
|
|
5653
|
+
"local_workflow_mutation",
|
|
5654
|
+
"shared_metadata_plan_mutation"
|
|
5655
|
+
],
|
|
5656
|
+
plan_revision: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5657
|
+
review_remediation: [
|
|
5658
|
+
"local_workflow_mutation",
|
|
5659
|
+
"shared_ledger_evidence",
|
|
5660
|
+
"review_skip_or_waiver"
|
|
5661
|
+
],
|
|
5662
|
+
verification_record: ["local_workflow_mutation", "shared_ledger_evidence"],
|
|
5663
|
+
task_complete: [
|
|
5664
|
+
"local_workflow_mutation",
|
|
5665
|
+
"task_complete_scope_change_child_merge"
|
|
5666
|
+
],
|
|
5667
|
+
publish_promote: ["shared_create_publish_promote"],
|
|
5668
|
+
handoff_transition: ["handoff_offer_cancel", "handoff_accept_reject"],
|
|
5669
|
+
handoff_accept: ["handoff_accept_reject"],
|
|
5670
|
+
scope_change_reclaim: ["task_complete_scope_change_child_merge"],
|
|
5671
|
+
claim_create: ["claim_create"],
|
|
5672
|
+
claim_renew_release: ["claim_renew_release_transfer"],
|
|
5673
|
+
claim_transfer: ["claim_renew_release_transfer"],
|
|
5674
|
+
claim_reclaim: ["claim_reclaim"],
|
|
5675
|
+
claim_revalidation: ["claim_renew_release_transfer"],
|
|
5676
|
+
checkpoint_create: [
|
|
5677
|
+
"local_workflow_mutation",
|
|
5678
|
+
"shared_metadata_plan_mutation"
|
|
5679
|
+
],
|
|
5680
|
+
solo_handoff: ["local_workflow_mutation"],
|
|
5681
|
+
reframe: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5682
|
+
child_result_merge: [
|
|
5683
|
+
"local_workflow_mutation",
|
|
5684
|
+
"task_complete_scope_change_child_merge"
|
|
5685
|
+
],
|
|
5686
|
+
task_head_reconcile: ["task_head_reconcile"],
|
|
5687
|
+
transport_migrate: ["team_policy_config_transport"],
|
|
5688
|
+
greenfield_initialize: ["team_policy_config_transport"],
|
|
5689
|
+
adapter_upgrade: ["project_maintenance"],
|
|
5690
|
+
project_policy_upgrade: ["project_maintenance"],
|
|
5691
|
+
privacy_policy_update: ["project_maintenance"],
|
|
5692
|
+
v3_activate: ["team_policy_config_transport"]
|
|
5693
|
+
};
|
|
5694
|
+
var prepare = (id, expectedRevisionPrefixes = [], requiredLockPrefixes = [], optional = {}) => ({
|
|
5695
|
+
id,
|
|
5696
|
+
visibility: "preparation",
|
|
5697
|
+
expectedRevisionPrefixes,
|
|
5698
|
+
requiredLockPrefixes,
|
|
5699
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5700
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5701
|
+
crashRecovery: "safe_abort"
|
|
5702
|
+
});
|
|
5703
|
+
var write = (id, expectedRevisionPrefixes, requiredLockPrefixes, optional = {}) => ({
|
|
5704
|
+
id,
|
|
5705
|
+
visibility: "business_write",
|
|
5706
|
+
expectedRevisionPrefixes,
|
|
5707
|
+
requiredLockPrefixes,
|
|
5708
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5709
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5710
|
+
crashRecovery: "forward_repair"
|
|
5711
|
+
});
|
|
5712
|
+
var commit = (id, expectedRevisionPrefixes, requiredLockPrefixes, optional = {}) => ({
|
|
5713
|
+
id,
|
|
5714
|
+
visibility: "commit",
|
|
5715
|
+
expectedRevisionPrefixes,
|
|
5716
|
+
requiredLockPrefixes,
|
|
5717
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5718
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5719
|
+
crashRecovery: "forward_repair"
|
|
5720
|
+
});
|
|
5721
|
+
var OPERATION_DEFINITIONS = {
|
|
5722
|
+
workflow_create: definition(
|
|
5723
|
+
"workflow_create",
|
|
5724
|
+
"publish-locator",
|
|
5725
|
+
false,
|
|
5726
|
+
false,
|
|
5727
|
+
[
|
|
5728
|
+
prepare("validate"),
|
|
5729
|
+
// The staging directory is private to this operation and has no task
|
|
5730
|
+
// locator/fence visibility. A crash here is compensable by deleting
|
|
5731
|
+
// verified staging, rather than forcing a fictitious forward repair.
|
|
5732
|
+
prepare("write-staging-aggregate", ["task:"], ["task:"]),
|
|
5733
|
+
prepare("validate-aggregate", ["task:"], ["task:"]),
|
|
5734
|
+
write("publish-task-directory", ["task:"], ["task:"]),
|
|
5735
|
+
write("publish-locator", ["locator:"], ["task:", "locator:"]),
|
|
5736
|
+
commit("commit", ["task:", "locator:"], ["task:", "locator:"])
|
|
5737
|
+
]
|
|
5738
|
+
),
|
|
5739
|
+
workflow_update: definition(
|
|
5740
|
+
"workflow_update",
|
|
5741
|
+
"write-metadata",
|
|
5742
|
+
true,
|
|
5743
|
+
false,
|
|
5744
|
+
[
|
|
5745
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5746
|
+
write("write-metadata", ["task:"], ["task:"]),
|
|
5747
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5748
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5749
|
+
]
|
|
5750
|
+
),
|
|
5751
|
+
requirements_draft: definition(
|
|
5752
|
+
"requirements_draft",
|
|
5753
|
+
"update-metadata",
|
|
5754
|
+
true,
|
|
5755
|
+
false,
|
|
5756
|
+
[
|
|
5757
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5758
|
+
write("write-requirements", ["requirements:", "task:"], ["task:"]),
|
|
5759
|
+
write(
|
|
5760
|
+
"mark-review-verification-stale",
|
|
5761
|
+
["review:", "verification:", "task:"],
|
|
5762
|
+
["task:"]
|
|
5763
|
+
),
|
|
5764
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5765
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5766
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5767
|
+
]
|
|
5768
|
+
),
|
|
5769
|
+
requirements_finalize: definition(
|
|
5770
|
+
"requirements_finalize",
|
|
5771
|
+
"update-metadata",
|
|
5772
|
+
true,
|
|
5773
|
+
false,
|
|
5774
|
+
[
|
|
5775
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5776
|
+
write("write-requirements", ["requirements:", "task:"], ["task:"]),
|
|
5777
|
+
write(
|
|
5778
|
+
"mark-review-verification-stale",
|
|
5779
|
+
["review:", "verification:", "task:"],
|
|
5780
|
+
["task:"]
|
|
5781
|
+
),
|
|
5782
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5783
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5784
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5785
|
+
]
|
|
5786
|
+
),
|
|
5787
|
+
plan_revision: definition("plan_revision", "update-metadata", true, false, [
|
|
5788
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5789
|
+
write("write-plan", ["plan:", "task:"], ["task:"]),
|
|
5790
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5791
|
+
write(
|
|
5792
|
+
"mark-review-verification-stale",
|
|
5793
|
+
["review:", "verification:", "task:"],
|
|
5794
|
+
["task:"]
|
|
5795
|
+
),
|
|
5796
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5797
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5798
|
+
]),
|
|
5799
|
+
review_remediation: definition(
|
|
5800
|
+
"review_remediation",
|
|
5801
|
+
"update-metadata",
|
|
5802
|
+
true,
|
|
5803
|
+
false,
|
|
5804
|
+
[
|
|
5805
|
+
prepare("validate", ["task:", "review:"], ["task:"]),
|
|
5806
|
+
write("write-review-ledger", ["review:", "task:"], ["task:"]),
|
|
5807
|
+
write("mark-verification-stale", ["verification:", "task:"], ["task:"]),
|
|
5808
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5809
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5810
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5811
|
+
]
|
|
5812
|
+
),
|
|
5813
|
+
verification_record: definition(
|
|
5814
|
+
"verification_record",
|
|
5815
|
+
"update-metadata",
|
|
5816
|
+
true,
|
|
5817
|
+
false,
|
|
5818
|
+
[
|
|
5819
|
+
prepare("validate", ["task:", "verification:"], ["task:"]),
|
|
5820
|
+
write("write-verification-ledger", ["verification:", "task:"], ["task:"]),
|
|
5821
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5822
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5823
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5824
|
+
]
|
|
5825
|
+
),
|
|
5826
|
+
task_complete: definition(
|
|
5827
|
+
"task_complete",
|
|
5828
|
+
"write-completed-metadata",
|
|
5829
|
+
true,
|
|
5830
|
+
false,
|
|
5831
|
+
[
|
|
5832
|
+
prepare("validate-completion-gate", ["task:"], ["task:"], {
|
|
5833
|
+
expectedRevisionPrefixes: ["claim:"]
|
|
5834
|
+
}),
|
|
5835
|
+
write("mark-operation-pending", ["task:"], ["task:"]),
|
|
5836
|
+
write("release-or-transfer-claims", ["task:"], ["task:"], {
|
|
5837
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5838
|
+
requiredLockPrefixes: ["claim:"]
|
|
5839
|
+
}),
|
|
5840
|
+
write("write-completed-metadata", ["task:"], ["task:"]),
|
|
5841
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5842
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5843
|
+
]
|
|
5844
|
+
),
|
|
5845
|
+
publish_promote: definition(
|
|
5846
|
+
"publish_promote",
|
|
5847
|
+
"publish-destination",
|
|
5848
|
+
true,
|
|
5849
|
+
true,
|
|
5850
|
+
[
|
|
5851
|
+
prepare("validate-privacy-and-paths", ["task:"], ["task:"]),
|
|
5852
|
+
// The destination staging directory remains private until its atomic
|
|
5853
|
+
// rename, so it is safely compensable just like workflow creation.
|
|
5854
|
+
prepare("stage-destination", ["task:"], ["task:"]),
|
|
5855
|
+
write("mark-source-operation-pending", ["task:"], ["task:"]),
|
|
5856
|
+
write(
|
|
5857
|
+
"publish-destination",
|
|
5858
|
+
["task:", "locator:"],
|
|
5859
|
+
["task:", "locator:"]
|
|
5860
|
+
),
|
|
5861
|
+
write("write-source-successor", ["task:"], ["task:"]),
|
|
5862
|
+
write("publish-destination-locator", ["locator:"], ["locator:"]),
|
|
5863
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5864
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5865
|
+
]
|
|
5866
|
+
),
|
|
5867
|
+
handoff_transition: definition(
|
|
5868
|
+
"handoff_transition",
|
|
5869
|
+
"write-handoff",
|
|
5870
|
+
false,
|
|
5871
|
+
false,
|
|
5872
|
+
[
|
|
5873
|
+
prepare("validate", ["task:", "handoff:"], ["task:", "handoff:"]),
|
|
5874
|
+
write("write-handoff", ["task:", "handoff:"], ["task:", "handoff:"]),
|
|
5875
|
+
commit("commit", ["task:", "handoff:"], ["task:", "handoff:"])
|
|
5876
|
+
]
|
|
5877
|
+
),
|
|
5878
|
+
handoff_accept: definition("handoff_accept", "accept-handoff", true, false, [
|
|
5879
|
+
prepare(
|
|
5880
|
+
"validate",
|
|
5881
|
+
["task:", "handoff:", "checkpoint:"],
|
|
5882
|
+
["task:", "handoff:", "checkpoint:"],
|
|
5883
|
+
{
|
|
5884
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5885
|
+
requiredLockPrefixes: ["claim:"]
|
|
5886
|
+
}
|
|
5887
|
+
),
|
|
5888
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
5889
|
+
write("create-pending-successor-claims", ["task:"], ["task:"], {
|
|
5890
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5891
|
+
requiredLockPrefixes: ["claim:"]
|
|
5892
|
+
}),
|
|
5893
|
+
write("transfer-old-claims", ["task:"], ["task:"], {
|
|
5894
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5895
|
+
requiredLockPrefixes: ["claim:"]
|
|
5896
|
+
}),
|
|
5897
|
+
write("update-owner-and-checkpoint", ["task:", "checkpoint:"], ["task:"]),
|
|
5898
|
+
write("activate-successor-claims", ["task:"], ["task:"], {
|
|
5899
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5900
|
+
requiredLockPrefixes: ["claim:"]
|
|
5901
|
+
}),
|
|
5902
|
+
write("accept-handoff", ["handoff:", "task:"], ["task:", "handoff:"]),
|
|
5903
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5904
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5905
|
+
]),
|
|
5906
|
+
scope_change_reclaim: definition(
|
|
5907
|
+
"scope_change_reclaim",
|
|
5908
|
+
"activate-successor-claims",
|
|
5909
|
+
true,
|
|
5910
|
+
false,
|
|
5911
|
+
[
|
|
5912
|
+
prepare("validate", ["task:"], ["task:"], {
|
|
5913
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5914
|
+
requiredLockPrefixes: ["claim:"]
|
|
5915
|
+
}),
|
|
5916
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
5917
|
+
write(
|
|
5918
|
+
"write-scope-changed-checkpoint",
|
|
5919
|
+
["checkpoint:", "task:"],
|
|
5920
|
+
["task:"]
|
|
5921
|
+
),
|
|
5922
|
+
write("create-pending-successor-claims", ["task:"], ["task:"], {
|
|
5923
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5924
|
+
requiredLockPrefixes: ["claim:"]
|
|
5925
|
+
}),
|
|
5926
|
+
write("terminate-old-claims", ["task:"], ["task:"], {
|
|
5927
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5928
|
+
requiredLockPrefixes: ["claim:"]
|
|
5929
|
+
}),
|
|
5930
|
+
write(
|
|
5931
|
+
"mark-review-verification-stale",
|
|
5932
|
+
["review:", "verification:", "task:"],
|
|
5933
|
+
["task:"]
|
|
5934
|
+
),
|
|
5935
|
+
write("update-metadata-scope", ["task:"], ["task:"]),
|
|
5936
|
+
write("activate-successor-claims", ["task:"], ["task:"], {
|
|
5937
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5938
|
+
requiredLockPrefixes: ["claim:"]
|
|
5939
|
+
}),
|
|
5940
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5941
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5942
|
+
]
|
|
5943
|
+
),
|
|
5944
|
+
claim_create: definition(
|
|
5945
|
+
"claim_create",
|
|
5946
|
+
"create-active-claim",
|
|
5947
|
+
false,
|
|
5948
|
+
false,
|
|
5949
|
+
[
|
|
5950
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5951
|
+
write("create-active-claim", ["claim:", "task:"], ["task:", "claim:"]),
|
|
5952
|
+
commit("commit", ["claim:", "task:"], ["task:", "claim:"])
|
|
5953
|
+
]
|
|
5954
|
+
),
|
|
5955
|
+
claim_renew_release: definition(
|
|
5956
|
+
"claim_renew_release",
|
|
5957
|
+
"update-claim",
|
|
5958
|
+
false,
|
|
5959
|
+
false,
|
|
5960
|
+
[
|
|
5961
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
5962
|
+
write("update-claim", ["task:", "claim:"], ["task:", "claim:"]),
|
|
5963
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
5964
|
+
]
|
|
5965
|
+
),
|
|
5966
|
+
claim_transfer: definition(
|
|
5967
|
+
"claim_transfer",
|
|
5968
|
+
"activate-successor-claim",
|
|
5969
|
+
false,
|
|
5970
|
+
false,
|
|
5971
|
+
[
|
|
5972
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
5973
|
+
write(
|
|
5974
|
+
"create-pending-successor-claim",
|
|
5975
|
+
["task:", "claim:"],
|
|
5976
|
+
["task:", "claim:"]
|
|
5977
|
+
),
|
|
5978
|
+
write(
|
|
5979
|
+
"transfer-predecessor-claim",
|
|
5980
|
+
["task:", "claim:"],
|
|
5981
|
+
["task:", "claim:"]
|
|
5982
|
+
),
|
|
5983
|
+
write(
|
|
5984
|
+
"activate-successor-claim",
|
|
5985
|
+
["task:", "claim:"],
|
|
5986
|
+
["task:", "claim:"]
|
|
5987
|
+
),
|
|
5988
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
5989
|
+
]
|
|
5990
|
+
),
|
|
5991
|
+
claim_reclaim: definition("claim_reclaim", "expire-claim", false, false, [
|
|
5992
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
5993
|
+
write("expire-claim", ["task:", "claim:"], ["task:", "claim:"]),
|
|
5994
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
5995
|
+
]),
|
|
5996
|
+
claim_revalidation: definition(
|
|
5997
|
+
"claim_revalidation",
|
|
5998
|
+
"update-claim-validation",
|
|
5999
|
+
true,
|
|
6000
|
+
false,
|
|
6001
|
+
[
|
|
6002
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6003
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6004
|
+
write("write-base-changed-checkpoint", ["task:"], ["task:"], {
|
|
6005
|
+
expectedRevisionPrefixes: ["checkpoint:"]
|
|
6006
|
+
}),
|
|
6007
|
+
write(
|
|
6008
|
+
"update-claim-validation",
|
|
6009
|
+
["claim:", "task:"],
|
|
6010
|
+
["task:", "claim:"]
|
|
6011
|
+
),
|
|
6012
|
+
write("complete-task-validation", ["task:"], ["task:"]),
|
|
6013
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6014
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6015
|
+
]
|
|
6016
|
+
),
|
|
6017
|
+
checkpoint_create: definition(
|
|
6018
|
+
"checkpoint_create",
|
|
6019
|
+
"update-metadata-checkpoint-ref",
|
|
6020
|
+
true,
|
|
6021
|
+
false,
|
|
6022
|
+
[
|
|
6023
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
6024
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6025
|
+
write("write-checkpoint", ["checkpoint:", "task:"], ["task:"]),
|
|
6026
|
+
write("update-metadata-checkpoint-ref", ["task:"], ["task:"]),
|
|
6027
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6028
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6029
|
+
]
|
|
6030
|
+
),
|
|
6031
|
+
solo_handoff: definition(
|
|
6032
|
+
"solo_handoff",
|
|
6033
|
+
"write-workflow-assignment",
|
|
6034
|
+
false,
|
|
6035
|
+
false,
|
|
6036
|
+
[
|
|
6037
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
6038
|
+
write("write-workflow-assignment", ["task:"], ["task:"]),
|
|
6039
|
+
// The session pointer is a compensable local projection, rather than
|
|
6040
|
+
// journaled workflow authority. It is intentionally not part of the
|
|
6041
|
+
// operation CAS/lock set: a failed or later-replaced pointer must never
|
|
6042
|
+
// block repair of the durable solo assignment.
|
|
6043
|
+
write("update-session-pointer", ["task:"], ["task:"]),
|
|
6044
|
+
commit("commit", ["task:"], ["task:"])
|
|
6045
|
+
]
|
|
6046
|
+
),
|
|
6047
|
+
reframe: definition("reframe", "commit-reframed-metadata", true, false, [
|
|
6048
|
+
prepare(
|
|
6049
|
+
"validate",
|
|
6050
|
+
[
|
|
6051
|
+
"task:",
|
|
6052
|
+
"requirements:",
|
|
6053
|
+
"plan:",
|
|
6054
|
+
"review:",
|
|
6055
|
+
"verification:",
|
|
6056
|
+
"archive:",
|
|
6057
|
+
"checkpoint:"
|
|
6058
|
+
],
|
|
6059
|
+
["task:", "archive:", "checkpoint:"],
|
|
6060
|
+
{
|
|
6061
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6062
|
+
requiredLockPrefixes: ["claim:"]
|
|
6063
|
+
}
|
|
6064
|
+
),
|
|
6065
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6066
|
+
write(
|
|
6067
|
+
"archive-requirements-plan",
|
|
6068
|
+
["archive:", "requirements:", "plan:", "task:"],
|
|
6069
|
+
["archive:", "task:"]
|
|
6070
|
+
),
|
|
6071
|
+
write("release-active-claims", ["task:"], ["task:"], {
|
|
6072
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6073
|
+
requiredLockPrefixes: ["claim:"]
|
|
6074
|
+
}),
|
|
6075
|
+
write("write-requirements-draft", ["requirements:", "task:"], ["task:"]),
|
|
6076
|
+
write(
|
|
6077
|
+
"mark-review-verification-stale",
|
|
6078
|
+
["review:", "verification:", "task:"],
|
|
6079
|
+
["task:"]
|
|
6080
|
+
),
|
|
6081
|
+
write(
|
|
6082
|
+
"write-reframe-checkpoint",
|
|
6083
|
+
["checkpoint:", "task:"],
|
|
6084
|
+
["checkpoint:", "task:"]
|
|
6085
|
+
),
|
|
6086
|
+
write("commit-reframed-metadata", ["task:"], ["task:"]),
|
|
6087
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6088
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6089
|
+
]),
|
|
6090
|
+
child_result_merge: definition(
|
|
6091
|
+
"child_result_merge",
|
|
6092
|
+
"update-parent-metadata",
|
|
6093
|
+
true,
|
|
6094
|
+
false,
|
|
6095
|
+
[
|
|
6096
|
+
prepare("validate-parent-snapshot", ["task:", "checkpoint:"], ["task:"]),
|
|
6097
|
+
write("mark-parent-operation-pending", ["task:"], ["task:"]),
|
|
6098
|
+
write("write-merge-checkpoint", ["checkpoint:", "task:"], ["task:"]),
|
|
6099
|
+
write("update-parent-metadata", ["task:"], ["task:"]),
|
|
6100
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6101
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6102
|
+
]
|
|
6103
|
+
),
|
|
6104
|
+
task_head_reconcile: definition(
|
|
6105
|
+
"task_head_reconcile",
|
|
6106
|
+
"adopt-task-head-fence",
|
|
6107
|
+
true,
|
|
6108
|
+
false,
|
|
6109
|
+
[
|
|
6110
|
+
prepare(
|
|
6111
|
+
"validate-clean-store-and-git-reachability",
|
|
6112
|
+
["task:", "task_head:"],
|
|
6113
|
+
["task:", "task_head:"]
|
|
6114
|
+
),
|
|
6115
|
+
prepare(
|
|
6116
|
+
"confirm-adoption",
|
|
6117
|
+
["task:", "task_head:"],
|
|
6118
|
+
["task:", "task_head:"]
|
|
6119
|
+
),
|
|
6120
|
+
write(
|
|
6121
|
+
"adopt-task-head-fence",
|
|
6122
|
+
["task_head:", "task:"],
|
|
6123
|
+
["task:", "task_head:"]
|
|
6124
|
+
),
|
|
6125
|
+
commit("commit", ["task_head:", "task:"], ["task:", "task_head:"])
|
|
6126
|
+
]
|
|
6127
|
+
),
|
|
6128
|
+
transport_migrate: definition(
|
|
6129
|
+
"transport_migrate",
|
|
6130
|
+
"switch-config-authority",
|
|
6131
|
+
true,
|
|
6132
|
+
true,
|
|
6133
|
+
[
|
|
6134
|
+
prepare(
|
|
6135
|
+
"freeze-shared-coordination-writes",
|
|
6136
|
+
["config:", "task_head:"],
|
|
6137
|
+
["config:"]
|
|
6138
|
+
),
|
|
6139
|
+
prepare(
|
|
6140
|
+
"validate-old-authority",
|
|
6141
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6142
|
+
["config:"]
|
|
6143
|
+
),
|
|
6144
|
+
write(
|
|
6145
|
+
"stage-new-authority",
|
|
6146
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6147
|
+
["config:"]
|
|
6148
|
+
),
|
|
6149
|
+
write(
|
|
6150
|
+
"establish-new-epoch",
|
|
6151
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6152
|
+
["config:"]
|
|
6153
|
+
),
|
|
6154
|
+
write("switch-config-authority", ["config:"], ["config:"]),
|
|
6155
|
+
commit("commit", ["config:"], ["config:"])
|
|
6156
|
+
]
|
|
6157
|
+
),
|
|
6158
|
+
greenfield_initialize: definition(
|
|
6159
|
+
"greenfield_initialize",
|
|
6160
|
+
"publish-v3-root",
|
|
6161
|
+
false,
|
|
6162
|
+
false,
|
|
6163
|
+
[
|
|
6164
|
+
prepare("verify-no-legacy-authority"),
|
|
6165
|
+
// The named staging root is still private and can be verified then
|
|
6166
|
+
// deleted before the atomic .mancode publication boundary.
|
|
6167
|
+
prepare(
|
|
6168
|
+
"write-initializing-staging-root",
|
|
6169
|
+
["schema:", "config:"],
|
|
6170
|
+
["schema:"]
|
|
6171
|
+
),
|
|
6172
|
+
prepare(
|
|
6173
|
+
"write-v3-config-policy-adapters",
|
|
6174
|
+
["schema:", "config:"],
|
|
6175
|
+
["schema:"]
|
|
6176
|
+
),
|
|
6177
|
+
write("publish-v3-root", ["schema:", "config:"], ["schema:"]),
|
|
6178
|
+
write("register-workspace-binding", ["binding:"], ["binding:"]),
|
|
6179
|
+
write("publish-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6180
|
+
write("activate-v3-manifest", ["schema:"], ["schema:"]),
|
|
6181
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6182
|
+
]
|
|
6183
|
+
),
|
|
6184
|
+
adapter_upgrade: definition(
|
|
6185
|
+
"adapter_upgrade",
|
|
6186
|
+
"replace-managed-adapters",
|
|
6187
|
+
false,
|
|
6188
|
+
false,
|
|
6189
|
+
[
|
|
6190
|
+
prepare("validate", ["adapter:"], ["adapter:"], {
|
|
6191
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6192
|
+
requiredLockPrefixes: ["schema:"]
|
|
6193
|
+
}),
|
|
6194
|
+
write("replace-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6195
|
+
write("update-adapter-inventory", [], [], {
|
|
6196
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6197
|
+
requiredLockPrefixes: ["schema:"]
|
|
6198
|
+
}),
|
|
6199
|
+
commit("verify", ["adapter:"], ["adapter:"], {
|
|
6200
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6201
|
+
requiredLockPrefixes: ["schema:"]
|
|
6202
|
+
}),
|
|
6203
|
+
commit("commit", ["adapter:"], ["adapter:"], {
|
|
6204
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6205
|
+
requiredLockPrefixes: ["schema:"]
|
|
6206
|
+
})
|
|
6207
|
+
]
|
|
6208
|
+
),
|
|
6209
|
+
project_policy_upgrade: definition(
|
|
6210
|
+
"project_policy_upgrade",
|
|
6211
|
+
"write-manifest",
|
|
6212
|
+
false,
|
|
6213
|
+
false,
|
|
6214
|
+
[
|
|
6215
|
+
prepare("validate", ["schema:"], ["schema:"]),
|
|
6216
|
+
write("write-manifest", ["schema:"], ["schema:"]),
|
|
6217
|
+
write("verify-manifest", ["schema:"], ["schema:"]),
|
|
6218
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6219
|
+
]
|
|
6220
|
+
),
|
|
6221
|
+
privacy_policy_update: definition(
|
|
6222
|
+
"privacy_policy_update",
|
|
6223
|
+
"write-manifest",
|
|
6224
|
+
false,
|
|
6225
|
+
false,
|
|
6226
|
+
[
|
|
6227
|
+
prepare("validate", ["schema:"], ["schema:"]),
|
|
6228
|
+
write("write-remote-policy", ["schema:"], ["schema:"]),
|
|
6229
|
+
write("write-exclusions", ["schema:"], ["schema:"]),
|
|
6230
|
+
write("write-policy", ["schema:"], ["schema:"]),
|
|
6231
|
+
write("write-manifest", ["schema:"], ["schema:"]),
|
|
6232
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6233
|
+
]
|
|
6234
|
+
),
|
|
6235
|
+
v3_activate: definition("v3_activate", "activate-manifest", true, true, [
|
|
6236
|
+
prepare(
|
|
6237
|
+
"validate-staged-migration",
|
|
6238
|
+
["schema:", "config:", "stage:"],
|
|
6239
|
+
["schema:", "config:", "stage:"],
|
|
6240
|
+
{
|
|
6241
|
+
expectedRevisionPrefixes: ["task:", "task_head:", "adapter:"],
|
|
6242
|
+
requiredLockPrefixes: ["task:", "task_head:", "adapter:"]
|
|
6243
|
+
}
|
|
6244
|
+
),
|
|
6245
|
+
write(
|
|
6246
|
+
"mark-manifest-activating",
|
|
6247
|
+
["schema:", "stage:"],
|
|
6248
|
+
["schema:", "stage:"]
|
|
6249
|
+
),
|
|
6250
|
+
write("replace-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6251
|
+
write("promote-staged-tasks", [], [], {
|
|
6252
|
+
expectedRevisionPrefixes: ["task:", "task_head:"],
|
|
6253
|
+
requiredLockPrefixes: ["task:", "task_head:"]
|
|
6254
|
+
}),
|
|
6255
|
+
write(
|
|
6256
|
+
"record-adapter-inventory-and-baseline",
|
|
6257
|
+
["schema:", "config:"],
|
|
6258
|
+
["schema:", "config:"]
|
|
6259
|
+
),
|
|
6260
|
+
write("activate-manifest", ["schema:", "stage:"], ["schema:", "stage:"]),
|
|
6261
|
+
commit("commit", ["schema:", "stage:"], ["schema:", "stage:"])
|
|
6262
|
+
])
|
|
6263
|
+
};
|
|
6264
|
+
var OPERATION_CRASH_FIXTURES = Object.fromEntries(
|
|
6265
|
+
Object.values(OPERATION_DEFINITIONS).map((definition2) => [
|
|
6266
|
+
definition2.type,
|
|
6267
|
+
[
|
|
6268
|
+
{
|
|
6269
|
+
operationType: definition2.type,
|
|
6270
|
+
crashAfter: "prepared",
|
|
6271
|
+
expectedRecovery: "safe_abort"
|
|
6272
|
+
},
|
|
6273
|
+
...definition2.steps.map((step) => ({
|
|
6274
|
+
operationType: definition2.type,
|
|
6275
|
+
crashAfter: step.id,
|
|
6276
|
+
expectedRecovery: step.crashRecovery
|
|
6277
|
+
}))
|
|
6278
|
+
]
|
|
6279
|
+
])
|
|
6280
|
+
);
|
|
6281
|
+
function getOperationDefinition(operationType) {
|
|
6282
|
+
return OPERATION_DEFINITIONS[operationType];
|
|
6283
|
+
}
|
|
6284
|
+
function assertOperationJournalMatchesDefinition(journal) {
|
|
6285
|
+
const definition2 = getOperationDefinition(journal.type);
|
|
6286
|
+
assertOperationAuthorizationAction(journal);
|
|
6287
|
+
const actualSteps = journal.steps.map((step) => step.id);
|
|
6288
|
+
const expectedSteps = definition2.steps.map((step) => step.id);
|
|
6289
|
+
if (actualSteps.length !== expectedSteps.length || actualSteps.some((step, index) => step !== expectedSteps[index])) {
|
|
6290
|
+
throw new Error(
|
|
6291
|
+
`operation ${journal.type} steps do not match its machine-readable definition`
|
|
6292
|
+
);
|
|
6293
|
+
}
|
|
6294
|
+
const expectedRevisionPrefixes = new Set(
|
|
6295
|
+
definition2.steps.flatMap((step) => step.expectedRevisionPrefixes)
|
|
6296
|
+
);
|
|
6297
|
+
const lockPrefixes = new Set(
|
|
6298
|
+
definition2.steps.flatMap((step) => step.requiredLockPrefixes)
|
|
6299
|
+
);
|
|
6300
|
+
const optionalExpectedRevisionPrefixes = new Set(
|
|
6301
|
+
definition2.steps.flatMap((step) => step.optionalExpectedRevisionPrefixes)
|
|
6302
|
+
);
|
|
6303
|
+
const optionalLockPrefixes = new Set(
|
|
6304
|
+
definition2.steps.flatMap((step) => step.optionalRequiredLockPrefixes)
|
|
6305
|
+
);
|
|
6306
|
+
const sharedTask = journal.entityLocks.some(
|
|
6307
|
+
(key) => key.startsWith("task:shared:")
|
|
6308
|
+
);
|
|
6309
|
+
if (!sharedTask) {
|
|
6310
|
+
expectedRevisionPrefixes.delete("task_head:");
|
|
6311
|
+
}
|
|
6312
|
+
assertPrefixCoverage(
|
|
6313
|
+
Object.keys(journal.expectedRevisions),
|
|
6314
|
+
expectedRevisionPrefixes,
|
|
6315
|
+
"expected revisions"
|
|
6316
|
+
);
|
|
6317
|
+
assertPrefixCoverage(journal.entityLocks, lockPrefixes, "entity locks");
|
|
6318
|
+
assertOptionalPrefixPairs(
|
|
6319
|
+
Object.keys(journal.expectedRevisions),
|
|
6320
|
+
journal.entityLocks,
|
|
6321
|
+
optionalExpectedRevisionPrefixes,
|
|
6322
|
+
optionalLockPrefixes
|
|
6323
|
+
);
|
|
6324
|
+
if (definition2.requiresSharedTaskHeadFence && sharedTask) {
|
|
6325
|
+
assertPrefixCoverage(
|
|
6326
|
+
Object.keys(journal.expectedRevisions),
|
|
6327
|
+
/* @__PURE__ */ new Set(["task_head:"]),
|
|
6328
|
+
"expected revisions"
|
|
6329
|
+
);
|
|
6330
|
+
assertPrefixCoverage(
|
|
6331
|
+
journal.entityLocks,
|
|
6332
|
+
/* @__PURE__ */ new Set(["task_head:"]),
|
|
6333
|
+
"entity locks"
|
|
6334
|
+
);
|
|
6335
|
+
}
|
|
6336
|
+
}
|
|
6337
|
+
function assertOperationAuthorizationAction(journal) {
|
|
6338
|
+
const definition2 = getOperationDefinition(journal.type);
|
|
6339
|
+
if (!definition2.authorizationActions.includes(journal.authorizationBasis.action)) {
|
|
6340
|
+
throw new Error("MANCODE_OPERATION_AUTHORIZATION_ACTION_MISMATCH");
|
|
6341
|
+
}
|
|
6342
|
+
}
|
|
6343
|
+
function definition(type, primaryCommitStep, requiresSharedTaskHeadFence, allowsSecondaryReservations, steps) {
|
|
6344
|
+
if (!steps.some((step) => step.id === primaryCommitStep)) {
|
|
6345
|
+
throw new Error(`operation ${type} has no primary commit step`);
|
|
6346
|
+
}
|
|
6347
|
+
for (const step of steps) {
|
|
6348
|
+
for (const prefix of step.optionalExpectedRevisionPrefixes) {
|
|
6349
|
+
if (step.expectedRevisionPrefixes.includes(prefix)) {
|
|
6350
|
+
throw new Error(
|
|
6351
|
+
`operation ${type} repeats optional expected revision prefix ${prefix}`
|
|
6352
|
+
);
|
|
6353
|
+
}
|
|
6354
|
+
}
|
|
6355
|
+
for (const prefix of step.optionalRequiredLockPrefixes) {
|
|
6356
|
+
if (step.requiredLockPrefixes.includes(prefix)) {
|
|
6357
|
+
throw new Error(
|
|
6358
|
+
`operation ${type} repeats optional lock prefix ${prefix}`
|
|
6359
|
+
);
|
|
6360
|
+
}
|
|
6361
|
+
}
|
|
6362
|
+
}
|
|
6363
|
+
return {
|
|
6364
|
+
schemaVersion: 1,
|
|
6365
|
+
type,
|
|
6366
|
+
authorizationActions: OPERATION_AUTHORIZATION_ACTIONS[type],
|
|
6367
|
+
primaryCommitStep,
|
|
6368
|
+
requiresSharedTaskHeadFence,
|
|
6369
|
+
allowsSecondaryReservations,
|
|
6370
|
+
steps
|
|
6371
|
+
};
|
|
6372
|
+
}
|
|
6373
|
+
function assertPrefixCoverage(values, prefixes, label) {
|
|
6374
|
+
for (const prefix of prefixes) {
|
|
6375
|
+
if (!values.some((value) => value.startsWith(prefix))) {
|
|
6376
|
+
throw new Error(`operation journal ${label} are missing ${prefix}`);
|
|
6377
|
+
}
|
|
6378
|
+
}
|
|
6379
|
+
}
|
|
6380
|
+
function assertOptionalPrefixPairs(expectedRevisions, entityLocks, optionalExpectedRevisionPrefixes, optionalLockPrefixes) {
|
|
6381
|
+
for (const prefix of optionalExpectedRevisionPrefixes) {
|
|
6382
|
+
if (expectedRevisions.some((value) => value.startsWith(prefix)) && !entityLocks.some((value) => value.startsWith(prefix))) {
|
|
6383
|
+
throw new Error(
|
|
6384
|
+
`operation journal entity locks are missing optional ${prefix}`
|
|
6385
|
+
);
|
|
6386
|
+
}
|
|
6387
|
+
}
|
|
6388
|
+
for (const prefix of optionalLockPrefixes) {
|
|
6389
|
+
if (entityLocks.some((value) => value.startsWith(prefix)) && !expectedRevisions.some((value) => value.startsWith(prefix))) {
|
|
6390
|
+
throw new Error(
|
|
6391
|
+
`operation journal expected revisions are missing optional ${prefix}`
|
|
6392
|
+
);
|
|
6393
|
+
}
|
|
6394
|
+
}
|
|
6395
|
+
}
|
|
6396
|
+
|
|
6397
|
+
// src/runtime/operation-store.ts
|
|
6398
|
+
async function createPreparedOperationJournal(store, journal) {
|
|
6399
|
+
assertOperationAuthorizationAction(journal);
|
|
6400
|
+
if (journal.state !== "prepared") {
|
|
6401
|
+
throw new Error("only a prepared operation journal may be created");
|
|
6402
|
+
}
|
|
6403
|
+
if (journal.primaryStoreId !== store.storeId) {
|
|
6404
|
+
throw new Error(
|
|
6405
|
+
"operation journal primaryStoreId does not match its home store"
|
|
6406
|
+
);
|
|
6407
|
+
}
|
|
6408
|
+
assertOperationReservationTopology(journal);
|
|
6409
|
+
if (journal.type !== "transport_migrate" && store.kind !== "checkout_local") {
|
|
6410
|
+
const migrationInProgress = (await listUnfinishedOperationJournals(store)).some((candidate) => candidate.type === "transport_migrate");
|
|
6411
|
+
if (migrationInProgress) {
|
|
6412
|
+
throw new Error("MANCODE_TRANSPORT_MIGRATION_FROZEN");
|
|
6413
|
+
}
|
|
6414
|
+
}
|
|
6415
|
+
const directory = operationDirectory(store);
|
|
6416
|
+
const target = operationJournalPath(store, journal.operationId);
|
|
6417
|
+
await mkdir3(directory, { recursive: true });
|
|
6418
|
+
try {
|
|
6419
|
+
await writeFile3(target, serialize2(journal), {
|
|
6420
|
+
encoding: "utf8",
|
|
6421
|
+
flag: "wx"
|
|
6422
|
+
});
|
|
6423
|
+
return journal;
|
|
6424
|
+
} catch (error) {
|
|
6425
|
+
if (!isAlreadyExists3(error)) throw error;
|
|
6426
|
+
const existing = await readOperationJournal(store, journal.operationId);
|
|
6427
|
+
if (existing !== null && operationJournalDigest(existing) === operationJournalDigest(journal)) {
|
|
6428
|
+
return existing;
|
|
6429
|
+
}
|
|
6430
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CONFLICT");
|
|
6431
|
+
}
|
|
6432
|
+
}
|
|
6433
|
+
async function prepareOperationStores(input) {
|
|
6434
|
+
const { primaryStore, journal } = input;
|
|
6435
|
+
validateSecondaryStores(primaryStore, journal, input.secondaryStores);
|
|
6436
|
+
await createPreparedOperationJournal(primaryStore, journal);
|
|
6437
|
+
const createdAt = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
6438
|
+
const reservations = [];
|
|
6439
|
+
for (const secondaryStore of input.secondaryStores) {
|
|
6440
|
+
const reservation = createOperationReservation(
|
|
6441
|
+
journal,
|
|
6442
|
+
secondaryStore.storeId,
|
|
6443
|
+
createdAt
|
|
6444
|
+
);
|
|
6445
|
+
reservations.push(
|
|
6446
|
+
await writeOperationReservation(secondaryStore, reservation)
|
|
6447
|
+
);
|
|
6448
|
+
}
|
|
6449
|
+
return reservations;
|
|
6450
|
+
}
|
|
6451
|
+
async function readOperationJournal(store, operationId) {
|
|
6452
|
+
try {
|
|
6453
|
+
assertUlid(operationId, "operation journal operationId");
|
|
6454
|
+
const raw = await readFile4(
|
|
6455
|
+
operationJournalPath(store, operationId),
|
|
6456
|
+
"utf8"
|
|
6457
|
+
);
|
|
6458
|
+
return parseOperationJournal(JSON.parse(raw));
|
|
6459
|
+
} catch (error) {
|
|
6460
|
+
if (isNotFound4(error)) return null;
|
|
6461
|
+
if (error instanceof SyntaxError) {
|
|
6462
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CORRUPT");
|
|
6463
|
+
}
|
|
6464
|
+
throw error;
|
|
6465
|
+
}
|
|
6466
|
+
}
|
|
6467
|
+
async function updateOperationJournal(store, next, options) {
|
|
6468
|
+
const previous = await readOperationJournal(store, next.operationId);
|
|
6469
|
+
if (previous === null) throw new Error("MANCODE_OPERATION_JOURNAL_NOT_FOUND");
|
|
6470
|
+
if (previous.primaryStoreId !== store.storeId || next.primaryStoreId !== store.storeId) {
|
|
6471
|
+
throw new Error(
|
|
6472
|
+
"operation journal must be updated in its primary home store"
|
|
6473
|
+
);
|
|
6474
|
+
}
|
|
6475
|
+
assertOperationJournalTransition(previous, next, options);
|
|
6476
|
+
await atomicWriteOperationJournal(store, next);
|
|
6477
|
+
return next;
|
|
6478
|
+
}
|
|
6479
|
+
async function listUnfinishedOperationJournals(store) {
|
|
6480
|
+
let entries;
|
|
6481
|
+
try {
|
|
6482
|
+
entries = await readdir2(operationDirectory(store));
|
|
6483
|
+
} catch (error) {
|
|
6484
|
+
if (isNotFound4(error)) return [];
|
|
6485
|
+
throw error;
|
|
6486
|
+
}
|
|
6487
|
+
const journals = [];
|
|
6488
|
+
for (const entry of entries) {
|
|
6489
|
+
if (!entry.endsWith(".json")) continue;
|
|
6490
|
+
const operationId = entry.slice(0, -".json".length);
|
|
6491
|
+
try {
|
|
6492
|
+
assertUlid(operationId, "operation journal filename");
|
|
6493
|
+
} catch {
|
|
6494
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CORRUPT");
|
|
6495
|
+
}
|
|
6496
|
+
const journal = await readOperationJournal(store, operationId);
|
|
6497
|
+
if (journal !== null && journal.state !== "committed" && journal.state !== "aborted") {
|
|
6498
|
+
journals.push(journal);
|
|
6499
|
+
}
|
|
6500
|
+
}
|
|
6501
|
+
return journals.sort(
|
|
6502
|
+
(left, right) => Buffer.from(left.operationId, "utf8").compare(
|
|
6503
|
+
Buffer.from(right.operationId, "utf8")
|
|
6504
|
+
)
|
|
6505
|
+
);
|
|
6506
|
+
}
|
|
6507
|
+
function operationJournalPath(store, operationId) {
|
|
6508
|
+
assertUlid(operationId, "operation journal operationId");
|
|
6509
|
+
return path5.join(operationDirectory(store), `${operationId}.json`);
|
|
6510
|
+
}
|
|
6511
|
+
async function atomicWriteOperationJournal(store, journal) {
|
|
6512
|
+
const target = operationJournalPath(store, journal.operationId);
|
|
6513
|
+
const temporary = path5.join(
|
|
6514
|
+
operationDirectory(store),
|
|
6515
|
+
`.${journal.operationId}.${process.pid}.${Date.now()}.tmp`
|
|
6516
|
+
);
|
|
6517
|
+
await writeFile3(temporary, serialize2(journal), {
|
|
6518
|
+
encoding: "utf8",
|
|
6519
|
+
flag: "wx"
|
|
6520
|
+
});
|
|
6521
|
+
await replaceFileAtomically(temporary, target);
|
|
6522
|
+
}
|
|
6523
|
+
function validateSecondaryStores(primaryStore, journal, secondaryStores) {
|
|
6524
|
+
if (journal.primaryStoreId !== primaryStore.storeId) {
|
|
6525
|
+
throw new Error(
|
|
6526
|
+
"operation journal primaryStoreId does not match primaryStore"
|
|
6527
|
+
);
|
|
6528
|
+
}
|
|
6529
|
+
const expectedStoreIds = new Set(
|
|
6530
|
+
journal.secondaryReservations.map((reservation) => reservation.storeId)
|
|
6531
|
+
);
|
|
6532
|
+
const suppliedStoreIds = /* @__PURE__ */ new Set();
|
|
6533
|
+
for (const store of secondaryStores) {
|
|
6534
|
+
if (store.storeId === primaryStore.storeId || suppliedStoreIds.has(store.storeId)) {
|
|
6535
|
+
throw new Error(
|
|
6536
|
+
"operation secondary stores must be unique and exclude primaryStore"
|
|
6537
|
+
);
|
|
6538
|
+
}
|
|
6539
|
+
suppliedStoreIds.add(store.storeId);
|
|
6540
|
+
}
|
|
6541
|
+
if (expectedStoreIds.size !== suppliedStoreIds.size || [...expectedStoreIds].some((storeId) => !suppliedStoreIds.has(storeId))) {
|
|
6542
|
+
throw new Error(
|
|
6543
|
+
"operation secondary stores do not match the journal reservations"
|
|
6544
|
+
);
|
|
6545
|
+
}
|
|
6546
|
+
}
|
|
6547
|
+
function serialize2(journal) {
|
|
6548
|
+
return `${JSON.stringify(journal, null, 2)}
|
|
6549
|
+
`;
|
|
6550
|
+
}
|
|
6551
|
+
function isAlreadyExists3(error) {
|
|
6552
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
6553
|
+
}
|
|
6554
|
+
function isNotFound4(error) {
|
|
6555
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6556
|
+
}
|
|
6557
|
+
|
|
6558
|
+
// src/runtime/project-write-barrier.ts
|
|
6559
|
+
var PROJECT_SCHEMA_LOCK = "schema:project";
|
|
6560
|
+
async function acquireProjectWriteBarrier(runtime, operationId, now) {
|
|
6561
|
+
const localStore = resolveLocalEntityHomeStore(
|
|
6562
|
+
runtime.entityHomeStoreContext
|
|
6563
|
+
);
|
|
6564
|
+
const [lock] = await acquireEntityLocks(
|
|
6565
|
+
localStore,
|
|
6566
|
+
operationId,
|
|
6567
|
+
[PROJECT_SCHEMA_LOCK],
|
|
6568
|
+
{ now }
|
|
6569
|
+
);
|
|
6570
|
+
if (lock === void 0) {
|
|
6571
|
+
throw new Error("MANCODE_LOCK_HELD");
|
|
6572
|
+
}
|
|
6573
|
+
try {
|
|
6574
|
+
const unfinished = await listUnfinishedOperationJournals(localStore);
|
|
6575
|
+
if (unfinished.some(
|
|
6576
|
+
(journal) => journal.entityLocks.includes(PROJECT_SCHEMA_LOCK)
|
|
6577
|
+
)) {
|
|
6578
|
+
throw new Error("MANCODE_OPERATION_REPAIR_REQUIRED");
|
|
6579
|
+
}
|
|
6580
|
+
return lock;
|
|
6581
|
+
} catch (error) {
|
|
6582
|
+
await lock.release().catch(() => void 0);
|
|
6583
|
+
throw error;
|
|
6584
|
+
}
|
|
6585
|
+
}
|
|
6586
|
+
|
|
6587
|
+
// src/context/privacy-policy.ts
|
|
6588
|
+
import { lstat as lstat2, readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
6589
|
+
import path6 from "path";
|
|
6590
|
+
|
|
6591
|
+
// src/context/manifest.ts
|
|
6592
|
+
var ACTIVATION_STATES = /* @__PURE__ */ new Set([
|
|
6593
|
+
"initializing",
|
|
6594
|
+
"dual_read",
|
|
6595
|
+
"activating",
|
|
6596
|
+
"v3_active",
|
|
6597
|
+
"repair_required"
|
|
6598
|
+
]);
|
|
6599
|
+
var MANAGED_ADAPTERS = [
|
|
6600
|
+
"claude-code",
|
|
6601
|
+
"codex",
|
|
6602
|
+
"cursor",
|
|
6603
|
+
"copilot",
|
|
6604
|
+
"zcode",
|
|
6605
|
+
"kimi-code",
|
|
6606
|
+
"qoder",
|
|
6607
|
+
"dsh"
|
|
6608
|
+
];
|
|
6609
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
6610
|
+
var DIGEST_PATTERN13 = /^sha256:[a-f0-9]{64}$/;
|
|
6611
|
+
function parseSchemaManifest(value) {
|
|
6612
|
+
assertRecord(value, "schema manifest");
|
|
6613
|
+
if (value.manifestVersion !== 1 && value.manifestVersion !== 2 && value.manifestVersion !== 3) {
|
|
6614
|
+
throw new Error(
|
|
6615
|
+
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${String(value.manifestVersion)} supported=1,2,3 requiredWriter=0.6.5`
|
|
6616
|
+
);
|
|
6617
|
+
}
|
|
6618
|
+
const manifestVersion = value.manifestVersion;
|
|
6619
|
+
assertKnownKeys(
|
|
6620
|
+
value,
|
|
6621
|
+
[
|
|
6622
|
+
"manifestVersion",
|
|
6623
|
+
"layoutVersion",
|
|
6624
|
+
"epoch",
|
|
6625
|
+
"activationState",
|
|
6626
|
+
"minReaderVersion",
|
|
6627
|
+
"minWriterVersion",
|
|
6628
|
+
"activatedAt",
|
|
6629
|
+
"legacyBaseline",
|
|
6630
|
+
"managedAdapters",
|
|
6631
|
+
"lastOperationId",
|
|
6632
|
+
...manifestVersion >= 2 ? ["workflowPolicyDefaults"] : [],
|
|
6633
|
+
...manifestVersion === 3 ? ["privacyPolicy"] : []
|
|
6634
|
+
],
|
|
6635
|
+
"schema manifest"
|
|
6636
|
+
);
|
|
6637
|
+
if (value.layoutVersion !== 3) {
|
|
6638
|
+
throw new Error("schema manifest must use layoutVersion 3");
|
|
6639
|
+
}
|
|
6640
|
+
assertUlid(value.epoch, "schema manifest epoch");
|
|
6641
|
+
if (typeof value.activationState !== "string" || !ACTIVATION_STATES.has(value.activationState)) {
|
|
6642
|
+
throw new Error("schema manifest activationState is invalid");
|
|
6643
|
+
}
|
|
6644
|
+
const common = {
|
|
6645
|
+
layoutVersion: 3,
|
|
6646
|
+
epoch: value.epoch,
|
|
6647
|
+
activationState: value.activationState,
|
|
6648
|
+
minReaderVersion: parseVersion(value.minReaderVersion, "minReaderVersion"),
|
|
6649
|
+
minWriterVersion: parseVersion(value.minWriterVersion, "minWriterVersion"),
|
|
6650
|
+
activatedAt: parseTimestampOrNull3(value.activatedAt, "activatedAt"),
|
|
6651
|
+
legacyBaseline: parseLegacyBaseline(value.legacyBaseline),
|
|
6652
|
+
managedAdapters: parseManagedAdapters(value.managedAdapters),
|
|
6653
|
+
lastOperationId: parseUlidOrNull8(value.lastOperationId, "lastOperationId")
|
|
6654
|
+
};
|
|
6655
|
+
const manifest = manifestVersion === 1 ? { manifestVersion: 1, ...common } : manifestVersion === 2 ? {
|
|
6656
|
+
manifestVersion: 2,
|
|
6657
|
+
...common,
|
|
6658
|
+
workflowPolicyDefaults: parseWorkflowPolicyDefaults(
|
|
6659
|
+
value.workflowPolicyDefaults
|
|
6660
|
+
)
|
|
6661
|
+
} : {
|
|
6662
|
+
manifestVersion: 3,
|
|
6663
|
+
...common,
|
|
6664
|
+
workflowPolicyDefaults: parsePrivacyManifestPlanningDefaults(
|
|
6665
|
+
value.workflowPolicyDefaults
|
|
6666
|
+
),
|
|
6667
|
+
privacyPolicy: parsePrivacyPolicyReference(value.privacyPolicy)
|
|
6668
|
+
};
|
|
6669
|
+
assertManifestStateShape(manifest);
|
|
6670
|
+
if (manifest.manifestVersion >= 2 && (compareVersions(manifest.minReaderVersion, "0.4.0") < 0 || compareVersions(manifest.minWriterVersion, "0.4.0") < 0)) {
|
|
6671
|
+
throw new Error(
|
|
6672
|
+
"schema manifest V2 requires minReaderVersion and minWriterVersion 0.4.0 or newer"
|
|
6673
|
+
);
|
|
6674
|
+
}
|
|
6675
|
+
if (manifest.manifestVersion === 3 && (compareVersions(manifest.minReaderVersion, "0.6.5") < 0 || compareVersions(manifest.minWriterVersion, "0.6.5") < 0))
|
|
6676
|
+
throw new Error(
|
|
6677
|
+
"schema manifest V3 requires minReaderVersion and minWriterVersion 0.6.5 or newer"
|
|
6678
|
+
);
|
|
6679
|
+
return manifest;
|
|
6680
|
+
}
|
|
6681
|
+
function serializeSchemaManifest(value) {
|
|
6682
|
+
return `${JSON.stringify(parseSchemaManifest(value), null, 2)}
|
|
6683
|
+
`;
|
|
6684
|
+
}
|
|
6685
|
+
function managedAdapterNames(inventory) {
|
|
6686
|
+
return MANAGED_ADAPTERS.filter((adapter) => inventory[adapter] !== void 0);
|
|
6687
|
+
}
|
|
6688
|
+
function assertSchemaManifestTransition(previous, next) {
|
|
6689
|
+
if (previous.manifestVersion !== next.manifestVersion || previous.layoutVersion !== next.layoutVersion || previous.epoch !== next.epoch || !sameLegacyBaseline(previous.legacyBaseline, next.legacyBaseline) || compareVersions(next.minReaderVersion, previous.minReaderVersion) < 0 || compareVersions(next.minWriterVersion, previous.minWriterVersion) < 0) {
|
|
6690
|
+
throw new Error(
|
|
6691
|
+
"schema manifest identity and legacy baseline are immutable"
|
|
6692
|
+
);
|
|
6693
|
+
}
|
|
6694
|
+
if (previous.manifestVersion === 3 && next.manifestVersion === 3 && (previous.privacyPolicy.revision !== next.privacyPolicy.revision || previous.privacyPolicy.digest !== next.privacyPolicy.digest || previous.workflowPolicyDefaults.planning !== next.workflowPolicyDefaults.planning))
|
|
6695
|
+
throw new Error("MANCODE_PRIVACY_MANIFEST_TRANSITION_REQUIRED");
|
|
6696
|
+
if (previous.activationState === next.activationState) return;
|
|
6697
|
+
if (!allowedManifestTransitions(previous.activationState).has(
|
|
6698
|
+
next.activationState
|
|
6699
|
+
)) {
|
|
6700
|
+
throw new Error(
|
|
6701
|
+
`invalid schema manifest transition: ${previous.activationState} -> ${next.activationState}`
|
|
6702
|
+
);
|
|
6703
|
+
}
|
|
6704
|
+
}
|
|
6705
|
+
function assertSchemaManifestPrivacyTransition(previous, next) {
|
|
6706
|
+
if (next.manifestVersion !== 3 || previous.epoch !== next.epoch || previous.layoutVersion !== next.layoutVersion || previous.activationState !== "v3_active" || next.activationState !== "v3_active" || previous.activatedAt !== next.activatedAt || !sameLegacyBaseline(previous.legacyBaseline, next.legacyBaseline) || !managedAdapterInventoriesMatch(
|
|
6707
|
+
previous.managedAdapters,
|
|
6708
|
+
next.managedAdapters
|
|
6709
|
+
) || compareVersions(next.minReaderVersion, previous.minReaderVersion) < 0 || compareVersions(next.minWriterVersion, previous.minWriterVersion) < 0 || next.lastOperationId === null || next.lastOperationId === previous.lastOperationId || next.privacyPolicy.revision !== (previous.manifestVersion === 3 ? previous.privacyPolicy.revision : 0) + 1 || next.workflowPolicyDefaults.planning !== (previous.manifestVersion === 1 ? 1 : previous.workflowPolicyDefaults.planning))
|
|
6710
|
+
throw new Error("MANCODE_PRIVACY_MANIFEST_TRANSITION_INVALID");
|
|
6711
|
+
}
|
|
6712
|
+
function parsePrivacyManifestPlanningDefaults(value) {
|
|
6713
|
+
assertRecord(value, "workflow policy defaults");
|
|
6714
|
+
assertKnownKeys(value, ["planning"], "workflow policy defaults");
|
|
6715
|
+
if (value.planning !== 1 && value.planning !== 2)
|
|
6716
|
+
throw new Error("schema manifest planning policy must be 1 or 2");
|
|
6717
|
+
return { planning: value.planning };
|
|
6718
|
+
}
|
|
6719
|
+
function parsePrivacyPolicyReference(value) {
|
|
6720
|
+
assertRecord(value, "privacy policy reference");
|
|
6721
|
+
assertKnownKeys(value, ["revision", "digest"], "privacy policy reference");
|
|
6722
|
+
if (!Number.isSafeInteger(value.revision) || value.revision < 1 || typeof value.digest !== "string" || !DIGEST_PATTERN13.test(value.digest))
|
|
6723
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REFERENCE_INVALID");
|
|
6724
|
+
return { revision: value.revision, digest: value.digest };
|
|
6725
|
+
}
|
|
6726
|
+
function assertSchemaManifestPolicyUpgrade(previous, next) {
|
|
6727
|
+
const legacyUpgrade = previous.manifestVersion === 1 && next.manifestVersion === 2;
|
|
6728
|
+
const privacyUpgrade = previous.manifestVersion === 3 && next.manifestVersion === 3 && previous.workflowPolicyDefaults.planning === 1 && previous.privacyPolicy.revision === next.privacyPolicy.revision && previous.privacyPolicy.digest === next.privacyPolicy.digest;
|
|
6729
|
+
if (!legacyUpgrade && !privacyUpgrade || previous.layoutVersion !== next.layoutVersion || previous.epoch !== next.epoch || previous.activationState !== "v3_active" || next.activationState !== "v3_active" || previous.activatedAt !== next.activatedAt || !sameLegacyBaseline(previous.legacyBaseline, next.legacyBaseline) || !managedAdapterInventoriesMatch(
|
|
6730
|
+
previous.managedAdapters,
|
|
6731
|
+
next.managedAdapters
|
|
6732
|
+
) || compareVersions(next.minReaderVersion, previous.minReaderVersion) < 0 || compareVersions(next.minWriterVersion, previous.minWriterVersion) < 0 || next.workflowPolicyDefaults.planning !== 2 || next.lastOperationId === null || next.lastOperationId === previous.lastOperationId) {
|
|
6733
|
+
throw new Error("invalid schema manifest Policy 2 upgrade");
|
|
6734
|
+
}
|
|
6735
|
+
}
|
|
6736
|
+
function assertActivationRollbackManifestTransition(previous, next) {
|
|
6737
|
+
if (previous.manifestVersion !== next.manifestVersion || previous.layoutVersion !== next.layoutVersion || previous.epoch !== next.epoch || !sameLegacyBaseline(previous.legacyBaseline, next.legacyBaseline) || previous.activationState !== "v3_active" || next.activationState !== "dual_read" || previous.activatedAt === null || next.activatedAt !== null) {
|
|
6738
|
+
throw new Error("invalid activation rollback manifest transition");
|
|
6739
|
+
}
|
|
6740
|
+
}
|
|
6741
|
+
function parseWorkflowPolicyDefaults(value) {
|
|
6742
|
+
assertRecord(value, "schema manifest workflowPolicyDefaults");
|
|
6743
|
+
assertKnownKeys(
|
|
6744
|
+
value,
|
|
6745
|
+
["planning"],
|
|
6746
|
+
"schema manifest workflowPolicyDefaults"
|
|
6747
|
+
);
|
|
6748
|
+
if (value.planning !== 2) {
|
|
6749
|
+
throw new Error(
|
|
6750
|
+
`MANCODE_POLICY_VERSION_UNSUPPORTED: component=planning observed=${String(value.planning)} supported=2 requiredWriter=0.4.0`
|
|
6751
|
+
);
|
|
6752
|
+
}
|
|
6753
|
+
return { planning: 2 };
|
|
6754
|
+
}
|
|
6755
|
+
function parseVersion(value, label) {
|
|
6756
|
+
if (typeof value !== "string" || !VERSION_PATTERN.test(value)) {
|
|
6757
|
+
throw new Error(`schema manifest ${label} must be a semantic version`);
|
|
6758
|
+
}
|
|
6759
|
+
return value;
|
|
6760
|
+
}
|
|
6761
|
+
function parseTimestampOrNull3(value, label) {
|
|
6762
|
+
if (value === null) return null;
|
|
6763
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
6764
|
+
throw new Error(
|
|
6765
|
+
`schema manifest ${label} must be an ISO timestamp or null`
|
|
6766
|
+
);
|
|
6767
|
+
}
|
|
6768
|
+
return value;
|
|
6769
|
+
}
|
|
6770
|
+
function parseLegacyBaseline(value) {
|
|
6771
|
+
if (value === null) return null;
|
|
6772
|
+
assertRecord(value, "schema manifest legacyBaseline");
|
|
6773
|
+
assertKnownKeys(
|
|
6774
|
+
value,
|
|
6775
|
+
["stateDigest", "workflowIndexDigest"],
|
|
6776
|
+
"schema manifest legacyBaseline"
|
|
6777
|
+
);
|
|
6778
|
+
if (typeof value.stateDigest !== "string" || !DIGEST_PATTERN13.test(value.stateDigest) || typeof value.workflowIndexDigest !== "string" || !DIGEST_PATTERN13.test(value.workflowIndexDigest)) {
|
|
6779
|
+
throw new Error(
|
|
6780
|
+
"schema manifest legacyBaseline must contain sha256 digests"
|
|
6781
|
+
);
|
|
6782
|
+
}
|
|
6783
|
+
return {
|
|
6784
|
+
stateDigest: value.stateDigest,
|
|
6785
|
+
workflowIndexDigest: value.workflowIndexDigest
|
|
6786
|
+
};
|
|
6787
|
+
}
|
|
6788
|
+
function parseManagedAdapters(value) {
|
|
6789
|
+
assertRecord(value, "schema manifest managedAdapters");
|
|
6790
|
+
assertKnownKeys(value, MANAGED_ADAPTERS, "schema manifest managedAdapters");
|
|
6791
|
+
const adapters = {};
|
|
6792
|
+
for (const adapter of MANAGED_ADAPTERS) {
|
|
6793
|
+
const version = value[adapter];
|
|
6794
|
+
if (version === void 0) continue;
|
|
6795
|
+
if (typeof version !== "string" || !version.trim()) {
|
|
6796
|
+
throw new Error(
|
|
6797
|
+
`schema manifest managedAdapters.${adapter} must be a non-empty version`
|
|
6798
|
+
);
|
|
6799
|
+
}
|
|
6800
|
+
adapters[adapter] = version;
|
|
6801
|
+
}
|
|
6802
|
+
return adapters;
|
|
6803
|
+
}
|
|
5793
6804
|
function parseUlidOrNull8(value, label) {
|
|
5794
6805
|
if (value === null) return null;
|
|
5795
|
-
assertUlid(value, label);
|
|
6806
|
+
assertUlid(value, `schema manifest ${label}`);
|
|
6807
|
+
return value;
|
|
6808
|
+
}
|
|
6809
|
+
function assertManifestStateShape(manifest) {
|
|
6810
|
+
if (manifest.activationState === "initializing" && manifest.legacyBaseline !== null) {
|
|
6811
|
+
throw new Error(
|
|
6812
|
+
"greenfield initializing manifests must not have a legacy baseline"
|
|
6813
|
+
);
|
|
6814
|
+
}
|
|
6815
|
+
if ((manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.legacyBaseline === null) {
|
|
6816
|
+
throw new Error(
|
|
6817
|
+
`${manifest.activationState} manifests require a legacy baseline`
|
|
6818
|
+
);
|
|
6819
|
+
}
|
|
6820
|
+
if (manifest.activationState === "v3_active" && manifest.activatedAt === null) {
|
|
6821
|
+
throw new Error("v3_active manifests require activatedAt");
|
|
6822
|
+
}
|
|
6823
|
+
if ((manifest.activationState === "initializing" || manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.activatedAt !== null) {
|
|
6824
|
+
throw new Error(
|
|
6825
|
+
`${manifest.activationState} manifests must not have activatedAt`
|
|
6826
|
+
);
|
|
6827
|
+
}
|
|
6828
|
+
}
|
|
6829
|
+
function managedAdapterInventoriesMatch(left, right) {
|
|
6830
|
+
const leftKeys = managedAdapterNames(left);
|
|
6831
|
+
const rightKeys = managedAdapterNames(right);
|
|
6832
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
6833
|
+
(adapter, index) => adapter === rightKeys[index] && left[adapter] === right[adapter]
|
|
6834
|
+
);
|
|
6835
|
+
}
|
|
6836
|
+
function compareVersions(left, right) {
|
|
6837
|
+
const [leftCore = "", leftPrerelease] = left.split("-", 2);
|
|
6838
|
+
const [rightCore = "", rightPrerelease] = right.split("-", 2);
|
|
6839
|
+
const leftParts = leftCore.split(".").map(Number);
|
|
6840
|
+
const rightParts = rightCore.split(".").map(Number);
|
|
6841
|
+
for (let index = 0; index < 3; index += 1) {
|
|
6842
|
+
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
6843
|
+
if (delta !== 0) return delta;
|
|
6844
|
+
}
|
|
6845
|
+
if (leftPrerelease === rightPrerelease) return 0;
|
|
6846
|
+
if (leftPrerelease === void 0) return 1;
|
|
6847
|
+
if (rightPrerelease === void 0) return -1;
|
|
6848
|
+
return comparePrerelease(leftPrerelease, rightPrerelease);
|
|
6849
|
+
}
|
|
6850
|
+
function comparePrerelease(left, right) {
|
|
6851
|
+
const leftParts = left.split(".");
|
|
6852
|
+
const rightParts = right.split(".");
|
|
6853
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
6854
|
+
const leftPart = leftParts[index];
|
|
6855
|
+
const rightPart = rightParts[index];
|
|
6856
|
+
if (leftPart === void 0) return -1;
|
|
6857
|
+
if (rightPart === void 0) return 1;
|
|
6858
|
+
if (leftPart === rightPart) continue;
|
|
6859
|
+
const leftNumeric = /^\d+$/.test(leftPart);
|
|
6860
|
+
const rightNumeric = /^\d+$/.test(rightPart);
|
|
6861
|
+
if (leftNumeric && rightNumeric)
|
|
6862
|
+
return Number(leftPart) - Number(rightPart);
|
|
6863
|
+
if (leftNumeric) return -1;
|
|
6864
|
+
if (rightNumeric) return 1;
|
|
6865
|
+
return leftPart.localeCompare(rightPart, "en");
|
|
6866
|
+
}
|
|
6867
|
+
return 0;
|
|
6868
|
+
}
|
|
6869
|
+
function sameLegacyBaseline(left, right) {
|
|
6870
|
+
return left === right || left !== null && right !== null && left.stateDigest === right.stateDigest && left.workflowIndexDigest === right.workflowIndexDigest;
|
|
6871
|
+
}
|
|
6872
|
+
function allowedManifestTransitions(from) {
|
|
6873
|
+
switch (from) {
|
|
6874
|
+
case "initializing":
|
|
6875
|
+
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6876
|
+
case "dual_read":
|
|
6877
|
+
return /* @__PURE__ */ new Set(["activating", "repair_required"]);
|
|
6878
|
+
case "activating":
|
|
6879
|
+
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6880
|
+
case "repair_required":
|
|
6881
|
+
return /* @__PURE__ */ new Set(["v3_active"]);
|
|
6882
|
+
case "v3_active":
|
|
6883
|
+
return /* @__PURE__ */ new Set(["repair_required"]);
|
|
6884
|
+
}
|
|
6885
|
+
}
|
|
6886
|
+
|
|
6887
|
+
// src/context/privacy-policy.ts
|
|
6888
|
+
var PRIVACY_MIN_VERSION = "0.6.5";
|
|
6889
|
+
var PRIVACY_POLICY_FILE = "shared/context/privacy-policy.json";
|
|
6890
|
+
var PRIVACY_EXCLUSIONS_FILE = "shared/context/privacy-exclusions.json";
|
|
6891
|
+
function parsePrivacyPolicyCandidate(value) {
|
|
6892
|
+
assertRecord(value, "privacy policy candidate");
|
|
6893
|
+
assertKnownKeys(
|
|
6894
|
+
value,
|
|
6895
|
+
["schemaVersion", "enabled", "rulesetVersion", "enabledRuleIds"],
|
|
6896
|
+
"privacy policy candidate"
|
|
6897
|
+
);
|
|
6898
|
+
if (value.schemaVersion !== 1 || typeof value.enabled !== "boolean")
|
|
6899
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
6900
|
+
if (value.rulesetVersion !== RULESET_VERSION)
|
|
6901
|
+
throw new Error("MANCODE_PRIVACY_RULESET_UNSUPPORTED");
|
|
6902
|
+
if (!Array.isArray(value.enabledRuleIds) || value.enabledRuleIds.some(
|
|
6903
|
+
(id) => typeof id !== "string" || !DEFAULT_RULE_IDS.includes(id)
|
|
6904
|
+
) || new Set(value.enabledRuleIds).size !== value.enabledRuleIds.length)
|
|
6905
|
+
throw new Error("MANCODE_PRIVACY_RULE_UNSUPPORTED");
|
|
6906
|
+
if (value.enabled && value.enabledRuleIds.length === 0)
|
|
6907
|
+
throw new Error("MANCODE_PRIVACY_EMPTY_RULESET");
|
|
6908
|
+
return {
|
|
6909
|
+
schemaVersion: 1,
|
|
6910
|
+
enabled: value.enabled,
|
|
6911
|
+
rulesetVersion: value.rulesetVersion,
|
|
6912
|
+
enabledRuleIds: [...value.enabledRuleIds].sort()
|
|
6913
|
+
};
|
|
6914
|
+
}
|
|
6915
|
+
function parsePrivacyPolicy(value) {
|
|
6916
|
+
assertRecord(value, "privacy policy");
|
|
6917
|
+
assertKnownKeys(
|
|
6918
|
+
value,
|
|
6919
|
+
[
|
|
6920
|
+
"schemaVersion",
|
|
6921
|
+
"revision",
|
|
6922
|
+
"workspaceId",
|
|
6923
|
+
"enabled",
|
|
6924
|
+
"rulesetVersion",
|
|
6925
|
+
"enabledRuleIds",
|
|
6926
|
+
"targets",
|
|
6927
|
+
"exclusions",
|
|
6928
|
+
"lastOperationId",
|
|
6929
|
+
"updatedAt"
|
|
6930
|
+
],
|
|
6931
|
+
"privacy policy"
|
|
6932
|
+
);
|
|
6933
|
+
const candidate = parsePrivacyPolicyCandidate({
|
|
6934
|
+
schemaVersion: value.schemaVersion,
|
|
6935
|
+
enabled: value.enabled,
|
|
6936
|
+
rulesetVersion: value.rulesetVersion,
|
|
6937
|
+
enabledRuleIds: value.enabledRuleIds
|
|
6938
|
+
});
|
|
6939
|
+
assertUlid(value.workspaceId, "privacy workspaceId");
|
|
6940
|
+
assertUlid(value.lastOperationId, "privacy lastOperationId");
|
|
6941
|
+
if (!Array.isArray(value.targets) || value.targets.length !== 2 || value.targets[0] !== "shared_write" || value.targets[1] !== "context_output")
|
|
6942
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
6943
|
+
assertRecord(value.exclusions, "privacy exclusions reference");
|
|
6944
|
+
assertKnownKeys(
|
|
6945
|
+
value.exclusions,
|
|
6946
|
+
["revision", "digest"],
|
|
6947
|
+
"privacy exclusions reference"
|
|
6948
|
+
);
|
|
6949
|
+
return {
|
|
6950
|
+
...candidate,
|
|
6951
|
+
revision: revision(value.revision),
|
|
6952
|
+
workspaceId: value.workspaceId,
|
|
6953
|
+
targets: ["shared_write", "context_output"],
|
|
6954
|
+
exclusions: {
|
|
6955
|
+
revision: revision(value.exclusions.revision),
|
|
6956
|
+
digest: digest(value.exclusions.digest)
|
|
6957
|
+
},
|
|
6958
|
+
lastOperationId: value.lastOperationId,
|
|
6959
|
+
updatedAt: timestamp(value.updatedAt)
|
|
6960
|
+
};
|
|
6961
|
+
}
|
|
6962
|
+
function parsePrivacyExclusions(value) {
|
|
6963
|
+
assertRecord(value, "privacy exclusions");
|
|
6964
|
+
assertKnownKeys(
|
|
6965
|
+
value,
|
|
6966
|
+
[
|
|
6967
|
+
"schemaVersion",
|
|
6968
|
+
"revision",
|
|
6969
|
+
"workspaceId",
|
|
6970
|
+
"entries",
|
|
6971
|
+
"lastOperationId",
|
|
6972
|
+
"updatedAt"
|
|
6973
|
+
],
|
|
6974
|
+
"privacy exclusions"
|
|
6975
|
+
);
|
|
6976
|
+
if (value.schemaVersion !== 1 || !Array.isArray(value.entries))
|
|
6977
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSIONS_INVALID");
|
|
6978
|
+
assertUlid(value.workspaceId, "privacy exclusions workspaceId");
|
|
6979
|
+
assertUlid(value.lastOperationId, "privacy exclusions operationId");
|
|
6980
|
+
const entries = value.entries.map((entry) => {
|
|
6981
|
+
assertRecord(entry, "privacy exclusion");
|
|
6982
|
+
assertKnownKeys(
|
|
6983
|
+
entry,
|
|
6984
|
+
["kind", "relativePath", "entityDigest"],
|
|
6985
|
+
"privacy exclusion"
|
|
6986
|
+
);
|
|
6987
|
+
const ulid = "[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}";
|
|
6988
|
+
const pattern = entry.kind === "confirmed_decision" ? new RegExp(`^shared/memory/decisions/${ulid}\\.json$`) : entry.kind === "checkpoint" ? new RegExp(`^shared/workflows/${ulid}/checkpoints/${ulid}\\.json$`) : null;
|
|
6989
|
+
if (pattern === null || typeof entry.relativePath !== "string" || !pattern.test(entry.relativePath))
|
|
6990
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSION_PATH_INVALID");
|
|
6991
|
+
return {
|
|
6992
|
+
kind: entry.kind,
|
|
6993
|
+
relativePath: entry.relativePath,
|
|
6994
|
+
entityDigest: digest(entry.entityDigest)
|
|
6995
|
+
};
|
|
6996
|
+
});
|
|
6997
|
+
if (new Set(entries.map((entry) => entry.relativePath)).size !== entries.length)
|
|
6998
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSIONS_DUPLICATE");
|
|
6999
|
+
return {
|
|
7000
|
+
schemaVersion: 1,
|
|
7001
|
+
revision: revision(value.revision),
|
|
7002
|
+
workspaceId: value.workspaceId,
|
|
7003
|
+
entries: entries.sort(
|
|
7004
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath, "en")
|
|
7005
|
+
),
|
|
7006
|
+
lastOperationId: value.lastOperationId,
|
|
7007
|
+
updatedAt: timestamp(value.updatedAt)
|
|
7008
|
+
};
|
|
7009
|
+
}
|
|
7010
|
+
function createInitialPrivacyPolicy(input) {
|
|
7011
|
+
const exclusions = parsePrivacyExclusions({
|
|
7012
|
+
schemaVersion: 1,
|
|
7013
|
+
revision: 1,
|
|
7014
|
+
workspaceId: input.workspaceId,
|
|
7015
|
+
entries: [],
|
|
7016
|
+
lastOperationId: input.operationId,
|
|
7017
|
+
updatedAt: input.now
|
|
7018
|
+
});
|
|
7019
|
+
const policy = parsePrivacyPolicy({
|
|
7020
|
+
schemaVersion: 1,
|
|
7021
|
+
revision: 1,
|
|
7022
|
+
workspaceId: input.workspaceId,
|
|
7023
|
+
enabled: input.enabled ?? true,
|
|
7024
|
+
rulesetVersion: RULESET_VERSION,
|
|
7025
|
+
enabledRuleIds: [...DEFAULT_RULE_IDS],
|
|
7026
|
+
targets: ["shared_write", "context_output"],
|
|
7027
|
+
exclusions: { revision: 1, digest: digestCanonicalJson(exclusions) },
|
|
7028
|
+
lastOperationId: input.operationId,
|
|
7029
|
+
updatedAt: input.now
|
|
7030
|
+
});
|
|
7031
|
+
return { policy, exclusions, digest: digestCanonicalJson(policy) };
|
|
7032
|
+
}
|
|
7033
|
+
function assertPrivacyPolicyTransition(previous, next) {
|
|
7034
|
+
if (next.revision !== (previous?.revision ?? 0) + 1 || previous !== null && (previous.workspaceId !== next.workspaceId || previous.lastOperationId === next.lastOperationId))
|
|
7035
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REVISION_CONFLICT");
|
|
7036
|
+
}
|
|
7037
|
+
function assertPrivacyExclusionsTransition(previous, next) {
|
|
7038
|
+
if (next.revision !== (previous?.revision ?? 0) + 1 || previous !== null && previous.workspaceId !== next.workspaceId)
|
|
7039
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REVISION_CONFLICT");
|
|
7040
|
+
if (previous?.entries.some(
|
|
7041
|
+
(entry) => !next.entries.some(
|
|
7042
|
+
(nextEntry) => nextEntry.relativePath === entry.relativePath && nextEntry.entityDigest === entry.entityDigest
|
|
7043
|
+
)
|
|
7044
|
+
))
|
|
7045
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSION_REMOVAL_FORBIDDEN");
|
|
7046
|
+
}
|
|
7047
|
+
async function readPrivacyPolicySnapshot(root, manifest) {
|
|
7048
|
+
await assertNoPendingPrivacyPolicyOperation(root);
|
|
7049
|
+
const current = manifest ?? parseSchemaManifest(
|
|
7050
|
+
JSON.parse(await readPrivacyAuthorityFile(root, "schema.json"))
|
|
7051
|
+
);
|
|
7052
|
+
if (current.manifestVersion !== 3) return null;
|
|
7053
|
+
const [policy, exclusions, config] = await Promise.all([
|
|
7054
|
+
readPrivacyAuthorityFile(root, PRIVACY_POLICY_FILE).then(
|
|
7055
|
+
(content) => parsePrivacyPolicy(JSON.parse(content))
|
|
7056
|
+
),
|
|
7057
|
+
readPrivacyAuthorityFile(root, PRIVACY_EXCLUSIONS_FILE).then(
|
|
7058
|
+
(content) => parsePrivacyExclusions(JSON.parse(content))
|
|
7059
|
+
),
|
|
7060
|
+
readPrivacyAuthorityFile(root, "shared/config.json").then(
|
|
7061
|
+
(content) => parseProjectConfig(JSON.parse(content))
|
|
7062
|
+
)
|
|
7063
|
+
]);
|
|
7064
|
+
const policyDigest = digestCanonicalJson(policy);
|
|
7065
|
+
if (policy.workspaceId !== config.workspaceId)
|
|
7066
|
+
throw new Error("MANCODE_PRIVACY_POLICY_WORKSPACE_MISMATCH");
|
|
7067
|
+
if (current.privacyPolicy.revision !== policy.revision || current.privacyPolicy.digest !== policyDigest || policy.workspaceId !== exclusions.workspaceId || policy.exclusions.revision !== exclusions.revision || policy.exclusions.digest !== digestCanonicalJson(exclusions))
|
|
7068
|
+
throw new Error("MANCODE_PRIVACY_POLICY_DIGEST_MISMATCH");
|
|
7069
|
+
return { policy, exclusions, digest: policyDigest };
|
|
7070
|
+
}
|
|
7071
|
+
async function assertNoPendingPrivacyPolicyOperation(root) {
|
|
7072
|
+
const relative = "local/runtime/operations";
|
|
7073
|
+
let entries;
|
|
7074
|
+
try {
|
|
7075
|
+
const directory = path6.join(root, ".mancode", relative);
|
|
7076
|
+
const stat2 = await lstat2(directory);
|
|
7077
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
7078
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7079
|
+
entries = await readdir3(directory);
|
|
7080
|
+
} catch (error) {
|
|
7081
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
7082
|
+
return;
|
|
7083
|
+
throw error;
|
|
7084
|
+
}
|
|
7085
|
+
for (const entry of entries) {
|
|
7086
|
+
if (!/^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}\.json$/.test(entry)) continue;
|
|
7087
|
+
const journal = JSON.parse(
|
|
7088
|
+
await readPrivacyAuthorityFile(root, `${relative}/${entry}`)
|
|
7089
|
+
);
|
|
7090
|
+
if (journal.type === "privacy_policy_update" && journal.state !== "committed" && journal.state !== "aborted")
|
|
7091
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REPAIR_REQUIRED");
|
|
7092
|
+
}
|
|
7093
|
+
}
|
|
7094
|
+
async function readPrivacyPolicyStatus(root) {
|
|
7095
|
+
try {
|
|
7096
|
+
const snapshot = await readPrivacyPolicySnapshot(root);
|
|
7097
|
+
return {
|
|
7098
|
+
schemaVersion: 1,
|
|
7099
|
+
state: snapshot === null ? "unconfigured" : snapshot.policy.enabled ? "enabled" : "disabled",
|
|
7100
|
+
enabled: snapshot?.policy.enabled ?? false,
|
|
7101
|
+
revision: snapshot?.policy.revision ?? 0,
|
|
7102
|
+
digest: snapshot?.digest ?? null,
|
|
7103
|
+
rulesetVersion: snapshot?.policy.rulesetVersion ?? RULESET_VERSION,
|
|
7104
|
+
excludedEntities: snapshot?.exclusions.entries.length ?? 0,
|
|
7105
|
+
scope: "project",
|
|
7106
|
+
error: null
|
|
7107
|
+
};
|
|
7108
|
+
} catch {
|
|
7109
|
+
return {
|
|
7110
|
+
schemaVersion: 1,
|
|
7111
|
+
state: "error",
|
|
7112
|
+
enabled: null,
|
|
7113
|
+
revision: null,
|
|
7114
|
+
digest: null,
|
|
7115
|
+
rulesetVersion: null,
|
|
7116
|
+
excludedEntities: null,
|
|
7117
|
+
scope: "project",
|
|
7118
|
+
error: "MANCODE_PRIVACY_POLICY_UNAVAILABLE"
|
|
7119
|
+
};
|
|
7120
|
+
}
|
|
7121
|
+
}
|
|
7122
|
+
async function readPrivacyAuthorityFile(root, relative) {
|
|
7123
|
+
let current = path6.join(path6.resolve(root), ".mancode");
|
|
7124
|
+
for (const part of relative.split("/")) {
|
|
7125
|
+
const parent = await lstat2(current);
|
|
7126
|
+
if (!parent.isDirectory() || parent.isSymbolicLink())
|
|
7127
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7128
|
+
current = path6.join(current, part);
|
|
7129
|
+
}
|
|
7130
|
+
const stat2 = await lstat2(current);
|
|
7131
|
+
if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.size > 8 * 1024 * 1024)
|
|
7132
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7133
|
+
return readFile5(current, "utf8");
|
|
7134
|
+
}
|
|
7135
|
+
function revision(value) {
|
|
7136
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
7137
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7138
|
+
return value;
|
|
7139
|
+
}
|
|
7140
|
+
function digest(value) {
|
|
7141
|
+
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value))
|
|
7142
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7143
|
+
return value;
|
|
7144
|
+
}
|
|
7145
|
+
function timestamp(value) {
|
|
7146
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
|
|
7147
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
5796
7148
|
return value;
|
|
5797
7149
|
}
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
7150
|
+
|
|
7151
|
+
// src/context/privacy-guard.ts
|
|
7152
|
+
function containsEnhancedSensitiveText(value, ruleIds) {
|
|
7153
|
+
const stack = [[value]];
|
|
7154
|
+
let visited = 0;
|
|
7155
|
+
while (stack.length > 0) {
|
|
7156
|
+
if (++visited > 1e5) throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
7157
|
+
const [item, key] = stack.pop();
|
|
7158
|
+
if (typeof item === "string") {
|
|
7159
|
+
const scan = scanSensitiveText(
|
|
7160
|
+
key === void 0 || item.length === 0 ? item : `${key}: ${item}`,
|
|
7161
|
+
ruleIds
|
|
7162
|
+
);
|
|
7163
|
+
if (scan.status !== "complete")
|
|
7164
|
+
throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
7165
|
+
if (scan.findings.length > 0) return true;
|
|
7166
|
+
} else if (Array.isArray(item)) {
|
|
7167
|
+
stack.push(...item.map((content) => [content, key]));
|
|
7168
|
+
} else if (item !== null && typeof item === "object") {
|
|
7169
|
+
for (const [key2, content] of Object.entries(item))
|
|
7170
|
+
stack.push([key2], [content, key2]);
|
|
7171
|
+
}
|
|
7172
|
+
}
|
|
7173
|
+
return false;
|
|
7174
|
+
}
|
|
7175
|
+
function isPrivacyExcluded(snapshot, value) {
|
|
7176
|
+
if (snapshot === null || snapshot === void 0 || snapshot.exclusions.entries.length === 0)
|
|
7177
|
+
return false;
|
|
7178
|
+
const entityDigest = digestCanonicalJson(value);
|
|
7179
|
+
return snapshot.exclusions.entries.some(
|
|
7180
|
+
(entry) => entry.entityDigest === entityDigest
|
|
7181
|
+
);
|
|
7182
|
+
}
|
|
7183
|
+
function assertPrivacyValueAllowed(snapshot, value) {
|
|
7184
|
+
if (isPrivacyExcluded(snapshot, value))
|
|
7185
|
+
throw new Error("MANCODE_PRIVACY_ENTITY_EXCLUDED");
|
|
7186
|
+
if (snapshot?.policy.enabled && containsEnhancedSensitiveText(value, snapshot.policy.enabledRuleIds))
|
|
7187
|
+
throw new Error("MANCODE_PRIVACY_BLOCKED");
|
|
7188
|
+
}
|
|
7189
|
+
async function assertSharedPrivacyValue(root, value) {
|
|
7190
|
+
assertPrivacyValueAllowed(await readSharedPrivacySnapshot(root), value);
|
|
7191
|
+
}
|
|
7192
|
+
async function readSharedPrivacySnapshot(root) {
|
|
7193
|
+
if (!await hasProjectAuthority(root)) return null;
|
|
7194
|
+
return readPrivacyPolicySnapshot(root);
|
|
7195
|
+
}
|
|
7196
|
+
async function acquireSharedPrivacyWriteBarrier(root, operationId) {
|
|
7197
|
+
if (!await hasProjectAuthority(root)) return null;
|
|
7198
|
+
const runtime = await readProjectRuntimeContext(root);
|
|
7199
|
+
const deadline = performance.now() + 5e3;
|
|
7200
|
+
for (; ; ) {
|
|
7201
|
+
try {
|
|
7202
|
+
return await acquireProjectWriteBarrier(runtime, operationId, /* @__PURE__ */ new Date());
|
|
7203
|
+
} catch (error) {
|
|
7204
|
+
if (!(error instanceof Error) || error.message !== "MANCODE_LOCK_HELD" || performance.now() >= deadline)
|
|
7205
|
+
throw error;
|
|
7206
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
7207
|
+
}
|
|
5802
7208
|
}
|
|
5803
|
-
return value;
|
|
5804
7209
|
}
|
|
5805
|
-
function
|
|
5806
|
-
|
|
5807
|
-
|
|
7210
|
+
async function withSharedPrivacyWrite(root, operationId, value, write2) {
|
|
7211
|
+
await assertSharedPrivacyValue(root, value);
|
|
7212
|
+
const barrier = await acquireSharedPrivacyWriteBarrier(root, operationId);
|
|
7213
|
+
try {
|
|
7214
|
+
await assertSharedPrivacyValue(root, value);
|
|
7215
|
+
return await write2();
|
|
7216
|
+
} finally {
|
|
7217
|
+
await barrier?.release();
|
|
7218
|
+
}
|
|
7219
|
+
}
|
|
7220
|
+
async function assertSharedTaskWriteAtRoot(taskRoot, fileName, content) {
|
|
7221
|
+
const workflows = path7.dirname(taskRoot);
|
|
7222
|
+
const shared = path7.dirname(workflows);
|
|
7223
|
+
const authority = path7.dirname(shared);
|
|
7224
|
+
if (path7.basename(workflows) !== "workflows" || path7.basename(shared) !== "shared" || path7.basename(authority) !== ".mancode")
|
|
7225
|
+
return;
|
|
7226
|
+
await assertSharedPrivacyValue(
|
|
7227
|
+
path7.dirname(authority),
|
|
7228
|
+
privacyContentValue(fileName, content)
|
|
7229
|
+
);
|
|
7230
|
+
}
|
|
7231
|
+
function assertPrivacyRecoveryActionsAllowed(snapshot, actions) {
|
|
7232
|
+
for (const action of actions) {
|
|
7233
|
+
switch (action.kind) {
|
|
7234
|
+
case "task_authority_file":
|
|
7235
|
+
if (action.taskRef.namespace === "shared")
|
|
7236
|
+
assertPrivacyValueAllowed(
|
|
7237
|
+
snapshot,
|
|
7238
|
+
privacyContentValue(action.fileName, action.targetContent)
|
|
7239
|
+
);
|
|
7240
|
+
break;
|
|
7241
|
+
case "workflow_task_directory":
|
|
7242
|
+
case "migration_task_directory":
|
|
7243
|
+
if (action.taskRef.namespace === "shared") {
|
|
7244
|
+
for (const file of action.files)
|
|
7245
|
+
assertPrivacyValueAllowed(
|
|
7246
|
+
snapshot,
|
|
7247
|
+
privacyContentValue(file.fileName, file.content)
|
|
7248
|
+
);
|
|
7249
|
+
if (action.kind === "migration_task_directory")
|
|
7250
|
+
for (const report of action.reports)
|
|
7251
|
+
assertPrivacyValueAllowed(snapshot, report.content);
|
|
7252
|
+
}
|
|
7253
|
+
break;
|
|
7254
|
+
case "task_archive":
|
|
7255
|
+
if (action.taskRef.namespace === "shared") {
|
|
7256
|
+
assertPrivacyValueAllowed(
|
|
7257
|
+
snapshot,
|
|
7258
|
+
privacyContentValue(
|
|
7259
|
+
"requirements.json",
|
|
7260
|
+
action.requirementsContent
|
|
7261
|
+
)
|
|
7262
|
+
);
|
|
7263
|
+
if (action.planContent !== null)
|
|
7264
|
+
assertPrivacyValueAllowed(snapshot, action.planContent);
|
|
7265
|
+
}
|
|
7266
|
+
break;
|
|
7267
|
+
case "checkpoint":
|
|
7268
|
+
if (action.checkpoint.taskRef.namespace === "shared")
|
|
7269
|
+
assertPrivacyValueAllowed(snapshot, action.checkpoint);
|
|
7270
|
+
break;
|
|
7271
|
+
case "claim":
|
|
7272
|
+
if (action.claim.taskRef.namespace === "shared")
|
|
7273
|
+
assertPrivacyValueAllowed(snapshot, action.claim);
|
|
7274
|
+
break;
|
|
7275
|
+
case "handoff":
|
|
7276
|
+
if (action.handoff.taskRef.namespace === "shared")
|
|
7277
|
+
assertPrivacyValueAllowed(snapshot, action.handoff);
|
|
7278
|
+
break;
|
|
7279
|
+
default:
|
|
7280
|
+
break;
|
|
7281
|
+
}
|
|
5808
7282
|
}
|
|
5809
|
-
|
|
7283
|
+
}
|
|
7284
|
+
async function hasProjectAuthority(root) {
|
|
7285
|
+
try {
|
|
7286
|
+
const stat2 = await lstat3(path7.join(root, ".mancode", "schema.json"));
|
|
7287
|
+
if (!stat2.isFile() || stat2.isSymbolicLink())
|
|
7288
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7289
|
+
return true;
|
|
7290
|
+
} catch (error) {
|
|
7291
|
+
if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
|
|
7292
|
+
throw error;
|
|
7293
|
+
try {
|
|
7294
|
+
await lstat3(path7.join(root, ".mancode", "shared", "config.json"));
|
|
7295
|
+
} catch (configError) {
|
|
7296
|
+
if (configError !== null && typeof configError === "object" && "code" in configError && configError.code === "ENOENT")
|
|
7297
|
+
return false;
|
|
7298
|
+
throw configError;
|
|
7299
|
+
}
|
|
7300
|
+
throw new Error("MANCODE_PRIVACY_POLICY_UNAVAILABLE");
|
|
7301
|
+
}
|
|
7302
|
+
}
|
|
7303
|
+
function privacyContentValue(fileName, content) {
|
|
7304
|
+
if (!fileName.endsWith(".json")) return content;
|
|
7305
|
+
try {
|
|
7306
|
+
return JSON.parse(content);
|
|
7307
|
+
} catch {
|
|
7308
|
+
throw new Error("MANCODE_PRIVACY_SHARED_JSON_INVALID");
|
|
7309
|
+
}
|
|
7310
|
+
}
|
|
7311
|
+
async function scanPrivacyActivation(root, ruleIds) {
|
|
7312
|
+
const files = await collectSharedFiles(root, "shared");
|
|
7313
|
+
const digests = [];
|
|
7314
|
+
const blockers = [];
|
|
7315
|
+
const exclusions = [];
|
|
7316
|
+
let scannedFiles = 0;
|
|
7317
|
+
for (const relative of files) {
|
|
7318
|
+
if (relative === PRIVACY_POLICY_FILE || relative === PRIVACY_EXCLUSIONS_FILE)
|
|
7319
|
+
continue;
|
|
7320
|
+
const content = await readPrivacyAuthorityFile(root, relative);
|
|
7321
|
+
digests.push({ path: relative, digest: digestCanonicalJson(content) });
|
|
7322
|
+
const fileIndex = scannedFiles++;
|
|
7323
|
+
let value;
|
|
7324
|
+
try {
|
|
7325
|
+
value = privacyContentValue(relative, content);
|
|
7326
|
+
} catch {
|
|
7327
|
+
blockers.push({ fileIndex, reason: "invalid_json" });
|
|
7328
|
+
continue;
|
|
7329
|
+
}
|
|
7330
|
+
if (!containsEnhancedSensitiveText(value, ruleIds)) continue;
|
|
7331
|
+
const ulid = "[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}";
|
|
7332
|
+
const kind = new RegExp(`^shared/memory/decisions/${ulid}\\.json$`).test(
|
|
7333
|
+
relative
|
|
7334
|
+
) ? "confirmed_decision" : new RegExp(
|
|
7335
|
+
`^shared/workflows/${ulid}/checkpoints/${ulid}\\.json$`
|
|
7336
|
+
).test(relative) ? "checkpoint" : null;
|
|
7337
|
+
if (kind !== null)
|
|
7338
|
+
exclusions.push({
|
|
7339
|
+
kind,
|
|
7340
|
+
relativePath: relative,
|
|
7341
|
+
entityDigest: digestCanonicalJson(value)
|
|
7342
|
+
});
|
|
7343
|
+
else blockers.push({ fileIndex, reason: "sensitive_content" });
|
|
7344
|
+
}
|
|
7345
|
+
return {
|
|
7346
|
+
fingerprint: digestCanonicalJson(digests),
|
|
7347
|
+
scannedFiles,
|
|
7348
|
+
blockers,
|
|
7349
|
+
exclusions
|
|
7350
|
+
};
|
|
7351
|
+
}
|
|
7352
|
+
async function collectSharedFiles(root, relative) {
|
|
7353
|
+
const target = path7.join(root, ".mancode", relative);
|
|
7354
|
+
const stat2 = await lstat3(target);
|
|
7355
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
7356
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7357
|
+
const files = [];
|
|
7358
|
+
for (const entry of (await readdir4(target, { withFileTypes: true })).sort(
|
|
7359
|
+
(a, b) => a.name.localeCompare(b.name, "en")
|
|
7360
|
+
)) {
|
|
7361
|
+
if (entry.isSymbolicLink()) throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7362
|
+
const child = `${relative}/${entry.name}`;
|
|
7363
|
+
if (entry.isDirectory())
|
|
7364
|
+
files.push(...await collectSharedFiles(root, child));
|
|
7365
|
+
else if (entry.isFile()) files.push(child);
|
|
7366
|
+
else throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7367
|
+
if (files.length > 1e5)
|
|
7368
|
+
throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
7369
|
+
}
|
|
7370
|
+
return files;
|
|
5810
7371
|
}
|
|
5811
7372
|
|
|
5812
7373
|
// src/context/confirmed-decision.ts
|
|
5813
|
-
import { lstat as lstat2, mkdir as mkdir2, readFile as readFile3, readdir as readdir2, writeFile as writeFile2 } from "fs/promises";
|
|
5814
|
-
import path5 from "path";
|
|
5815
7374
|
var DECISION_FILENAME = /^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}\.json$/;
|
|
5816
7375
|
function createConfirmedDecision(input) {
|
|
5817
7376
|
assertUlid(input.decisionId, "confirmed decision decisionId");
|
|
@@ -5891,8 +7450,8 @@ function confirmedDecisionDigest(decision) {
|
|
|
5891
7450
|
return digestCanonicalJson(parseConfirmedDecision(decision));
|
|
5892
7451
|
}
|
|
5893
7452
|
function confirmedDecisionDirectory(projectRoot) {
|
|
5894
|
-
return
|
|
5895
|
-
|
|
7453
|
+
return path8.join(
|
|
7454
|
+
path8.resolve(projectRoot),
|
|
5896
7455
|
".mancode",
|
|
5897
7456
|
"shared",
|
|
5898
7457
|
"memory",
|
|
@@ -5901,430 +7460,162 @@ function confirmedDecisionDirectory(projectRoot) {
|
|
|
5901
7460
|
}
|
|
5902
7461
|
function confirmedDecisionPath(projectRoot, decisionId) {
|
|
5903
7462
|
assertUlid(decisionId, "confirmed decision decisionId");
|
|
5904
|
-
return
|
|
7463
|
+
return path8.join(
|
|
5905
7464
|
confirmedDecisionDirectory(projectRoot),
|
|
5906
7465
|
`${decisionId}.json`
|
|
5907
7466
|
);
|
|
5908
7467
|
}
|
|
5909
7468
|
async function publishConfirmedDecision(projectRoot, decision) {
|
|
7469
|
+
const parsed = parseConfirmedDecision(decision);
|
|
7470
|
+
return withSharedPrivacyWrite(
|
|
7471
|
+
projectRoot,
|
|
7472
|
+
parsed.operationId,
|
|
7473
|
+
parsed,
|
|
7474
|
+
() => publishConfirmedDecisionUnlocked(projectRoot, parsed)
|
|
7475
|
+
);
|
|
7476
|
+
}
|
|
7477
|
+
async function publishConfirmedDecisionUnlocked(projectRoot, decision) {
|
|
5910
7478
|
const parsed = parseConfirmedDecision(decision);
|
|
5911
7479
|
const directory = confirmedDecisionDirectory(projectRoot);
|
|
5912
7480
|
await ensureSafeDirectory(projectRoot, directory);
|
|
5913
7481
|
const target = confirmedDecisionPath(projectRoot, parsed.decisionId);
|
|
5914
7482
|
try {
|
|
5915
|
-
await
|
|
7483
|
+
await writeFile4(target, `${JSON.stringify(parsed, null, 2)}
|
|
5916
7484
|
`, {
|
|
5917
7485
|
encoding: "utf8",
|
|
5918
7486
|
flag: "wx"
|
|
5919
7487
|
});
|
|
5920
7488
|
return parsed;
|
|
5921
7489
|
} catch (error) {
|
|
5922
|
-
if (!
|
|
7490
|
+
if (!isAlreadyExists4(error)) throw error;
|
|
5923
7491
|
const existing = await readConfirmedDecision(
|
|
5924
|
-
projectRoot,
|
|
5925
|
-
parsed.decisionId
|
|
5926
|
-
);
|
|
5927
|
-
if (existing !== null && confirmedDecisionDigest(existing) === confirmedDecisionDigest(parsed)) {
|
|
5928
|
-
return existing;
|
|
5929
|
-
}
|
|
5930
|
-
throw new Error("MANCODE_CONFIRMED_DECISION_ID_CONFLICT");
|
|
5931
|
-
}
|
|
5932
|
-
}
|
|
5933
|
-
async function readConfirmedDecision(projectRoot, decisionId) {
|
|
5934
|
-
assertUlid(decisionId, "confirmed decision decisionId");
|
|
5935
|
-
try {
|
|
5936
|
-
return parseConfirmedDecision(
|
|
5937
|
-
JSON.parse(
|
|
5938
|
-
await readSafeText(
|
|
5939
|
-
confirmedDecisionDirectory(projectRoot),
|
|
5940
|
-
`${decisionId}.json`
|
|
5941
|
-
)
|
|
5942
|
-
)
|
|
5943
|
-
);
|
|
5944
|
-
} catch (error) {
|
|
5945
|
-
if (isNotFound3(error)) return null;
|
|
5946
|
-
if (error instanceof SyntaxError) {
|
|
5947
|
-
throw new Error("MANCODE_CONFIRMED_DECISION_CORRUPT");
|
|
5948
|
-
}
|
|
5949
|
-
throw error;
|
|
5950
|
-
}
|
|
5951
|
-
}
|
|
5952
|
-
async function listConfirmedDecisions(projectRoot) {
|
|
5953
|
-
const directory = confirmedDecisionDirectory(projectRoot);
|
|
5954
|
-
let entries;
|
|
5955
|
-
try {
|
|
5956
|
-
await assertSafeDirectory(directory);
|
|
5957
|
-
entries = await readdir2(directory);
|
|
5958
|
-
} catch (error) {
|
|
5959
|
-
if (isNotFound3(error)) return [];
|
|
5960
|
-
throw error;
|
|
5961
|
-
}
|
|
5962
|
-
const decisions = [];
|
|
5963
|
-
for (const entry of entries.sort(compareUtf82)) {
|
|
5964
|
-
if (!entry.endsWith(".json")) continue;
|
|
5965
|
-
if (!DECISION_FILENAME.test(entry)) {
|
|
5966
|
-
throw new Error("MANCODE_CONTEXT_COLLECTION_ENTRY_INVALID");
|
|
5967
|
-
}
|
|
5968
|
-
const decision = await readConfirmedDecision(
|
|
5969
|
-
projectRoot,
|
|
5970
|
-
entry.slice(0, -".json".length)
|
|
5971
|
-
);
|
|
5972
|
-
if (decision === null) {
|
|
5973
|
-
throw new Error("MANCODE_CONTEXT_COLLECTION_CHANGED_DURING_READ");
|
|
5974
|
-
}
|
|
5975
|
-
decisions.push(decision);
|
|
5976
|
-
}
|
|
5977
|
-
return decisions.sort(
|
|
5978
|
-
(left, right) => compareUtf82(left.decisionId, right.decisionId)
|
|
5979
|
-
);
|
|
5980
|
-
}
|
|
5981
|
-
function parseDecisionText(value, label, maxLength) {
|
|
5982
|
-
if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > maxLength) {
|
|
5983
|
-
throw new Error(`${label} is invalid`);
|
|
5984
|
-
}
|
|
5985
|
-
assertSharedTextSafe(value, label);
|
|
5986
|
-
return value;
|
|
5987
|
-
}
|
|
5988
|
-
function parseTimestamp13(value, label) {
|
|
5989
|
-
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
5990
|
-
throw new Error(`${label} must be an ISO timestamp`);
|
|
5991
|
-
}
|
|
5992
|
-
return value;
|
|
5993
|
-
}
|
|
5994
|
-
async function ensureSafeDirectory(projectRoot, directory) {
|
|
5995
|
-
const root = path5.resolve(projectRoot);
|
|
5996
|
-
const target = path5.resolve(directory);
|
|
5997
|
-
const relative = path5.relative(root, target);
|
|
5998
|
-
if (!relative || path5.isAbsolute(relative) || relative.split(path5.sep).some((segment) => segment === "..")) {
|
|
5999
|
-
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6000
|
-
}
|
|
6001
|
-
await assertSafeDirectory(root);
|
|
6002
|
-
let current = root;
|
|
6003
|
-
for (const segment of relative.split(path5.sep)) {
|
|
6004
|
-
current = path5.join(current, segment);
|
|
6005
|
-
try {
|
|
6006
|
-
await mkdir2(current);
|
|
6007
|
-
} catch (error) {
|
|
6008
|
-
if (!isAlreadyExists2(error)) throw error;
|
|
6009
|
-
}
|
|
6010
|
-
await assertSafeDirectory(current);
|
|
6011
|
-
}
|
|
6012
|
-
}
|
|
6013
|
-
async function assertSafeDirectory(directory) {
|
|
6014
|
-
const stat2 = await lstat2(directory);
|
|
6015
|
-
if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
|
|
6016
|
-
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6017
|
-
}
|
|
6018
|
-
}
|
|
6019
|
-
async function readSafeText(directory, filename) {
|
|
6020
|
-
await assertSafeDirectory(directory);
|
|
6021
|
-
const target = path5.join(directory, filename);
|
|
6022
|
-
const before = await lstat2(target);
|
|
6023
|
-
if (!before.isFile() || before.isSymbolicLink()) {
|
|
6024
|
-
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6025
|
-
}
|
|
6026
|
-
const content = await readFile3(target, "utf8");
|
|
6027
|
-
await assertSafeDirectory(directory);
|
|
6028
|
-
const after = await lstat2(target);
|
|
6029
|
-
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino) {
|
|
6030
|
-
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6031
|
-
}
|
|
6032
|
-
return content;
|
|
6033
|
-
}
|
|
6034
|
-
function compareUtf82(left, right) {
|
|
6035
|
-
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
6036
|
-
}
|
|
6037
|
-
function isAlreadyExists2(error) {
|
|
6038
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
6039
|
-
}
|
|
6040
|
-
function isNotFound3(error) {
|
|
6041
|
-
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6042
|
-
}
|
|
6043
|
-
|
|
6044
|
-
// src/context/manifest.ts
|
|
6045
|
-
var ACTIVATION_STATES = /* @__PURE__ */ new Set([
|
|
6046
|
-
"initializing",
|
|
6047
|
-
"dual_read",
|
|
6048
|
-
"activating",
|
|
6049
|
-
"v3_active",
|
|
6050
|
-
"repair_required"
|
|
6051
|
-
]);
|
|
6052
|
-
var MANAGED_ADAPTERS = [
|
|
6053
|
-
"claude-code",
|
|
6054
|
-
"codex",
|
|
6055
|
-
"cursor",
|
|
6056
|
-
"copilot",
|
|
6057
|
-
"zcode",
|
|
6058
|
-
"kimi-code",
|
|
6059
|
-
"qoder",
|
|
6060
|
-
"dsh"
|
|
6061
|
-
];
|
|
6062
|
-
var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
6063
|
-
var DIGEST_PATTERN13 = /^sha256:[a-f0-9]{64}$/;
|
|
6064
|
-
function parseSchemaManifest(value) {
|
|
6065
|
-
assertRecord(value, "schema manifest");
|
|
6066
|
-
if (value.manifestVersion !== 1 && value.manifestVersion !== 2) {
|
|
6067
|
-
throw new Error(
|
|
6068
|
-
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${String(value.manifestVersion)} supported=1,2 requiredWriter=0.4.0`
|
|
6069
|
-
);
|
|
6070
|
-
}
|
|
6071
|
-
const manifestVersion = value.manifestVersion;
|
|
6072
|
-
assertKnownKeys(
|
|
6073
|
-
value,
|
|
6074
|
-
[
|
|
6075
|
-
"manifestVersion",
|
|
6076
|
-
"layoutVersion",
|
|
6077
|
-
"epoch",
|
|
6078
|
-
"activationState",
|
|
6079
|
-
"minReaderVersion",
|
|
6080
|
-
"minWriterVersion",
|
|
6081
|
-
"activatedAt",
|
|
6082
|
-
"legacyBaseline",
|
|
6083
|
-
"managedAdapters",
|
|
6084
|
-
"lastOperationId",
|
|
6085
|
-
...manifestVersion === 2 ? ["workflowPolicyDefaults"] : []
|
|
6086
|
-
],
|
|
6087
|
-
"schema manifest"
|
|
6088
|
-
);
|
|
6089
|
-
if (value.layoutVersion !== 3) {
|
|
6090
|
-
throw new Error("schema manifest must use layoutVersion 3");
|
|
6091
|
-
}
|
|
6092
|
-
assertUlid(value.epoch, "schema manifest epoch");
|
|
6093
|
-
if (typeof value.activationState !== "string" || !ACTIVATION_STATES.has(value.activationState)) {
|
|
6094
|
-
throw new Error("schema manifest activationState is invalid");
|
|
6095
|
-
}
|
|
6096
|
-
const common = {
|
|
6097
|
-
layoutVersion: 3,
|
|
6098
|
-
epoch: value.epoch,
|
|
6099
|
-
activationState: value.activationState,
|
|
6100
|
-
minReaderVersion: parseVersion(value.minReaderVersion, "minReaderVersion"),
|
|
6101
|
-
minWriterVersion: parseVersion(value.minWriterVersion, "minWriterVersion"),
|
|
6102
|
-
activatedAt: parseTimestampOrNull3(value.activatedAt, "activatedAt"),
|
|
6103
|
-
legacyBaseline: parseLegacyBaseline(value.legacyBaseline),
|
|
6104
|
-
managedAdapters: parseManagedAdapters(value.managedAdapters),
|
|
6105
|
-
lastOperationId: parseUlidOrNull9(value.lastOperationId, "lastOperationId")
|
|
6106
|
-
};
|
|
6107
|
-
const manifest = manifestVersion === 1 ? { manifestVersion: 1, ...common } : {
|
|
6108
|
-
manifestVersion: 2,
|
|
6109
|
-
...common,
|
|
6110
|
-
workflowPolicyDefaults: parseWorkflowPolicyDefaults(
|
|
6111
|
-
value.workflowPolicyDefaults
|
|
6112
|
-
)
|
|
6113
|
-
};
|
|
6114
|
-
assertManifestStateShape(manifest);
|
|
6115
|
-
if (manifest.manifestVersion === 2 && (compareVersions(manifest.minReaderVersion, "0.4.0") < 0 || compareVersions(manifest.minWriterVersion, "0.4.0") < 0)) {
|
|
6116
|
-
throw new Error(
|
|
6117
|
-
"schema manifest V2 requires minReaderVersion and minWriterVersion 0.4.0 or newer"
|
|
6118
|
-
);
|
|
6119
|
-
}
|
|
6120
|
-
return manifest;
|
|
6121
|
-
}
|
|
6122
|
-
function serializeSchemaManifest(value) {
|
|
6123
|
-
return `${JSON.stringify(parseSchemaManifest(value), null, 2)}
|
|
6124
|
-
`;
|
|
6125
|
-
}
|
|
6126
|
-
function managedAdapterNames(inventory) {
|
|
6127
|
-
return MANAGED_ADAPTERS.filter((adapter) => inventory[adapter] !== void 0);
|
|
6128
|
-
}
|
|
6129
|
-
function parseSchemaManifestV1(value) {
|
|
6130
|
-
const manifest = parseSchemaManifest(value);
|
|
6131
|
-
if (manifest.manifestVersion !== 1) {
|
|
6132
|
-
throw new Error(
|
|
6133
|
-
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${manifest.manifestVersion} supported=1 requiredWriter=0.3.x`
|
|
6134
|
-
);
|
|
6135
|
-
}
|
|
6136
|
-
return manifest;
|
|
6137
|
-
}
|
|
6138
|
-
function parseSchemaManifestV2(value) {
|
|
6139
|
-
const manifest = parseSchemaManifest(value);
|
|
6140
|
-
if (manifest.manifestVersion !== 2) {
|
|
6141
|
-
throw new Error(
|
|
6142
|
-
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${manifest.manifestVersion} supported=2 requiredWriter=0.4.0`
|
|
7492
|
+
projectRoot,
|
|
7493
|
+
parsed.decisionId
|
|
6143
7494
|
);
|
|
7495
|
+
if (existing !== null && confirmedDecisionDigest(existing) === confirmedDecisionDigest(parsed)) {
|
|
7496
|
+
return existing;
|
|
7497
|
+
}
|
|
7498
|
+
throw new Error("MANCODE_CONFIRMED_DECISION_ID_CONFLICT");
|
|
6144
7499
|
}
|
|
6145
|
-
return manifest;
|
|
6146
7500
|
}
|
|
6147
|
-
function
|
|
6148
|
-
|
|
6149
|
-
|
|
6150
|
-
|
|
6151
|
-
|
|
6152
|
-
|
|
6153
|
-
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
throw new Error(
|
|
6158
|
-
`invalid schema manifest transition: ${previous.activationState} -> ${next.activationState}`
|
|
7501
|
+
async function readConfirmedDecision(projectRoot, decisionId) {
|
|
7502
|
+
assertUlid(decisionId, "confirmed decision decisionId");
|
|
7503
|
+
try {
|
|
7504
|
+
return parseConfirmedDecision(
|
|
7505
|
+
JSON.parse(
|
|
7506
|
+
await readSafeText(
|
|
7507
|
+
confirmedDecisionDirectory(projectRoot),
|
|
7508
|
+
`${decisionId}.json`
|
|
7509
|
+
)
|
|
7510
|
+
)
|
|
6159
7511
|
);
|
|
7512
|
+
} catch (error) {
|
|
7513
|
+
if (isNotFound5(error)) return null;
|
|
7514
|
+
if (error instanceof SyntaxError) {
|
|
7515
|
+
throw new Error("MANCODE_CONFIRMED_DECISION_CORRUPT");
|
|
7516
|
+
}
|
|
7517
|
+
throw error;
|
|
6160
7518
|
}
|
|
6161
7519
|
}
|
|
6162
|
-
function
|
|
6163
|
-
|
|
6164
|
-
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
}
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
if (previous.manifestVersion !== next.manifestVersion || previous.layoutVersion !== next.layoutVersion || previous.epoch !== next.epoch || !sameLegacyBaseline(previous.legacyBaseline, next.legacyBaseline) || previous.activationState !== "v3_active" || next.activationState !== "dual_read" || previous.activatedAt === null || next.activatedAt !== null) {
|
|
6172
|
-
throw new Error("invalid activation rollback manifest transition");
|
|
7520
|
+
async function listConfirmedDecisions(projectRoot) {
|
|
7521
|
+
const directory = confirmedDecisionDirectory(projectRoot);
|
|
7522
|
+
let entries;
|
|
7523
|
+
try {
|
|
7524
|
+
await assertSafeDirectory(directory);
|
|
7525
|
+
entries = await readdir5(directory);
|
|
7526
|
+
} catch (error) {
|
|
7527
|
+
if (isNotFound5(error)) return [];
|
|
7528
|
+
throw error;
|
|
6173
7529
|
}
|
|
6174
|
-
|
|
6175
|
-
|
|
6176
|
-
|
|
6177
|
-
|
|
6178
|
-
|
|
6179
|
-
|
|
6180
|
-
|
|
6181
|
-
|
|
6182
|
-
|
|
6183
|
-
throw new Error(
|
|
6184
|
-
`MANCODE_POLICY_VERSION_UNSUPPORTED: component=planning observed=${String(value.planning)} supported=2 requiredWriter=0.4.0`
|
|
7530
|
+
const decisions = [];
|
|
7531
|
+
for (const entry of entries.sort(compareUtf83)) {
|
|
7532
|
+
if (!entry.endsWith(".json")) continue;
|
|
7533
|
+
if (!DECISION_FILENAME.test(entry)) {
|
|
7534
|
+
throw new Error("MANCODE_CONTEXT_COLLECTION_ENTRY_INVALID");
|
|
7535
|
+
}
|
|
7536
|
+
const decision = await readConfirmedDecision(
|
|
7537
|
+
projectRoot,
|
|
7538
|
+
entry.slice(0, -".json".length)
|
|
6185
7539
|
);
|
|
7540
|
+
if (decision === null) {
|
|
7541
|
+
throw new Error("MANCODE_CONTEXT_COLLECTION_CHANGED_DURING_READ");
|
|
7542
|
+
}
|
|
7543
|
+
decisions.push(decision);
|
|
6186
7544
|
}
|
|
6187
|
-
return
|
|
7545
|
+
return decisions.sort(
|
|
7546
|
+
(left, right) => compareUtf83(left.decisionId, right.decisionId)
|
|
7547
|
+
);
|
|
6188
7548
|
}
|
|
6189
|
-
function
|
|
6190
|
-
if (typeof value !== "string" || !
|
|
6191
|
-
throw new Error(
|
|
7549
|
+
function parseDecisionText(value, label, maxLength) {
|
|
7550
|
+
if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > maxLength) {
|
|
7551
|
+
throw new Error(`${label} is invalid`);
|
|
6192
7552
|
}
|
|
7553
|
+
assertSharedTextSafe(value, label);
|
|
6193
7554
|
return value;
|
|
6194
7555
|
}
|
|
6195
|
-
function
|
|
6196
|
-
if (value === null) return null;
|
|
7556
|
+
function parseTimestamp13(value, label) {
|
|
6197
7557
|
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
6198
|
-
throw new Error(
|
|
6199
|
-
`schema manifest ${label} must be an ISO timestamp or null`
|
|
6200
|
-
);
|
|
7558
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
6201
7559
|
}
|
|
6202
7560
|
return value;
|
|
6203
7561
|
}
|
|
6204
|
-
function
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
"schema manifest legacyBaseline"
|
|
6211
|
-
);
|
|
6212
|
-
if (typeof value.stateDigest !== "string" || !DIGEST_PATTERN13.test(value.stateDigest) || typeof value.workflowIndexDigest !== "string" || !DIGEST_PATTERN13.test(value.workflowIndexDigest)) {
|
|
6213
|
-
throw new Error(
|
|
6214
|
-
"schema manifest legacyBaseline must contain sha256 digests"
|
|
6215
|
-
);
|
|
7562
|
+
async function ensureSafeDirectory(projectRoot, directory) {
|
|
7563
|
+
const root = path8.resolve(projectRoot);
|
|
7564
|
+
const target = path8.resolve(directory);
|
|
7565
|
+
const relative = path8.relative(root, target);
|
|
7566
|
+
if (!relative || path8.isAbsolute(relative) || relative.split(path8.sep).some((segment) => segment === "..")) {
|
|
7567
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6216
7568
|
}
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
|
|
6222
|
-
|
|
6223
|
-
|
|
6224
|
-
|
|
6225
|
-
const adapters = {};
|
|
6226
|
-
for (const adapter of MANAGED_ADAPTERS) {
|
|
6227
|
-
const version = value[adapter];
|
|
6228
|
-
if (version === void 0) continue;
|
|
6229
|
-
if (typeof version !== "string" || !version.trim()) {
|
|
6230
|
-
throw new Error(
|
|
6231
|
-
`schema manifest managedAdapters.${adapter} must be a non-empty version`
|
|
6232
|
-
);
|
|
7569
|
+
await assertSafeDirectory(root);
|
|
7570
|
+
let current = root;
|
|
7571
|
+
for (const segment of relative.split(path8.sep)) {
|
|
7572
|
+
current = path8.join(current, segment);
|
|
7573
|
+
try {
|
|
7574
|
+
await mkdir4(current);
|
|
7575
|
+
} catch (error) {
|
|
7576
|
+
if (!isAlreadyExists4(error)) throw error;
|
|
6233
7577
|
}
|
|
6234
|
-
|
|
7578
|
+
await assertSafeDirectory(current);
|
|
6235
7579
|
}
|
|
6236
|
-
return adapters;
|
|
6237
|
-
}
|
|
6238
|
-
function parseUlidOrNull9(value, label) {
|
|
6239
|
-
if (value === null) return null;
|
|
6240
|
-
assertUlid(value, `schema manifest ${label}`);
|
|
6241
|
-
return value;
|
|
6242
7580
|
}
|
|
6243
|
-
function
|
|
6244
|
-
|
|
6245
|
-
|
|
6246
|
-
|
|
6247
|
-
);
|
|
6248
|
-
}
|
|
6249
|
-
if ((manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.legacyBaseline === null) {
|
|
6250
|
-
throw new Error(
|
|
6251
|
-
`${manifest.activationState} manifests require a legacy baseline`
|
|
6252
|
-
);
|
|
6253
|
-
}
|
|
6254
|
-
if (manifest.activationState === "v3_active" && manifest.activatedAt === null) {
|
|
6255
|
-
throw new Error("v3_active manifests require activatedAt");
|
|
6256
|
-
}
|
|
6257
|
-
if ((manifest.activationState === "initializing" || manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.activatedAt !== null) {
|
|
6258
|
-
throw new Error(
|
|
6259
|
-
`${manifest.activationState} manifests must not have activatedAt`
|
|
6260
|
-
);
|
|
7581
|
+
async function assertSafeDirectory(directory) {
|
|
7582
|
+
const stat2 = await lstat4(directory);
|
|
7583
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
|
|
7584
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6261
7585
|
}
|
|
6262
7586
|
}
|
|
6263
|
-
function
|
|
6264
|
-
|
|
6265
|
-
const
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
}
|
|
6270
|
-
function compareVersions(left, right) {
|
|
6271
|
-
const [leftCore = "", leftPrerelease] = left.split("-", 2);
|
|
6272
|
-
const [rightCore = "", rightPrerelease] = right.split("-", 2);
|
|
6273
|
-
const leftParts = leftCore.split(".").map(Number);
|
|
6274
|
-
const rightParts = rightCore.split(".").map(Number);
|
|
6275
|
-
for (let index = 0; index < 3; index += 1) {
|
|
6276
|
-
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
6277
|
-
if (delta !== 0) return delta;
|
|
7587
|
+
async function readSafeText(directory, filename) {
|
|
7588
|
+
await assertSafeDirectory(directory);
|
|
7589
|
+
const target = path8.join(directory, filename);
|
|
7590
|
+
const before = await lstat4(target);
|
|
7591
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7592
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6278
7593
|
}
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
function comparePrerelease(left, right) {
|
|
6285
|
-
const leftParts = left.split(".");
|
|
6286
|
-
const rightParts = right.split(".");
|
|
6287
|
-
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
6288
|
-
const leftPart = leftParts[index];
|
|
6289
|
-
const rightPart = rightParts[index];
|
|
6290
|
-
if (leftPart === void 0) return -1;
|
|
6291
|
-
if (rightPart === void 0) return 1;
|
|
6292
|
-
if (leftPart === rightPart) continue;
|
|
6293
|
-
const leftNumeric = /^\d+$/.test(leftPart);
|
|
6294
|
-
const rightNumeric = /^\d+$/.test(rightPart);
|
|
6295
|
-
if (leftNumeric && rightNumeric)
|
|
6296
|
-
return Number(leftPart) - Number(rightPart);
|
|
6297
|
-
if (leftNumeric) return -1;
|
|
6298
|
-
if (rightNumeric) return 1;
|
|
6299
|
-
return leftPart.localeCompare(rightPart, "en");
|
|
7594
|
+
const content = await readFile6(target, "utf8");
|
|
7595
|
+
await assertSafeDirectory(directory);
|
|
7596
|
+
const after = await lstat4(target);
|
|
7597
|
+
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino) {
|
|
7598
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6300
7599
|
}
|
|
6301
|
-
return
|
|
7600
|
+
return content;
|
|
6302
7601
|
}
|
|
6303
|
-
function
|
|
6304
|
-
return left
|
|
7602
|
+
function compareUtf83(left, right) {
|
|
7603
|
+
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
6305
7604
|
}
|
|
6306
|
-
function
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
return /* @__PURE__ */ new Set(["activating", "repair_required"]);
|
|
6312
|
-
case "activating":
|
|
6313
|
-
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6314
|
-
case "repair_required":
|
|
6315
|
-
return /* @__PURE__ */ new Set(["v3_active"]);
|
|
6316
|
-
case "v3_active":
|
|
6317
|
-
return /* @__PURE__ */ new Set(["repair_required"]);
|
|
6318
|
-
}
|
|
7605
|
+
function isAlreadyExists4(error) {
|
|
7606
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
7607
|
+
}
|
|
7608
|
+
function isNotFound5(error) {
|
|
7609
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6319
7610
|
}
|
|
6320
7611
|
|
|
6321
7612
|
// src/context/project-facts.ts
|
|
6322
|
-
import { mkdir as
|
|
6323
|
-
import
|
|
7613
|
+
import { mkdir as mkdir5, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
7614
|
+
import path10 from "path";
|
|
6324
7615
|
|
|
6325
7616
|
// src/system/project-profile.ts
|
|
6326
|
-
import { readFile as
|
|
6327
|
-
import
|
|
7617
|
+
import { readFile as readFile7, readdir as readdir6, stat } from "fs/promises";
|
|
7618
|
+
import path9 from "path";
|
|
6328
7619
|
var PROJECT_MANIFESTS = [
|
|
6329
7620
|
"package.json",
|
|
6330
7621
|
"pyproject.toml",
|
|
@@ -6382,29 +7673,29 @@ var PROJECT_SOURCE_ROOTS = [
|
|
|
6382
7673
|
var PROJECT_MANIFEST_NAMES = new Set(PROJECT_MANIFESTS);
|
|
6383
7674
|
var PROJECT_SOURCE_ROOT_NAMES = new Set(PROJECT_SOURCE_ROOTS);
|
|
6384
7675
|
async function hasProjectEvidence(projectRoot) {
|
|
6385
|
-
const entries = await
|
|
7676
|
+
const entries = await readdir6(projectRoot, { withFileTypes: true }).catch(
|
|
6386
7677
|
() => []
|
|
6387
7678
|
);
|
|
6388
7679
|
return entries.some((entry) => {
|
|
6389
7680
|
if (entry.isDirectory()) return PROJECT_SOURCE_ROOT_NAMES.has(entry.name);
|
|
6390
7681
|
if (!entry.isFile()) return false;
|
|
6391
|
-
return PROJECT_MANIFEST_NAMES.has(entry.name) || PROJECT_SOURCE_EXTENSIONS.has(
|
|
7682
|
+
return PROJECT_MANIFEST_NAMES.has(entry.name) || PROJECT_SOURCE_EXTENSIONS.has(path9.extname(entry.name).toLowerCase());
|
|
6392
7683
|
});
|
|
6393
7684
|
}
|
|
6394
7685
|
async function detectProjectProfile(projectRoot) {
|
|
6395
|
-
const entries = await
|
|
7686
|
+
const entries = await readdir6(projectRoot).catch(() => []);
|
|
6396
7687
|
const manifests = PROJECT_MANIFESTS.filter((file) => entries.includes(file));
|
|
6397
7688
|
const sourceRoots = await existingDirs(projectRoot, [
|
|
6398
7689
|
...PROJECT_SOURCE_ROOTS,
|
|
6399
7690
|
"data",
|
|
6400
7691
|
"notebooks"
|
|
6401
7692
|
]);
|
|
6402
|
-
const packageJson = await readJson(
|
|
7693
|
+
const packageJson = await readJson(path9.join(projectRoot, "package.json"));
|
|
6403
7694
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
6404
|
-
readText(
|
|
7695
|
+
readText(path9.join(projectRoot, "pubspec.yaml")),
|
|
6405
7696
|
Promise.all([
|
|
6406
|
-
readText(
|
|
6407
|
-
readText(
|
|
7697
|
+
readText(path9.join(projectRoot, "pyproject.toml")),
|
|
7698
|
+
readText(path9.join(projectRoot, "requirements.txt"))
|
|
6408
7699
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
6409
7700
|
]);
|
|
6410
7701
|
const deps = Object.keys({
|
|
@@ -6413,7 +7704,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
6413
7704
|
...packageJson?.peerDependencies
|
|
6414
7705
|
});
|
|
6415
7706
|
const flutter = isFlutterProject(pubspec);
|
|
6416
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
7707
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path9.join(projectRoot, "src", "components", "ui"));
|
|
6417
7708
|
const languages = inferLanguages(manifests, entries);
|
|
6418
7709
|
const frameworks = inferFrameworks(
|
|
6419
7710
|
deps,
|
|
@@ -6456,7 +7747,7 @@ async function existingDirs(root, candidates) {
|
|
|
6456
7747
|
const found = [];
|
|
6457
7748
|
for (const candidate of candidates) {
|
|
6458
7749
|
try {
|
|
6459
|
-
if ((await stat(
|
|
7750
|
+
if ((await stat(path9.join(root, candidate))).isDirectory())
|
|
6460
7751
|
found.push(candidate);
|
|
6461
7752
|
} catch {
|
|
6462
7753
|
}
|
|
@@ -6472,14 +7763,14 @@ async function isDirectory(candidate) {
|
|
|
6472
7763
|
}
|
|
6473
7764
|
async function readJson(file) {
|
|
6474
7765
|
try {
|
|
6475
|
-
return JSON.parse(await
|
|
7766
|
+
return JSON.parse(await readFile7(file, "utf-8"));
|
|
6476
7767
|
} catch {
|
|
6477
7768
|
return null;
|
|
6478
7769
|
}
|
|
6479
7770
|
}
|
|
6480
7771
|
async function readText(file) {
|
|
6481
7772
|
try {
|
|
6482
|
-
return await
|
|
7773
|
+
return await readFile7(file, "utf-8");
|
|
6483
7774
|
} catch {
|
|
6484
7775
|
return "";
|
|
6485
7776
|
}
|
|
@@ -6702,8 +7993,8 @@ function parseProjectFacts(value) {
|
|
|
6702
7993
|
if (value.schemaVersion !== 1) {
|
|
6703
7994
|
throw new Error("project facts schemaVersion must be 1");
|
|
6704
7995
|
}
|
|
6705
|
-
const
|
|
6706
|
-
if (typeof
|
|
7996
|
+
const revision2 = value.revision;
|
|
7997
|
+
if (typeof revision2 !== "number" || !Number.isSafeInteger(revision2) || revision2 < 1) {
|
|
6707
7998
|
throw new Error("project facts revision must be a positive integer");
|
|
6708
7999
|
}
|
|
6709
8000
|
if (value.trust !== "detected") {
|
|
@@ -6712,20 +8003,20 @@ function parseProjectFacts(value) {
|
|
|
6712
8003
|
const uiLibrary = parseTextOrNull(value.uiLibrary, "project facts uiLibrary");
|
|
6713
8004
|
return {
|
|
6714
8005
|
schemaVersion: 1,
|
|
6715
|
-
revision,
|
|
8006
|
+
revision: revision2,
|
|
6716
8007
|
trust: "detected",
|
|
6717
8008
|
profile: parseProjectProfile(value.profile),
|
|
6718
8009
|
uiLibrary,
|
|
6719
8010
|
detectedAt: parseTimestamp14(value.detectedAt, "project facts detectedAt"),
|
|
6720
|
-
lastOperationId:
|
|
8011
|
+
lastOperationId: parseUlidOrNull9(
|
|
6721
8012
|
value.lastOperationId,
|
|
6722
8013
|
"project facts lastOperationId"
|
|
6723
8014
|
)
|
|
6724
8015
|
};
|
|
6725
8016
|
}
|
|
6726
8017
|
function projectFactsPath(projectRoot) {
|
|
6727
|
-
return
|
|
6728
|
-
|
|
8018
|
+
return path10.join(
|
|
8019
|
+
path10.resolve(projectRoot),
|
|
6729
8020
|
".mancode",
|
|
6730
8021
|
"shared",
|
|
6731
8022
|
"context",
|
|
@@ -6733,22 +8024,31 @@ function projectFactsPath(projectRoot) {
|
|
|
6733
8024
|
);
|
|
6734
8025
|
}
|
|
6735
8026
|
async function writeProjectFacts(projectRoot, facts) {
|
|
8027
|
+
const parsed = parseProjectFacts(facts);
|
|
8028
|
+
return withSharedPrivacyWrite(
|
|
8029
|
+
projectRoot,
|
|
8030
|
+
parsed.lastOperationId ?? createUlid(),
|
|
8031
|
+
parsed,
|
|
8032
|
+
() => writeProjectFactsUnlocked(projectRoot, parsed)
|
|
8033
|
+
);
|
|
8034
|
+
}
|
|
8035
|
+
async function writeProjectFactsUnlocked(projectRoot, facts) {
|
|
6736
8036
|
const parsed = parseProjectFacts(facts);
|
|
6737
8037
|
const target = projectFactsPath(projectRoot);
|
|
6738
|
-
await
|
|
6739
|
-
const temporary =
|
|
6740
|
-
|
|
6741
|
-
`.${
|
|
8038
|
+
await mkdir5(path10.dirname(target), { recursive: true });
|
|
8039
|
+
const temporary = path10.join(
|
|
8040
|
+
path10.dirname(target),
|
|
8041
|
+
`.${path10.basename(target)}.${process.pid}.${Date.now()}.tmp`
|
|
6742
8042
|
);
|
|
6743
8043
|
try {
|
|
6744
|
-
await
|
|
8044
|
+
await writeFile5(temporary, `${JSON.stringify(parsed, null, 2)}
|
|
6745
8045
|
`, {
|
|
6746
8046
|
encoding: "utf8",
|
|
6747
8047
|
flag: "wx"
|
|
6748
8048
|
});
|
|
6749
|
-
await
|
|
8049
|
+
await rename3(temporary, target);
|
|
6750
8050
|
} finally {
|
|
6751
|
-
await
|
|
8051
|
+
await rm2(temporary, { force: true }).catch(() => void 0);
|
|
6752
8052
|
}
|
|
6753
8053
|
return parsed;
|
|
6754
8054
|
}
|
|
@@ -6821,7 +8121,7 @@ function parseTextArray(value, label) {
|
|
|
6821
8121
|
}
|
|
6822
8122
|
function parseSafeRelativePathArray(value, label) {
|
|
6823
8123
|
return parseTextArray(value, label).map((item) => {
|
|
6824
|
-
if (
|
|
8124
|
+
if (path10.isAbsolute(item) || item.includes("\\") || item.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
6825
8125
|
throw new Error(`${label} must contain safe relative paths`);
|
|
6826
8126
|
}
|
|
6827
8127
|
return item;
|
|
@@ -6844,17 +8144,17 @@ function parseTimestamp14(value, label) {
|
|
|
6844
8144
|
}
|
|
6845
8145
|
return value;
|
|
6846
8146
|
}
|
|
6847
|
-
function
|
|
8147
|
+
function parseUlidOrNull9(value, label) {
|
|
6848
8148
|
if (value === null) return null;
|
|
6849
8149
|
assertUlid(value, label);
|
|
6850
8150
|
return value;
|
|
6851
8151
|
}
|
|
6852
8152
|
|
|
6853
8153
|
// src/context/task-locator.ts
|
|
6854
|
-
import { lstat as
|
|
6855
|
-
import
|
|
8154
|
+
import { lstat as lstat5, readFile as readFile9, realpath } from "fs/promises";
|
|
8155
|
+
import path11 from "path";
|
|
6856
8156
|
async function locateTask(projectRoot, requested) {
|
|
6857
|
-
const root =
|
|
8157
|
+
const root = path11.resolve(projectRoot);
|
|
6858
8158
|
if (typeof requested !== "string") {
|
|
6859
8159
|
const taskRef = parseTaskRefValue(requested);
|
|
6860
8160
|
return locateExplicitTask(root, taskRef);
|
|
@@ -6881,8 +8181,8 @@ async function locateTask(projectRoot, requested) {
|
|
|
6881
8181
|
}
|
|
6882
8182
|
function taskRootPath(projectRoot, taskRef) {
|
|
6883
8183
|
const parsed = parseTaskRefValue(taskRef);
|
|
6884
|
-
return
|
|
6885
|
-
|
|
8184
|
+
return path11.join(
|
|
8185
|
+
path11.resolve(projectRoot),
|
|
6886
8186
|
".mancode",
|
|
6887
8187
|
parsed.namespace,
|
|
6888
8188
|
"workflows",
|
|
@@ -6898,7 +8198,7 @@ async function locateExplicitTask(projectRoot, taskRef) {
|
|
|
6898
8198
|
}
|
|
6899
8199
|
async function isDirectoryWithoutSymlink(target) {
|
|
6900
8200
|
try {
|
|
6901
|
-
const entry = await
|
|
8201
|
+
const entry = await lstat5(target);
|
|
6902
8202
|
return entry.isDirectory() && !entry.isSymbolicLink();
|
|
6903
8203
|
} catch {
|
|
6904
8204
|
return false;
|
|
@@ -6916,13 +8216,13 @@ var V3ContextStore = class {
|
|
|
6916
8216
|
if (typeof projectRoot !== "string" || !projectRoot.trim()) {
|
|
6917
8217
|
throw new Error("context store projectRoot is required");
|
|
6918
8218
|
}
|
|
6919
|
-
this.projectRoot =
|
|
8219
|
+
this.projectRoot = path12.resolve(projectRoot);
|
|
6920
8220
|
}
|
|
6921
8221
|
async locateTask(requested) {
|
|
6922
8222
|
const location = await locateTask(this.projectRoot, requested);
|
|
6923
8223
|
await assertSafeDirectoryWithin(
|
|
6924
8224
|
this.projectRoot,
|
|
6925
|
-
|
|
8225
|
+
path12.relative(this.projectRoot, location.taskRoot)
|
|
6926
8226
|
);
|
|
6927
8227
|
return location;
|
|
6928
8228
|
}
|
|
@@ -7001,33 +8301,45 @@ var V3ContextStore = class {
|
|
|
7001
8301
|
),
|
|
7002
8302
|
this.readRequiredJson(
|
|
7003
8303
|
this.mancodeRoot(),
|
|
7004
|
-
|
|
8304
|
+
path12.join("shared", "config.json"),
|
|
7005
8305
|
parseProjectConfig
|
|
7006
8306
|
),
|
|
7007
8307
|
this.readRequiredJson(
|
|
7008
8308
|
this.mancodeRoot(),
|
|
7009
|
-
|
|
8309
|
+
path12.join("shared", "team", "policy.json"),
|
|
7010
8310
|
parseTeamPolicy
|
|
7011
8311
|
),
|
|
7012
8312
|
readOptionalJsonWithin(
|
|
7013
8313
|
this.mancodeRoot(),
|
|
7014
|
-
|
|
8314
|
+
path12.join("shared", "context", "project.json"),
|
|
7015
8315
|
parseProjectFacts
|
|
7016
8316
|
),
|
|
7017
8317
|
listConfirmedDecisions(this.projectRoot)
|
|
7018
8318
|
]);
|
|
8319
|
+
const privacy = await readPrivacyPolicySnapshot(this.projectRoot, manifest);
|
|
8320
|
+
if (privacy !== null && privacy.policy.workspaceId !== config.workspaceId)
|
|
8321
|
+
throw new Error("MANCODE_PRIVACY_POLICY_WORKSPACE_MISMATCH");
|
|
8322
|
+
const verifiedManifest = await this.readRequiredJson(
|
|
8323
|
+
this.mancodeRoot(),
|
|
8324
|
+
"schema.json",
|
|
8325
|
+
parseSchemaManifest
|
|
8326
|
+
);
|
|
8327
|
+
if (digestCanonicalJson(manifest) !== digestCanonicalJson(verifiedManifest))
|
|
8328
|
+
throw new Error("MANCODE_PROJECT_SNAPSHOT_CHANGED");
|
|
7019
8329
|
return {
|
|
7020
8330
|
manifest,
|
|
7021
8331
|
config,
|
|
7022
8332
|
policy,
|
|
7023
8333
|
projectFacts,
|
|
7024
8334
|
confirmedDecisions,
|
|
8335
|
+
privacy,
|
|
7025
8336
|
fingerprint: digestCanonicalJson({
|
|
7026
8337
|
manifest,
|
|
7027
8338
|
config,
|
|
7028
8339
|
policy,
|
|
7029
8340
|
projectFacts,
|
|
7030
|
-
confirmedDecisions
|
|
8341
|
+
confirmedDecisions,
|
|
8342
|
+
...privacy === null ? {} : { privacy }
|
|
7031
8343
|
})
|
|
7032
8344
|
};
|
|
7033
8345
|
}
|
|
@@ -7037,7 +8349,7 @@ var V3ContextStore = class {
|
|
|
7037
8349
|
const parentRoot = taskRootPath(this.projectRoot, parentRef);
|
|
7038
8350
|
await assertSafeDirectoryWithin(
|
|
7039
8351
|
this.projectRoot,
|
|
7040
|
-
|
|
8352
|
+
path12.relative(this.projectRoot, parentRoot)
|
|
7041
8353
|
);
|
|
7042
8354
|
const metadata = await this.readRequiredJson(
|
|
7043
8355
|
parentRoot,
|
|
@@ -7103,12 +8415,12 @@ var V3ContextStore = class {
|
|
|
7103
8415
|
async listWorkflowMetadata() {
|
|
7104
8416
|
const workflows = [];
|
|
7105
8417
|
for (const namespace of ["local", "shared"]) {
|
|
7106
|
-
const directory =
|
|
8418
|
+
const directory = path12.join(".mancode", namespace, "workflows");
|
|
7107
8419
|
const entries = await readDirectoryEntriesWithin(
|
|
7108
8420
|
this.projectRoot,
|
|
7109
8421
|
directory
|
|
7110
8422
|
);
|
|
7111
|
-
for (const taskId of entries.sort(
|
|
8423
|
+
for (const taskId of entries.sort(compareUtf84)) {
|
|
7112
8424
|
try {
|
|
7113
8425
|
assertUlid(taskId, "workflow directory");
|
|
7114
8426
|
} catch {
|
|
@@ -7118,7 +8430,7 @@ var V3ContextStore = class {
|
|
|
7118
8430
|
const taskRoot = taskRootPath(this.projectRoot, taskRef);
|
|
7119
8431
|
await assertSafeDirectoryWithin(
|
|
7120
8432
|
this.projectRoot,
|
|
7121
|
-
|
|
8433
|
+
path12.relative(this.projectRoot, taskRoot)
|
|
7122
8434
|
);
|
|
7123
8435
|
const metadata = await this.readRequiredJson(
|
|
7124
8436
|
taskRoot,
|
|
@@ -7132,7 +8444,7 @@ var V3ContextStore = class {
|
|
|
7132
8444
|
}
|
|
7133
8445
|
}
|
|
7134
8446
|
return workflows.sort(
|
|
7135
|
-
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) ||
|
|
8447
|
+
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || compareUtf84(left.taskRef.namespace, right.taskRef.namespace) || compareUtf84(left.taskRef.taskId, right.taskRef.taskId)
|
|
7136
8448
|
);
|
|
7137
8449
|
}
|
|
7138
8450
|
/**
|
|
@@ -7145,11 +8457,11 @@ var V3ContextStore = class {
|
|
|
7145
8457
|
return (await this.listWorkflowMetadata()).filter(
|
|
7146
8458
|
(metadata) => metadata.parent !== null && sameTaskRef(metadata.parent.taskRef, parent) && !isTerminalWorkflowStatus(metadata.status)
|
|
7147
8459
|
).map((metadata) => metadata.taskRef).sort(
|
|
7148
|
-
(left, right) =>
|
|
8460
|
+
(left, right) => compareUtf84(left.namespace, right.namespace) || compareUtf84(left.taskId, right.taskId)
|
|
7149
8461
|
);
|
|
7150
8462
|
}
|
|
7151
8463
|
mancodeRoot() {
|
|
7152
|
-
return
|
|
8464
|
+
return path12.join(this.projectRoot, ".mancode");
|
|
7153
8465
|
}
|
|
7154
8466
|
async readOptionalPlan(taskRef, taskRoot) {
|
|
7155
8467
|
const content = await readOptionalTextWithin(taskRoot, "plan.md");
|
|
@@ -7169,7 +8481,7 @@ var V3ContextStore = class {
|
|
|
7169
8481
|
}
|
|
7170
8482
|
const checkpoint = await this.readRequiredJson(
|
|
7171
8483
|
taskRoot,
|
|
7172
|
-
|
|
8484
|
+
path12.join("checkpoints", `${ref.artifactId}.json`),
|
|
7173
8485
|
parseCheckpoint
|
|
7174
8486
|
);
|
|
7175
8487
|
if (!sameTaskRef(checkpoint.taskRef, metadata.taskRef)) {
|
|
@@ -7180,8 +8492,8 @@ var V3ContextStore = class {
|
|
|
7180
8492
|
async readTaskHeadFence(homeStore, taskRef) {
|
|
7181
8493
|
const value = await readOptionalJsonWithin(
|
|
7182
8494
|
homeStore.root,
|
|
7183
|
-
|
|
7184
|
-
|
|
8495
|
+
path12.join(
|
|
8496
|
+
path12.relative(homeStore.root, taskHeadDirectory(homeStore)),
|
|
7185
8497
|
`${taskRef.taskId}.json`
|
|
7186
8498
|
),
|
|
7187
8499
|
parseTaskHeadFence
|
|
@@ -7193,16 +8505,16 @@ var V3ContextStore = class {
|
|
|
7193
8505
|
return value;
|
|
7194
8506
|
}
|
|
7195
8507
|
async readClaims(homeStore, taskRef) {
|
|
7196
|
-
const directory =
|
|
8508
|
+
const directory = path12.relative(homeStore.root, claimDirectory(homeStore));
|
|
7197
8509
|
const claims = await readJsonCollectionWithin(
|
|
7198
8510
|
homeStore.root,
|
|
7199
8511
|
directory,
|
|
7200
8512
|
parseClaim
|
|
7201
8513
|
);
|
|
7202
|
-
return claims.filter((claim) => sameTaskRef(claim.taskRef, taskRef)).sort((left, right) =>
|
|
8514
|
+
return claims.filter((claim) => sameTaskRef(claim.taskRef, taskRef)).sort((left, right) => compareUtf84(left.claimId, right.claimId));
|
|
7203
8515
|
}
|
|
7204
8516
|
async readHandoffs(homeStore, taskRef) {
|
|
7205
|
-
const directory =
|
|
8517
|
+
const directory = path12.relative(
|
|
7206
8518
|
homeStore.root,
|
|
7207
8519
|
handoffDirectory(homeStore)
|
|
7208
8520
|
);
|
|
@@ -7212,7 +8524,7 @@ var V3ContextStore = class {
|
|
|
7212
8524
|
parseHandoff
|
|
7213
8525
|
);
|
|
7214
8526
|
return handoffs.filter((handoff) => sameTaskRef(handoff.taskRef, taskRef)).sort(
|
|
7215
|
-
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) ||
|
|
8527
|
+
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || compareUtf84(left.handoffId, right.handoffId)
|
|
7216
8528
|
);
|
|
7217
8529
|
}
|
|
7218
8530
|
async readPendingOperations(taskRef, homeStore) {
|
|
@@ -7220,12 +8532,12 @@ var V3ContextStore = class {
|
|
|
7220
8532
|
const [journals, reservations, gitRefWorkflowRepairs] = await Promise.all([
|
|
7221
8533
|
readJsonCollectionWithin(
|
|
7222
8534
|
homeStore.root,
|
|
7223
|
-
|
|
8535
|
+
path12.relative(homeStore.root, operationDirectory(homeStore)),
|
|
7224
8536
|
parseOperationJournal
|
|
7225
8537
|
),
|
|
7226
8538
|
readJsonCollectionWithin(
|
|
7227
8539
|
homeStore.root,
|
|
7228
|
-
|
|
8540
|
+
path12.relative(homeStore.root, reservationDirectory(homeStore)),
|
|
7229
8541
|
parseOperationReservation
|
|
7230
8542
|
),
|
|
7231
8543
|
listUnfinishedGitRefWorkflowRepairs(this.projectRoot)
|
|
@@ -7251,14 +8563,14 @@ var V3ContextStore = class {
|
|
|
7251
8563
|
entityKeys: [taskKey]
|
|
7252
8564
|
}));
|
|
7253
8565
|
return [...primary, ...secondary, ...gitRefWorkflow].sort(
|
|
7254
|
-
(left, right) =>
|
|
8566
|
+
(left, right) => compareUtf84(left.operationId, right.operationId) || left.source.localeCompare(right.source, "en")
|
|
7255
8567
|
);
|
|
7256
8568
|
}
|
|
7257
8569
|
async readRequiredJson(root, relativePath, parser) {
|
|
7258
8570
|
try {
|
|
7259
8571
|
return parser(JSON.parse(await readTextWithin(root, relativePath)));
|
|
7260
8572
|
} catch (error) {
|
|
7261
|
-
if (
|
|
8573
|
+
if (isNotFound6(error)) {
|
|
7262
8574
|
throw new Error(`MANCODE_CONTEXT_ENTITY_UNAVAILABLE: ${relativePath}`);
|
|
7263
8575
|
}
|
|
7264
8576
|
if (error instanceof SyntaxError) {
|
|
@@ -7293,7 +8605,7 @@ async function readOptionalJsonWithin(root, relativePath, parser) {
|
|
|
7293
8605
|
try {
|
|
7294
8606
|
return parser(JSON.parse(await readTextWithin(root, relativePath)));
|
|
7295
8607
|
} catch (error) {
|
|
7296
|
-
if (
|
|
8608
|
+
if (isNotFound6(error)) return null;
|
|
7297
8609
|
if (error instanceof SyntaxError) {
|
|
7298
8610
|
throw new Error(`MANCODE_CONTEXT_ENTITY_CORRUPT: ${relativePath}`);
|
|
7299
8611
|
}
|
|
@@ -7304,14 +8616,14 @@ async function readJsonCollectionWithin(root, relativeDirectory, parser) {
|
|
|
7304
8616
|
const entries = await readDirectoryEntriesWithin(root, relativeDirectory);
|
|
7305
8617
|
const jsonEntries = entries.filter((entry) => entry.endsWith(JSON_SUFFIX));
|
|
7306
8618
|
const values = [];
|
|
7307
|
-
for (const entry of jsonEntries.sort(
|
|
8619
|
+
for (const entry of jsonEntries.sort(compareUtf84)) {
|
|
7308
8620
|
const stem = entry.slice(0, -JSON_SUFFIX.length);
|
|
7309
8621
|
if (!/^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}$/.test(stem)) {
|
|
7310
8622
|
throw new Error("MANCODE_CONTEXT_COLLECTION_ENTRY_INVALID");
|
|
7311
8623
|
}
|
|
7312
8624
|
const value = await readOptionalJsonWithin(
|
|
7313
8625
|
root,
|
|
7314
|
-
|
|
8626
|
+
path12.join(relativeDirectory, entry),
|
|
7315
8627
|
parser
|
|
7316
8628
|
);
|
|
7317
8629
|
if (value === null) {
|
|
@@ -7325,31 +8637,31 @@ async function readOptionalTextWithin(root, relativePath) {
|
|
|
7325
8637
|
try {
|
|
7326
8638
|
return await readTextWithin(root, relativePath);
|
|
7327
8639
|
} catch (error) {
|
|
7328
|
-
if (
|
|
8640
|
+
if (isNotFound6(error)) return null;
|
|
7329
8641
|
throw error;
|
|
7330
8642
|
}
|
|
7331
8643
|
}
|
|
7332
8644
|
async function readTextWithin(root, relativePath) {
|
|
7333
|
-
const absoluteRoot =
|
|
8645
|
+
const absoluteRoot = path12.resolve(root);
|
|
7334
8646
|
const segments = safeRelativeSegments(relativePath);
|
|
7335
8647
|
await assertSafeDirectoryWithin(absoluteRoot, ".");
|
|
7336
8648
|
const parentSegments = segments.slice(0, -1);
|
|
7337
8649
|
let current = absoluteRoot;
|
|
7338
8650
|
for (const segment of parentSegments) {
|
|
7339
|
-
current =
|
|
8651
|
+
current = path12.join(current, segment);
|
|
7340
8652
|
await assertSafeDirectoryAt(current);
|
|
7341
8653
|
}
|
|
7342
|
-
const target =
|
|
7343
|
-
const before = await
|
|
8654
|
+
const target = path12.join(absoluteRoot, ...segments);
|
|
8655
|
+
const before = await lstat6(target);
|
|
7344
8656
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7345
8657
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7346
8658
|
}
|
|
7347
|
-
const content = await
|
|
8659
|
+
const content = await readFile10(target, "utf8");
|
|
7348
8660
|
await assertSafeDirectoryWithin(
|
|
7349
8661
|
absoluteRoot,
|
|
7350
|
-
parentSegments.length === 0 ? "." :
|
|
8662
|
+
parentSegments.length === 0 ? "." : path12.join(...parentSegments)
|
|
7351
8663
|
);
|
|
7352
|
-
const after = await
|
|
8664
|
+
const after = await lstat6(target);
|
|
7353
8665
|
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino) {
|
|
7354
8666
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7355
8667
|
}
|
|
@@ -7358,43 +8670,43 @@ async function readTextWithin(root, relativePath) {
|
|
|
7358
8670
|
async function readDirectoryEntriesWithin(root, relativeDirectory) {
|
|
7359
8671
|
try {
|
|
7360
8672
|
const directory = await assertSafeDirectoryWithin(root, relativeDirectory);
|
|
7361
|
-
return await
|
|
8673
|
+
return await readdir7(directory);
|
|
7362
8674
|
} catch (error) {
|
|
7363
|
-
if (
|
|
8675
|
+
if (isNotFound6(error)) return [];
|
|
7364
8676
|
throw error;
|
|
7365
8677
|
}
|
|
7366
8678
|
}
|
|
7367
8679
|
async function assertSafeDirectoryWithin(root, relativeDirectory) {
|
|
7368
|
-
const absoluteRoot =
|
|
8680
|
+
const absoluteRoot = path12.resolve(root);
|
|
7369
8681
|
const segments = relativeDirectory === "." ? [] : safeRelativeSegments(relativeDirectory);
|
|
7370
8682
|
await assertSafeDirectoryAt(absoluteRoot);
|
|
7371
8683
|
let current = absoluteRoot;
|
|
7372
8684
|
for (const segment of segments) {
|
|
7373
|
-
current =
|
|
8685
|
+
current = path12.join(current, segment);
|
|
7374
8686
|
await assertSafeDirectoryAt(current);
|
|
7375
8687
|
}
|
|
7376
8688
|
return current;
|
|
7377
8689
|
}
|
|
7378
8690
|
async function assertSafeDirectoryAt(target) {
|
|
7379
|
-
const stat2 = await
|
|
8691
|
+
const stat2 = await lstat6(target);
|
|
7380
8692
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
|
|
7381
8693
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7382
8694
|
}
|
|
7383
8695
|
}
|
|
7384
8696
|
function safeRelativeSegments(relativePath) {
|
|
7385
|
-
if (typeof relativePath !== "string" || !relativePath || relativePath.includes("\0") ||
|
|
8697
|
+
if (typeof relativePath !== "string" || !relativePath || relativePath.includes("\0") || path12.isAbsolute(relativePath)) {
|
|
7386
8698
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7387
8699
|
}
|
|
7388
|
-
const segments = relativePath.split(
|
|
8700
|
+
const segments = relativePath.split(path12.sep);
|
|
7389
8701
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
7390
8702
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7391
8703
|
}
|
|
7392
8704
|
return segments;
|
|
7393
8705
|
}
|
|
7394
|
-
function
|
|
8706
|
+
function compareUtf84(left, right) {
|
|
7395
8707
|
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
7396
8708
|
}
|
|
7397
|
-
function
|
|
8709
|
+
function isNotFound6(error) {
|
|
7398
8710
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7399
8711
|
}
|
|
7400
8712
|
function isTerminalWorkflowStatus(status) {
|
|
@@ -7405,38 +8717,17 @@ function storedTaskAggregateDigest(snapshot) {
|
|
|
7405
8717
|
}
|
|
7406
8718
|
|
|
7407
8719
|
export {
|
|
7408
|
-
isUlid,
|
|
7409
|
-
assertUlid,
|
|
7410
|
-
createUlid,
|
|
7411
|
-
assertRecord,
|
|
7412
|
-
assertKnownKeys,
|
|
7413
8720
|
parseSchemaManifest,
|
|
7414
8721
|
serializeSchemaManifest,
|
|
7415
8722
|
managedAdapterNames,
|
|
7416
|
-
parseSchemaManifestV1,
|
|
7417
|
-
parseSchemaManifestV2,
|
|
7418
8723
|
assertSchemaManifestTransition,
|
|
8724
|
+
assertSchemaManifestPrivacyTransition,
|
|
7419
8725
|
assertSchemaManifestPolicyUpgrade,
|
|
7420
8726
|
assertActivationRollbackManifestTransition,
|
|
7421
8727
|
managedAdapterInventoriesMatch,
|
|
7422
|
-
formatTaskRef,
|
|
7423
|
-
parseTaskRef,
|
|
7424
|
-
parseTaskRefValue,
|
|
7425
|
-
sameTaskRef,
|
|
7426
|
-
resolveTaskEntityHomeStore,
|
|
7427
|
-
resolveLocalEntityHomeStore,
|
|
7428
|
-
resolveCoordinationEntityHomeStore,
|
|
7429
|
-
operationDirectory,
|
|
7430
|
-
lockDirectory,
|
|
7431
|
-
claimDirectory,
|
|
7432
|
-
handoffDirectory,
|
|
7433
|
-
taskHeadDirectory,
|
|
7434
8728
|
gitRefWorkflowRepairJournalPath,
|
|
7435
8729
|
listGitRefWorkflowRepairJournalSummaries,
|
|
7436
8730
|
listUnfinishedGitRefWorkflowRepairs,
|
|
7437
|
-
canonicalizeJson,
|
|
7438
|
-
digestCanonicalJson,
|
|
7439
|
-
sortUtf8StringSet,
|
|
7440
8731
|
scanSharedText,
|
|
7441
8732
|
assertSharedTextSafe,
|
|
7442
8733
|
assertSafeSharedRelativePath,
|
|
@@ -7446,14 +8737,10 @@ export {
|
|
|
7446
8737
|
parseAuthorizationBasis,
|
|
7447
8738
|
assertRepairUsesOriginalAuthorization,
|
|
7448
8739
|
parseOperationJournal,
|
|
7449
|
-
assertOperationJournalTransition,
|
|
7450
|
-
operationJournalDigest,
|
|
7451
8740
|
operationReservationJournalDigest,
|
|
7452
8741
|
assertOperationReservationTopology,
|
|
7453
8742
|
withOperationReservationDigests,
|
|
7454
8743
|
parseOperationReservation,
|
|
7455
|
-
createOperationReservation,
|
|
7456
|
-
writeOperationReservation,
|
|
7457
8744
|
readOperationReservation,
|
|
7458
8745
|
removeOperationReservation,
|
|
7459
8746
|
parseCheckpoint,
|
|
@@ -7500,13 +8787,41 @@ export {
|
|
|
7500
8787
|
normalizeClaimScope,
|
|
7501
8788
|
parseHandoff,
|
|
7502
8789
|
assertHandoffTransition,
|
|
7503
|
-
|
|
7504
|
-
|
|
7505
|
-
|
|
7506
|
-
|
|
7507
|
-
|
|
7508
|
-
|
|
7509
|
-
|
|
8790
|
+
acquireLocalLock,
|
|
8791
|
+
acquireEntityLocks,
|
|
8792
|
+
acquireOperationEntityLocks,
|
|
8793
|
+
readLocalLock,
|
|
8794
|
+
replaceFileAtomically,
|
|
8795
|
+
getOperationDefinition,
|
|
8796
|
+
assertOperationJournalMatchesDefinition,
|
|
8797
|
+
createPreparedOperationJournal,
|
|
8798
|
+
prepareOperationStores,
|
|
8799
|
+
readOperationJournal,
|
|
8800
|
+
updateOperationJournal,
|
|
8801
|
+
listUnfinishedOperationJournals,
|
|
8802
|
+
PROJECT_SCHEMA_LOCK,
|
|
8803
|
+
acquireProjectWriteBarrier,
|
|
8804
|
+
PRIVACY_MIN_VERSION,
|
|
8805
|
+
PRIVACY_POLICY_FILE,
|
|
8806
|
+
PRIVACY_EXCLUSIONS_FILE,
|
|
8807
|
+
parsePrivacyPolicyCandidate,
|
|
8808
|
+
parsePrivacyPolicy,
|
|
8809
|
+
parsePrivacyExclusions,
|
|
8810
|
+
createInitialPrivacyPolicy,
|
|
8811
|
+
assertPrivacyPolicyTransition,
|
|
8812
|
+
assertPrivacyExclusionsTransition,
|
|
8813
|
+
readPrivacyPolicySnapshot,
|
|
8814
|
+
readPrivacyPolicyStatus,
|
|
8815
|
+
readPrivacyAuthorityFile,
|
|
8816
|
+
containsEnhancedSensitiveText,
|
|
8817
|
+
isPrivacyExcluded,
|
|
8818
|
+
assertPrivacyValueAllowed,
|
|
8819
|
+
assertSharedPrivacyValue,
|
|
8820
|
+
acquireSharedPrivacyWriteBarrier,
|
|
8821
|
+
withSharedPrivacyWrite,
|
|
8822
|
+
assertSharedTaskWriteAtRoot,
|
|
8823
|
+
assertPrivacyRecoveryActionsAllowed,
|
|
8824
|
+
scanPrivacyActivation,
|
|
7510
8825
|
createConfirmedDecision,
|
|
7511
8826
|
publishConfirmedDecision,
|
|
7512
8827
|
readConfirmedDecision,
|
|
@@ -7521,4 +8836,4 @@ export {
|
|
|
7521
8836
|
V3ContextStore,
|
|
7522
8837
|
storedTaskAggregateDigest
|
|
7523
8838
|
};
|
|
7524
|
-
//# sourceMappingURL=chunk-
|
|
8839
|
+
//# sourceMappingURL=chunk-E2K22WYH.js.map
|