mancode 0.6.4 → 0.6.6
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 +165 -45
- package/README.md +109 -28
- package/dist/{chunk-7HPP3KYG.js → chunk-D5ABGAF4.js} +2422 -1003
- package/dist/chunk-D5ABGAF4.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-WJ6WARGG.js → chunk-RMHUYJXB.js} +46 -47
- package/dist/chunk-RMHUYJXB.js.map +1 -0
- package/dist/chunk-THOE33LU.js +1124 -0
- package/dist/chunk-THOE33LU.js.map +1 -0
- package/dist/chunk-WRBNOPFA.js +1235 -0
- package/dist/chunk-WRBNOPFA.js.map +1 -0
- package/dist/cli.js +15488 -15613
- 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-LLXL7QYG.js +11 -0
- package/dist/{v3-adapter-T3IKK3LU.js → v3-adapter-KA2NOVI5.js} +2 -2
- package/dist/v3-adapter-KA2NOVI5.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 +10 -4
- package/dist/chunk-7HPP3KYG.js.map +0 -1
- package/dist/chunk-WJ6WARGG.js.map +0 -1
- package/dist/store-LN63OL66.js +0 -9
- /package/dist/{store-LN63OL66.js.map → privacy-gateway-Q4GD2JXF.js.map} +0 -0
- /package/dist/{v3-adapter-T3IKK3LU.js.map → store-LLXL7QYG.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 lstat7, 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",
|
|
@@ -516,6 +216,28 @@ function scanSharedText(value) {
|
|
|
516
216
|
}
|
|
517
217
|
return removeNestedFindings(findings);
|
|
518
218
|
}
|
|
219
|
+
function redactSharedText(value) {
|
|
220
|
+
assertText(value, "shared text");
|
|
221
|
+
const findings = scanSharedText(value);
|
|
222
|
+
if (findings.length === 0) return { text: value, redactions: [] };
|
|
223
|
+
let redacted = "";
|
|
224
|
+
let cursor = 0;
|
|
225
|
+
const counts = /* @__PURE__ */ new Map();
|
|
226
|
+
for (const finding of findings) {
|
|
227
|
+
redacted += value.slice(cursor, finding.start);
|
|
228
|
+
redacted += `[REDACTED:${finding.kind}]`;
|
|
229
|
+
cursor = finding.end;
|
|
230
|
+
counts.set(finding.kind, (counts.get(finding.kind) ?? 0) + 1);
|
|
231
|
+
}
|
|
232
|
+
redacted += value.slice(cursor);
|
|
233
|
+
if (scanSharedText(redacted).length > 0) {
|
|
234
|
+
throw new Error("MANCODE_PRIVACY_BLOCKED");
|
|
235
|
+
}
|
|
236
|
+
return {
|
|
237
|
+
text: redacted,
|
|
238
|
+
redactions: [...counts.entries()].sort(([left], [right]) => left.localeCompare(right, "en")).map(([kind, count]) => ({ kind, count }))
|
|
239
|
+
};
|
|
240
|
+
}
|
|
519
241
|
function assertSharedTextSafe(value, label) {
|
|
520
242
|
assertText(value, label);
|
|
521
243
|
if (scanSharedText(value).length > 0) {
|
|
@@ -523,7 +245,7 @@ function assertSharedTextSafe(value, label) {
|
|
|
523
245
|
}
|
|
524
246
|
}
|
|
525
247
|
function assertSafeSharedRelativePath(value) {
|
|
526
|
-
if (typeof value !== "string" || !value || value.includes("\0") ||
|
|
248
|
+
if (typeof value !== "string" || !value || value.includes("\0") || path2.isAbsolute(value) || /^[A-Za-z][A-Za-z0-9+.-]*:/.test(value)) {
|
|
527
249
|
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
528
250
|
}
|
|
529
251
|
const normalized = value.split(/[\\/]/);
|
|
@@ -1077,6 +799,7 @@ var OPERATION_TYPES = /* @__PURE__ */ new Set([
|
|
|
1077
799
|
"greenfield_initialize",
|
|
1078
800
|
"adapter_upgrade",
|
|
1079
801
|
"project_policy_upgrade",
|
|
802
|
+
"privacy_policy_update",
|
|
1080
803
|
"v3_activate"
|
|
1081
804
|
]);
|
|
1082
805
|
var OPERATION_STATES = /* @__PURE__ */ new Set([
|
|
@@ -1275,14 +998,14 @@ function parseSecondaryReservations(value) {
|
|
|
1275
998
|
function parseExpectedRevisions(value) {
|
|
1276
999
|
assertRecord(value, "operation expectedRevisions");
|
|
1277
1000
|
const parsed = {};
|
|
1278
|
-
for (const [entityKey,
|
|
1001
|
+
for (const [entityKey, revision2] of Object.entries(value)) {
|
|
1279
1002
|
assertEntityKey(entityKey, "operation expected revision key");
|
|
1280
|
-
if (typeof
|
|
1003
|
+
if (typeof revision2 !== "number" || !Number.isSafeInteger(revision2) || revision2 < 0) {
|
|
1281
1004
|
throw new Error(
|
|
1282
1005
|
"operation expected revisions must be non-negative integers"
|
|
1283
1006
|
);
|
|
1284
1007
|
}
|
|
1285
|
-
parsed[entityKey] =
|
|
1008
|
+
parsed[entityKey] = revision2;
|
|
1286
1009
|
}
|
|
1287
1010
|
return parsed;
|
|
1288
1011
|
}
|
|
@@ -1365,9 +1088,9 @@ function assertReframeCheckpointReplacement(previous, next, replacement) {
|
|
|
1365
1088
|
throw new Error("MANCODE_REFRAME_CHECKPOINT_REPLACEMENT_INVALID");
|
|
1366
1089
|
}
|
|
1367
1090
|
const expectedRevisions = Object.fromEntries(
|
|
1368
|
-
Object.entries(previous.expectedRevisions).map(([key,
|
|
1091
|
+
Object.entries(previous.expectedRevisions).map(([key, revision2]) => [
|
|
1369
1092
|
key === fromKey ? toKey : key,
|
|
1370
|
-
|
|
1093
|
+
revision2
|
|
1371
1094
|
])
|
|
1372
1095
|
);
|
|
1373
1096
|
if (JSON.stringify(next.expectedRevisions) !== JSON.stringify(expectedRevisions)) {
|
|
@@ -1426,7 +1149,7 @@ function allowedOperationTransitions(from) {
|
|
|
1426
1149
|
|
|
1427
1150
|
// src/runtime/operation-reservation.ts
|
|
1428
1151
|
import { mkdir, readFile as readFile2, unlink, writeFile } from "fs/promises";
|
|
1429
|
-
import
|
|
1152
|
+
import path3 from "path";
|
|
1430
1153
|
var DIGEST_PATTERN3 = /^sha256:[a-f0-9]{64}$/;
|
|
1431
1154
|
var ENTITY_KEY_PATTERN2 = /^[a-z][a-z0-9_-]*:[^\0/\\]+$/;
|
|
1432
1155
|
function parseOperationReservation(value) {
|
|
@@ -1545,7 +1268,7 @@ async function removeOperationReservation(store, operationId, primaryStoreId) {
|
|
|
1545
1268
|
}
|
|
1546
1269
|
function reservationPath(store, operationId) {
|
|
1547
1270
|
assertUlid(operationId, "operation reservation operationId");
|
|
1548
|
-
return
|
|
1271
|
+
return path3.join(reservationDirectory(store), `${operationId}.json`);
|
|
1549
1272
|
}
|
|
1550
1273
|
function parseEntityKeySet2(value, label) {
|
|
1551
1274
|
if (!Array.isArray(value) || value.length === 0) {
|
|
@@ -4143,16 +3866,16 @@ function parseImplementationScope(value) {
|
|
|
4143
3866
|
"workflow metadata implementationScope modules"
|
|
4144
3867
|
)
|
|
4145
3868
|
};
|
|
4146
|
-
const
|
|
3869
|
+
const digest2 = parseDigest8(
|
|
4147
3870
|
value.digest,
|
|
4148
3871
|
"workflow metadata implementationScope digest"
|
|
4149
3872
|
);
|
|
4150
|
-
if (
|
|
3873
|
+
if (digest2 !== digestCanonicalJson(scope)) {
|
|
4151
3874
|
throw new Error(
|
|
4152
3875
|
"workflow metadata implementationScope digest does not match scope"
|
|
4153
3876
|
);
|
|
4154
3877
|
}
|
|
4155
|
-
return { ...scope, digest };
|
|
3878
|
+
return { ...scope, digest: digest2 };
|
|
4156
3879
|
}
|
|
4157
3880
|
function parseGovernance2(value) {
|
|
4158
3881
|
assertRecord(value, "workflow metadata governance");
|
|
@@ -4455,9 +4178,55 @@ function parseTimestamp8(value, label) {
|
|
|
4455
4178
|
function parseTimestampOrNull(value, label) {
|
|
4456
4179
|
return value === null ? null : parseTimestamp8(value, label);
|
|
4457
4180
|
}
|
|
4181
|
+
function isActiveSoloHandoff(metadata) {
|
|
4182
|
+
return metadata.workflowMode === "man" && metadata.coordination === "single" && metadata.status === "planned" && metadata.governance.planDecision === "solo_handoff" && metadata.soloExecution?.state === "active" && metadata.soloExecution.planVersion === metadata.governance.planVersion;
|
|
4183
|
+
}
|
|
4184
|
+
function assertSoloHandoffSession(metadata, session) {
|
|
4185
|
+
if (metadata.governance.planDecision !== "solo_handoff") return;
|
|
4186
|
+
if (!isActiveSoloHandoff(metadata) || metadata.soloExecution?.assignedSessionId !== session.sessionId || metadata.ownerActorId !== session.actorId) {
|
|
4187
|
+
throw new Error("MANCODE_SOLO_HANDOFF_NOT_ACTIVE");
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4458
4190
|
|
|
4459
4191
|
// src/context/aggregate.ts
|
|
4460
4192
|
var DIGEST_PATTERN9 = /^sha256:[a-f0-9]{64}$/;
|
|
4193
|
+
function assertDiagnosticCompletionGate(input, context) {
|
|
4194
|
+
const { metadata, requirements, review, verification } = input;
|
|
4195
|
+
if (metadata.governance.requirementsStatus !== "ready" || requirements.status !== "confirmed" || !requirementsAreReady(requirements)) {
|
|
4196
|
+
throw new Error("task completion requires ready confirmed requirements");
|
|
4197
|
+
}
|
|
4198
|
+
assertVerificationLedgerRequirements(verification, requirements);
|
|
4199
|
+
assertVerificationLedgerAgainstContext(verification, {
|
|
4200
|
+
requirementsDigest: requirements.contentDigest,
|
|
4201
|
+
planVersion: metadata.governance.planVersion,
|
|
4202
|
+
remediationRound: review.remediationRound
|
|
4203
|
+
});
|
|
4204
|
+
if (context.diagnosticOutcome === "manual_test_required") {
|
|
4205
|
+
if (verification.status !== "manual_required") {
|
|
4206
|
+
throw new Error(
|
|
4207
|
+
"diagnostic completion requires current manual-required evidence"
|
|
4208
|
+
);
|
|
4209
|
+
}
|
|
4210
|
+
for (const check of verification.checks.filter((item) => item.required)) {
|
|
4211
|
+
for (const evidence of [check.automated, check.manual]) {
|
|
4212
|
+
if (evidence === null) continue;
|
|
4213
|
+
if (evidence.status !== "passed" && !(evidence.status === "manual_required" && evidence.summary?.trim())) {
|
|
4214
|
+
throw new Error("diagnostic completion has unresolved verification");
|
|
4215
|
+
}
|
|
4216
|
+
}
|
|
4217
|
+
}
|
|
4218
|
+
} else {
|
|
4219
|
+
if (context.diagnosticOutcome !== "fixed" && context.diagnosticOutcome !== "verified" && context.diagnosticOutcome !== "no_repro") {
|
|
4220
|
+
throw new Error("MANCODE_MANBA_OUTCOME_REQUIRED");
|
|
4221
|
+
}
|
|
4222
|
+
if (verification.status !== "passed") {
|
|
4223
|
+
throw new Error(
|
|
4224
|
+
"task completion requires current required acceptance evidence"
|
|
4225
|
+
);
|
|
4226
|
+
}
|
|
4227
|
+
}
|
|
4228
|
+
assertTaskCompletionContext(metadata, context);
|
|
4229
|
+
}
|
|
4461
4230
|
function buildTaskAggregateManifest(input) {
|
|
4462
4231
|
assertTaskAggregateConsistency(input);
|
|
4463
4232
|
const latestCheckpoint = input.latestCheckpoint;
|
|
@@ -4579,6 +4348,10 @@ function assertTaskCompletionGate(input, context) {
|
|
|
4579
4348
|
if (metadata.transitionState !== "stable") {
|
|
4580
4349
|
throw new Error("workflows with a pending operation cannot complete");
|
|
4581
4350
|
}
|
|
4351
|
+
if (metadata.workflowMode === "manba") {
|
|
4352
|
+
assertDiagnosticCompletionGate(input, context);
|
|
4353
|
+
return;
|
|
4354
|
+
}
|
|
4582
4355
|
if (metadata.governance.planDecision === null) {
|
|
4583
4356
|
throw new Error("task completion requires a plan decision");
|
|
4584
4357
|
}
|
|
@@ -4586,8 +4359,7 @@ function assertTaskCompletionGate(input, context) {
|
|
|
4586
4359
|
assertExecutableImplementationScope(metadata.implementationScope);
|
|
4587
4360
|
}
|
|
4588
4361
|
if (metadata.governance.planDecision === "solo_handoff") {
|
|
4589
|
-
assertSoloHandoffCompletionGate(input
|
|
4590
|
-
return;
|
|
4362
|
+
assertSoloHandoffCompletionGate(input);
|
|
4591
4363
|
}
|
|
4592
4364
|
if (metadata.soloExecution?.state === "active") {
|
|
4593
4365
|
throw new Error(
|
|
@@ -4619,15 +4391,11 @@ function assertTaskCompletionGate(input, context) {
|
|
|
4619
4391
|
}
|
|
4620
4392
|
assertTaskCompletionContext(metadata, context);
|
|
4621
4393
|
}
|
|
4622
|
-
function assertSoloHandoffCompletionGate(input
|
|
4623
|
-
const { metadata
|
|
4394
|
+
function assertSoloHandoffCompletionGate(input) {
|
|
4395
|
+
const { metadata } = input;
|
|
4624
4396
|
if (metadata.workflowMode !== "man" || metadata.coordination !== "single" || metadata.soloExecution?.state !== "completed" || metadata.soloExecution.planVersion !== metadata.governance.planVersion || input.planDigest === null) {
|
|
4625
4397
|
throw new Error("task completion solo handoff assignment is invalid");
|
|
4626
4398
|
}
|
|
4627
|
-
if (metadata.governance.requirementsStatus !== "ready" || requirements.status !== "confirmed" || !requirementsAreReady(requirements)) {
|
|
4628
|
-
throw new Error("task completion requires ready confirmed requirements");
|
|
4629
|
-
}
|
|
4630
|
-
assertTaskCompletionContext(metadata, context);
|
|
4631
4399
|
}
|
|
4632
4400
|
function assertRequirementsCache(metadata, requirements) {
|
|
4633
4401
|
if (metadata.governance.requirementsDigest !== requirements.contentDigest) {
|
|
@@ -5590,228 +5358,2110 @@ function allowedHandoffTransitions(from) {
|
|
|
5590
5358
|
}
|
|
5591
5359
|
}
|
|
5592
5360
|
|
|
5593
|
-
// src/
|
|
5594
|
-
|
|
5595
|
-
|
|
5596
|
-
|
|
5597
|
-
|
|
5598
|
-
|
|
5599
|
-
|
|
5600
|
-
|
|
5601
|
-
|
|
5602
|
-
|
|
5603
|
-
|
|
5604
|
-
|
|
5605
|
-
|
|
5606
|
-
|
|
5607
|
-
|
|
5608
|
-
|
|
5609
|
-
|
|
5610
|
-
|
|
5361
|
+
// src/context/confirmed-decision.ts
|
|
5362
|
+
import { lstat as lstat5, mkdir as mkdir4, readFile as readFile6, readdir as readdir5, writeFile as writeFile4 } from "fs/promises";
|
|
5363
|
+
import path8 from "path";
|
|
5364
|
+
|
|
5365
|
+
// src/context/privacy-guard.ts
|
|
5366
|
+
import { lstat as lstat4, readdir as readdir4 } from "fs/promises";
|
|
5367
|
+
import path7 from "path";
|
|
5368
|
+
import { performance } from "perf_hooks";
|
|
5369
|
+
|
|
5370
|
+
// src/runtime/local-lock.ts
|
|
5371
|
+
import { createHash, randomUUID } from "crypto";
|
|
5372
|
+
import {
|
|
5373
|
+
link,
|
|
5374
|
+
lstat as lstat2,
|
|
5375
|
+
mkdir as mkdir2,
|
|
5376
|
+
readFile as readFile3,
|
|
5377
|
+
rename,
|
|
5378
|
+
rm,
|
|
5379
|
+
writeFile as writeFile2
|
|
5380
|
+
} from "fs/promises";
|
|
5381
|
+
import path4 from "path";
|
|
5382
|
+
var ENTITY_LOCK_KEY_PATTERN = /^[a-z][a-z0-9_-]*:[^\0/\\]+$/;
|
|
5383
|
+
var DEFAULT_LOCK_LEASE_MS = 3e4;
|
|
5384
|
+
var MINIMUM_LOCK_LEASE_MS = 1e3;
|
|
5385
|
+
var MAXIMUM_LOCK_LEASE_MS = 5 * 6e4;
|
|
5386
|
+
async function acquireLocalLock(store, input) {
|
|
5387
|
+
const owner = createLockOwner(store, input);
|
|
5388
|
+
const target = lockPath(store, owner.entityLockKey);
|
|
5389
|
+
await mkdir2(lockDirectory(store), { recursive: true });
|
|
5390
|
+
const temporary = temporaryOwnerPath(target);
|
|
5391
|
+
try {
|
|
5392
|
+
await writeOwnerFile(temporary, owner);
|
|
5393
|
+
let acquired = false;
|
|
5394
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
5395
|
+
try {
|
|
5396
|
+
await link(temporary, target);
|
|
5397
|
+
acquired = true;
|
|
5398
|
+
break;
|
|
5399
|
+
} catch (error) {
|
|
5400
|
+
if (!isAlreadyExists2(error)) throw error;
|
|
5401
|
+
if (!await reclaimExpiredDeadLock(store, owner.entityLockKey, owner)) {
|
|
5402
|
+
throw new Error("MANCODE_LOCK_HELD");
|
|
5403
|
+
}
|
|
5404
|
+
}
|
|
5405
|
+
}
|
|
5406
|
+
if (!acquired) throw new Error("MANCODE_LOCK_HELD");
|
|
5407
|
+
} finally {
|
|
5408
|
+
await rm(temporary, { force: true }).catch(() => void 0);
|
|
5611
5409
|
}
|
|
5612
|
-
|
|
5410
|
+
let released = false;
|
|
5411
|
+
let currentOwner = owner;
|
|
5613
5412
|
return {
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
|
|
5617
|
-
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5413
|
+
storeId: store.storeId,
|
|
5414
|
+
entityLockKey: owner.entityLockKey,
|
|
5415
|
+
get owner() {
|
|
5416
|
+
return currentOwner;
|
|
5417
|
+
},
|
|
5418
|
+
async renew(now = /* @__PURE__ */ new Date()) {
|
|
5419
|
+
if (released) throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5420
|
+
const next = renewLockOwner(currentOwner, now);
|
|
5421
|
+
const current = await readLocalLock(store, currentOwner.entityLockKey);
|
|
5422
|
+
if (!sameLockOwner(current, currentOwner)) {
|
|
5423
|
+
throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5424
|
+
}
|
|
5425
|
+
await atomicWriteLockOwner(target, next);
|
|
5426
|
+
currentOwner = next;
|
|
5427
|
+
},
|
|
5428
|
+
async release() {
|
|
5429
|
+
if (released) return;
|
|
5430
|
+
const current = await readLocalLock(store, currentOwner.entityLockKey);
|
|
5431
|
+
if (!sameLockOwner(current, currentOwner)) {
|
|
5432
|
+
throw new Error("MANCODE_LOCK_OWNERSHIP_LOST");
|
|
5433
|
+
}
|
|
5434
|
+
await rm(target, { recursive: true, force: false });
|
|
5435
|
+
released = true;
|
|
5436
|
+
}
|
|
5623
5437
|
};
|
|
5624
5438
|
}
|
|
5625
|
-
function
|
|
5626
|
-
|
|
5439
|
+
async function acquireEntityLocks(store, operationId, entityLockKeys, options = {}) {
|
|
5440
|
+
assertUlid(operationId, "local lock operationId");
|
|
5441
|
+
const orderedKeys = normalizeEntityLockKeys(entityLockKeys);
|
|
5442
|
+
const locks = [];
|
|
5443
|
+
try {
|
|
5444
|
+
for (const entityLockKey of orderedKeys) {
|
|
5445
|
+
locks.push(
|
|
5446
|
+
await acquireLocalLock(store, {
|
|
5447
|
+
operationId,
|
|
5448
|
+
entityLockKey,
|
|
5449
|
+
processId: options.processId,
|
|
5450
|
+
now: options.now,
|
|
5451
|
+
leaseMs: options.leaseMs
|
|
5452
|
+
})
|
|
5453
|
+
);
|
|
5454
|
+
}
|
|
5455
|
+
return locks;
|
|
5456
|
+
} catch (error) {
|
|
5457
|
+
await Promise.allSettled(
|
|
5458
|
+
[...locks].reverse().map((lock) => lock.release())
|
|
5459
|
+
);
|
|
5460
|
+
throw error;
|
|
5461
|
+
}
|
|
5462
|
+
}
|
|
5463
|
+
async function acquireOperationEntityLocks(operationId, targets, options = {}) {
|
|
5464
|
+
assertUlid(operationId, "operation lock operationId");
|
|
5465
|
+
if (!Array.isArray(targets) || targets.length === 0) {
|
|
5466
|
+
throw new Error("operation lock targets must not be empty");
|
|
5467
|
+
}
|
|
5468
|
+
const byStoreId = /* @__PURE__ */ new Map();
|
|
5469
|
+
for (const target of targets) {
|
|
5470
|
+
const normalizedKeys = normalizeEntityLockKeys(target.entityLockKeys);
|
|
5471
|
+
const existing = byStoreId.get(target.store.storeId);
|
|
5472
|
+
if (existing !== void 0) {
|
|
5473
|
+
throw new Error("operation lock targets must not repeat a store");
|
|
5474
|
+
}
|
|
5475
|
+
byStoreId.set(target.store.storeId, {
|
|
5476
|
+
store: target.store,
|
|
5477
|
+
entityLockKeys: normalizedKeys
|
|
5478
|
+
});
|
|
5479
|
+
}
|
|
5480
|
+
const locks = [];
|
|
5481
|
+
try {
|
|
5482
|
+
for (const target of [...byStoreId.values()].sort(
|
|
5483
|
+
(left, right) => compareUtf82(left.store.storeId, right.store.storeId)
|
|
5484
|
+
)) {
|
|
5485
|
+
locks.push(
|
|
5486
|
+
...await acquireEntityLocks(
|
|
5487
|
+
target.store,
|
|
5488
|
+
operationId,
|
|
5489
|
+
target.entityLockKeys,
|
|
5490
|
+
options
|
|
5491
|
+
)
|
|
5492
|
+
);
|
|
5493
|
+
}
|
|
5494
|
+
return locks;
|
|
5495
|
+
} catch (error) {
|
|
5496
|
+
await Promise.allSettled(
|
|
5497
|
+
[...locks].reverse().map((lock) => lock.release())
|
|
5498
|
+
);
|
|
5499
|
+
throw error;
|
|
5500
|
+
}
|
|
5501
|
+
}
|
|
5502
|
+
async function readLocalLock(store, entityLockKey) {
|
|
5503
|
+
assertEntityLockKey(entityLockKey);
|
|
5504
|
+
try {
|
|
5505
|
+
const target = lockPath(store, entityLockKey);
|
|
5506
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
5507
|
+
const entry = await lstat2(target);
|
|
5508
|
+
if (!entry.isFile() && !entry.isDirectory()) {
|
|
5509
|
+
throw new Error("MANCODE_LOCK_CORRUPT");
|
|
5510
|
+
}
|
|
5511
|
+
let raw;
|
|
5512
|
+
try {
|
|
5513
|
+
raw = await readFile3(
|
|
5514
|
+
entry.isDirectory() ? path4.join(target, "owner.json") : target,
|
|
5515
|
+
"utf8"
|
|
5516
|
+
);
|
|
5517
|
+
} catch (error) {
|
|
5518
|
+
const current = await lstat2(target);
|
|
5519
|
+
if (entry.isDirectory() !== current.isDirectory()) continue;
|
|
5520
|
+
throw error;
|
|
5521
|
+
}
|
|
5522
|
+
const owner = parseLocalLockOwner(JSON.parse(raw));
|
|
5523
|
+
if (owner.storeId !== store.storeId || owner.entityLockKey !== entityLockKey) {
|
|
5524
|
+
throw new Error("MANCODE_LOCK_CORRUPT");
|
|
5525
|
+
}
|
|
5526
|
+
return owner;
|
|
5527
|
+
}
|
|
5528
|
+
throw new Error("MANCODE_LOCK_HELD");
|
|
5529
|
+
} catch (error) {
|
|
5530
|
+
if (isNotFound3(error)) return null;
|
|
5531
|
+
if (error instanceof SyntaxError) throw new Error("MANCODE_LOCK_CORRUPT");
|
|
5532
|
+
throw error;
|
|
5533
|
+
}
|
|
5534
|
+
}
|
|
5535
|
+
function parseLocalLockOwner(value) {
|
|
5536
|
+
assertRecord(value, "local lock owner");
|
|
5627
5537
|
assertKnownKeys(
|
|
5628
5538
|
value,
|
|
5629
5539
|
[
|
|
5630
5540
|
"schemaVersion",
|
|
5631
|
-
"
|
|
5632
|
-
"
|
|
5633
|
-
"
|
|
5634
|
-
"
|
|
5635
|
-
"
|
|
5636
|
-
"
|
|
5637
|
-
"retention",
|
|
5638
|
-
"lastOperationId",
|
|
5639
|
-
"updatedAt"
|
|
5541
|
+
"operationId",
|
|
5542
|
+
"processId",
|
|
5543
|
+
"storeId",
|
|
5544
|
+
"entityLockKey",
|
|
5545
|
+
"acquiredAt",
|
|
5546
|
+
"leaseExpiresAt"
|
|
5640
5547
|
],
|
|
5641
|
-
"
|
|
5548
|
+
"local lock owner"
|
|
5642
5549
|
);
|
|
5643
5550
|
if (value.schemaVersion !== 1) {
|
|
5644
|
-
throw new Error("
|
|
5551
|
+
throw new Error("local lock owner schemaVersion must be 1");
|
|
5645
5552
|
}
|
|
5646
|
-
assertUlid(value.
|
|
5647
|
-
|
|
5648
|
-
|
|
5553
|
+
assertUlid(value.operationId, "local lock operationId");
|
|
5554
|
+
assertEntityLockKey(value.entityLockKey);
|
|
5555
|
+
if (typeof value.processId !== "number" || !Number.isSafeInteger(value.processId) || value.processId < 1) {
|
|
5556
|
+
throw new Error("local lock processId must be a positive integer");
|
|
5649
5557
|
}
|
|
5650
|
-
if (value.
|
|
5651
|
-
throw new Error("
|
|
5652
|
-
}
|
|
5653
|
-
if (typeof value.shareConfirmedDecisions !== "boolean") {
|
|
5654
|
-
throw new Error("team policy shareConfirmedDecisions must be boolean");
|
|
5558
|
+
if (typeof value.storeId !== "string" || !value.storeId.trim()) {
|
|
5559
|
+
throw new Error("local lock storeId is required");
|
|
5655
5560
|
}
|
|
5656
5561
|
return {
|
|
5657
5562
|
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")
|
|
5563
|
+
operationId: value.operationId,
|
|
5564
|
+
processId: value.processId,
|
|
5565
|
+
storeId: value.storeId,
|
|
5566
|
+
entityLockKey: value.entityLockKey,
|
|
5567
|
+
acquiredAt: parseTimestamp12(value.acquiredAt, "local lock acquiredAt"),
|
|
5568
|
+
leaseExpiresAt: value.leaseExpiresAt === void 0 ? null : value.leaseExpiresAt === null ? null : parseTimestamp12(value.leaseExpiresAt, "local lock leaseExpiresAt")
|
|
5673
5569
|
};
|
|
5674
5570
|
}
|
|
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");
|
|
5571
|
+
function normalizeEntityLockKeys(entityLockKeys) {
|
|
5572
|
+
if (!Array.isArray(entityLockKeys) || entityLockKeys.length === 0) {
|
|
5573
|
+
throw new Error("entity lock keys must be a non-empty array");
|
|
5688
5574
|
}
|
|
5575
|
+
const keys = /* @__PURE__ */ new Set();
|
|
5576
|
+
for (const key of entityLockKeys) {
|
|
5577
|
+
assertEntityLockKey(key);
|
|
5578
|
+
if (keys.has(key)) throw new Error("entity lock keys must not repeat");
|
|
5579
|
+
keys.add(key);
|
|
5580
|
+
}
|
|
5581
|
+
return [...keys].sort(compareUtf82);
|
|
5582
|
+
}
|
|
5583
|
+
function createLockOwner(store, input) {
|
|
5584
|
+
assertUlid(input.operationId, "local lock operationId");
|
|
5585
|
+
assertEntityLockKey(input.entityLockKey);
|
|
5586
|
+
const processId = input.processId ?? process.pid;
|
|
5587
|
+
if (!Number.isSafeInteger(processId) || processId < 1) {
|
|
5588
|
+
throw new Error("local lock processId must be a positive integer");
|
|
5589
|
+
}
|
|
5590
|
+
const now = input.now ?? /* @__PURE__ */ new Date();
|
|
5591
|
+
const leaseMs = parseLeaseMs(input.leaseMs);
|
|
5592
|
+
return {
|
|
5593
|
+
schemaVersion: 1,
|
|
5594
|
+
operationId: input.operationId,
|
|
5595
|
+
processId,
|
|
5596
|
+
storeId: store.storeId,
|
|
5597
|
+
entityLockKey: input.entityLockKey,
|
|
5598
|
+
acquiredAt: now.toISOString(),
|
|
5599
|
+
leaseExpiresAt: new Date(now.getTime() + leaseMs).toISOString()
|
|
5600
|
+
};
|
|
5689
5601
|
}
|
|
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
|
-
);
|
|
5602
|
+
function renewLockOwner(owner, now) {
|
|
5603
|
+
if (owner.leaseExpiresAt === null) {
|
|
5604
|
+
throw new Error("MANCODE_LOCK_LEASE_UNAVAILABLE");
|
|
5702
5605
|
}
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5606
|
+
return {
|
|
5607
|
+
...owner,
|
|
5608
|
+
leaseExpiresAt: new Date(now.getTime() + lockLeaseMs(owner)).toISOString()
|
|
5609
|
+
};
|
|
5610
|
+
}
|
|
5611
|
+
async function reclaimExpiredDeadLock(store, entityLockKey, contender) {
|
|
5612
|
+
const existing = await readLocalLock(store, entityLockKey);
|
|
5613
|
+
if (existing === null || existing.leaseExpiresAt === null || Date.parse(existing.leaseExpiresAt) >= Date.parse(contender.acquiredAt) || processIsAlive(existing.processId)) {
|
|
5614
|
+
return false;
|
|
5707
5615
|
}
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
|
|
5711
|
-
);
|
|
5616
|
+
const directory = lockPath(store, entityLockKey);
|
|
5617
|
+
const staleDirectory = `${directory}.stale.${process.pid}.${Date.now()}`;
|
|
5618
|
+
try {
|
|
5619
|
+
await rename(directory, staleDirectory);
|
|
5620
|
+
} catch (error) {
|
|
5621
|
+
if (isNotFound3(error) || isAlreadyExists2(error)) return true;
|
|
5622
|
+
throw error;
|
|
5712
5623
|
}
|
|
5624
|
+
await rm(staleDirectory, { recursive: true, force: true });
|
|
5625
|
+
return true;
|
|
5713
5626
|
}
|
|
5714
|
-
function
|
|
5715
|
-
if (
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
|
|
5720
|
-
|
|
5721
|
-
|
|
5627
|
+
function processIsAlive(processId) {
|
|
5628
|
+
if (processId === process.pid) return true;
|
|
5629
|
+
try {
|
|
5630
|
+
process.kill(processId, 0);
|
|
5631
|
+
return true;
|
|
5632
|
+
} catch (error) {
|
|
5633
|
+
return !(typeof error === "object" && error !== null && "code" in error && error.code === "ESRCH");
|
|
5634
|
+
}
|
|
5635
|
+
}
|
|
5636
|
+
async function atomicWriteLockOwner(target, owner) {
|
|
5637
|
+
const temporary = temporaryOwnerPath(target);
|
|
5638
|
+
try {
|
|
5639
|
+
await writeOwnerFile(temporary, owner);
|
|
5640
|
+
await rename(temporary, target);
|
|
5641
|
+
} finally {
|
|
5642
|
+
await rm(temporary, { force: true }).catch(() => void 0);
|
|
5643
|
+
}
|
|
5644
|
+
}
|
|
5645
|
+
function temporaryOwnerPath(target) {
|
|
5646
|
+
return `${target}.pending.${process.pid}.${randomUUID()}`;
|
|
5647
|
+
}
|
|
5648
|
+
async function writeOwnerFile(target, owner) {
|
|
5649
|
+
await writeFile2(target, `${JSON.stringify(owner, null, 2)}
|
|
5650
|
+
`, {
|
|
5651
|
+
encoding: "utf8",
|
|
5652
|
+
flag: "wx"
|
|
5653
|
+
});
|
|
5654
|
+
}
|
|
5655
|
+
function parseLeaseMs(value) {
|
|
5656
|
+
if (value === void 0) return DEFAULT_LOCK_LEASE_MS;
|
|
5657
|
+
if (!Number.isSafeInteger(value) || value < MINIMUM_LOCK_LEASE_MS || value > MAXIMUM_LOCK_LEASE_MS) {
|
|
5658
|
+
throw new Error("local lock leaseMs is invalid");
|
|
5659
|
+
}
|
|
5660
|
+
return value;
|
|
5661
|
+
}
|
|
5662
|
+
function lockLeaseMs(owner) {
|
|
5663
|
+
if (owner.leaseExpiresAt === null) return DEFAULT_LOCK_LEASE_MS;
|
|
5664
|
+
const duration = Date.parse(owner.leaseExpiresAt) - Date.parse(owner.acquiredAt);
|
|
5665
|
+
return duration >= MINIMUM_LOCK_LEASE_MS && duration <= MAXIMUM_LOCK_LEASE_MS ? duration : DEFAULT_LOCK_LEASE_MS;
|
|
5666
|
+
}
|
|
5667
|
+
function lockPath(store, entityLockKey) {
|
|
5668
|
+
assertEntityLockKey(entityLockKey);
|
|
5669
|
+
const fileName = createHash("sha256").update(entityLockKey, "utf8").digest("hex");
|
|
5670
|
+
return path4.join(lockDirectory(store), `${fileName}.lock`);
|
|
5671
|
+
}
|
|
5672
|
+
function assertEntityLockKey(value) {
|
|
5673
|
+
if (typeof value !== "string" || !ENTITY_LOCK_KEY_PATTERN.test(value) || value.includes("..")) {
|
|
5674
|
+
throw new Error("entity lock key is invalid");
|
|
5675
|
+
}
|
|
5676
|
+
}
|
|
5677
|
+
function sameLockOwner(current, expected) {
|
|
5678
|
+
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;
|
|
5679
|
+
}
|
|
5680
|
+
function compareUtf82(left, right) {
|
|
5681
|
+
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
5682
|
+
}
|
|
5683
|
+
function parseTimestamp12(value, label) {
|
|
5684
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
5685
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
5686
|
+
}
|
|
5687
|
+
return value;
|
|
5688
|
+
}
|
|
5689
|
+
function isAlreadyExists2(error) {
|
|
5690
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
5691
|
+
}
|
|
5692
|
+
function isNotFound3(error) {
|
|
5693
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
5694
|
+
}
|
|
5695
|
+
|
|
5696
|
+
// src/runtime/operation-store.ts
|
|
5697
|
+
import { mkdir as mkdir3, readFile as readFile4, readdir as readdir2, writeFile as writeFile3 } from "fs/promises";
|
|
5698
|
+
import path5 from "path";
|
|
5699
|
+
|
|
5700
|
+
// src/runtime/atomic-file.ts
|
|
5701
|
+
import { rename as rename2 } from "fs/promises";
|
|
5702
|
+
var RETRIABLE_WINDOWS_RENAME_CODES = /* @__PURE__ */ new Set(["EACCES", "EBUSY", "EPERM"]);
|
|
5703
|
+
async function replaceFileAtomically(temporary, target, options = {}) {
|
|
5704
|
+
const maxAttempts = options.maxAttempts ?? 12;
|
|
5705
|
+
const retryDelayMs = options.retryDelayMs ?? 25;
|
|
5706
|
+
if (!Number.isSafeInteger(maxAttempts) || maxAttempts < 1) {
|
|
5707
|
+
throw new Error("MANCODE_ATOMIC_REPLACE_ATTEMPTS_INVALID");
|
|
5708
|
+
}
|
|
5709
|
+
if (!Number.isSafeInteger(retryDelayMs) || retryDelayMs < 0) {
|
|
5710
|
+
throw new Error("MANCODE_ATOMIC_REPLACE_DELAY_INVALID");
|
|
5711
|
+
}
|
|
5712
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt += 1) {
|
|
5713
|
+
try {
|
|
5714
|
+
await rename2(temporary, target);
|
|
5715
|
+
return;
|
|
5716
|
+
} catch (error) {
|
|
5717
|
+
if (process.platform !== "win32" || !isRetriableWindowsRenameError(error) || attempt === maxAttempts) {
|
|
5718
|
+
throw error;
|
|
5719
|
+
}
|
|
5720
|
+
await delay(retryDelayMs * attempt);
|
|
5721
|
+
}
|
|
5722
|
+
}
|
|
5723
|
+
}
|
|
5724
|
+
function isRetriableWindowsRenameError(error) {
|
|
5725
|
+
return typeof error === "object" && error !== null && "code" in error && typeof error.code === "string" && RETRIABLE_WINDOWS_RENAME_CODES.has(
|
|
5726
|
+
error.code ?? ""
|
|
5727
|
+
);
|
|
5728
|
+
}
|
|
5729
|
+
async function delay(milliseconds) {
|
|
5730
|
+
await new Promise((resolve) => {
|
|
5731
|
+
setTimeout(resolve, milliseconds);
|
|
5732
|
+
});
|
|
5733
|
+
}
|
|
5734
|
+
|
|
5735
|
+
// src/runtime/operation-definition.ts
|
|
5736
|
+
var OPERATION_AUTHORIZATION_ACTIONS = {
|
|
5737
|
+
workflow_create: ["local_workflow_mutation", "shared_create_publish_promote"],
|
|
5738
|
+
workflow_update: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5739
|
+
requirements_draft: [
|
|
5740
|
+
"local_workflow_mutation",
|
|
5741
|
+
"shared_metadata_plan_mutation"
|
|
5742
|
+
],
|
|
5743
|
+
requirements_finalize: [
|
|
5744
|
+
"local_workflow_mutation",
|
|
5745
|
+
"shared_metadata_plan_mutation"
|
|
5746
|
+
],
|
|
5747
|
+
plan_revision: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5748
|
+
review_remediation: [
|
|
5749
|
+
"local_workflow_mutation",
|
|
5750
|
+
"shared_ledger_evidence",
|
|
5751
|
+
"review_skip_or_waiver"
|
|
5752
|
+
],
|
|
5753
|
+
verification_record: ["local_workflow_mutation", "shared_ledger_evidence"],
|
|
5754
|
+
task_complete: [
|
|
5755
|
+
"local_workflow_mutation",
|
|
5756
|
+
"task_complete_scope_change_child_merge"
|
|
5757
|
+
],
|
|
5758
|
+
publish_promote: ["shared_create_publish_promote"],
|
|
5759
|
+
handoff_transition: ["handoff_offer_cancel", "handoff_accept_reject"],
|
|
5760
|
+
handoff_accept: ["handoff_accept_reject"],
|
|
5761
|
+
scope_change_reclaim: ["task_complete_scope_change_child_merge"],
|
|
5762
|
+
claim_create: ["claim_create"],
|
|
5763
|
+
claim_renew_release: ["claim_renew_release_transfer"],
|
|
5764
|
+
claim_transfer: ["claim_renew_release_transfer"],
|
|
5765
|
+
claim_reclaim: ["claim_reclaim"],
|
|
5766
|
+
claim_revalidation: ["claim_renew_release_transfer"],
|
|
5767
|
+
checkpoint_create: [
|
|
5768
|
+
"local_workflow_mutation",
|
|
5769
|
+
"shared_metadata_plan_mutation"
|
|
5770
|
+
],
|
|
5771
|
+
solo_handoff: ["local_workflow_mutation"],
|
|
5772
|
+
reframe: ["local_workflow_mutation", "shared_metadata_plan_mutation"],
|
|
5773
|
+
child_result_merge: [
|
|
5774
|
+
"local_workflow_mutation",
|
|
5775
|
+
"task_complete_scope_change_child_merge"
|
|
5776
|
+
],
|
|
5777
|
+
task_head_reconcile: ["task_head_reconcile"],
|
|
5778
|
+
transport_migrate: ["team_policy_config_transport"],
|
|
5779
|
+
greenfield_initialize: ["team_policy_config_transport"],
|
|
5780
|
+
adapter_upgrade: ["project_maintenance"],
|
|
5781
|
+
project_policy_upgrade: ["project_maintenance"],
|
|
5782
|
+
privacy_policy_update: ["project_maintenance"],
|
|
5783
|
+
v3_activate: ["team_policy_config_transport"]
|
|
5784
|
+
};
|
|
5785
|
+
var prepare = (id, expectedRevisionPrefixes = [], requiredLockPrefixes = [], optional = {}) => ({
|
|
5786
|
+
id,
|
|
5787
|
+
visibility: "preparation",
|
|
5788
|
+
expectedRevisionPrefixes,
|
|
5789
|
+
requiredLockPrefixes,
|
|
5790
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5791
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5792
|
+
crashRecovery: "safe_abort"
|
|
5793
|
+
});
|
|
5794
|
+
var write = (id, expectedRevisionPrefixes, requiredLockPrefixes, optional = {}) => ({
|
|
5795
|
+
id,
|
|
5796
|
+
visibility: "business_write",
|
|
5797
|
+
expectedRevisionPrefixes,
|
|
5798
|
+
requiredLockPrefixes,
|
|
5799
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5800
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5801
|
+
crashRecovery: "forward_repair"
|
|
5802
|
+
});
|
|
5803
|
+
var commit = (id, expectedRevisionPrefixes, requiredLockPrefixes, optional = {}) => ({
|
|
5804
|
+
id,
|
|
5805
|
+
visibility: "commit",
|
|
5806
|
+
expectedRevisionPrefixes,
|
|
5807
|
+
requiredLockPrefixes,
|
|
5808
|
+
optionalExpectedRevisionPrefixes: optional.expectedRevisionPrefixes ?? [],
|
|
5809
|
+
optionalRequiredLockPrefixes: optional.requiredLockPrefixes ?? [],
|
|
5810
|
+
crashRecovery: "forward_repair"
|
|
5811
|
+
});
|
|
5812
|
+
var OPERATION_DEFINITIONS = {
|
|
5813
|
+
workflow_create: definition(
|
|
5814
|
+
"workflow_create",
|
|
5815
|
+
"publish-locator",
|
|
5816
|
+
false,
|
|
5817
|
+
false,
|
|
5818
|
+
[
|
|
5819
|
+
prepare("validate"),
|
|
5820
|
+
// The staging directory is private to this operation and has no task
|
|
5821
|
+
// locator/fence visibility. A crash here is compensable by deleting
|
|
5822
|
+
// verified staging, rather than forcing a fictitious forward repair.
|
|
5823
|
+
prepare("write-staging-aggregate", ["task:"], ["task:"]),
|
|
5824
|
+
prepare("validate-aggregate", ["task:"], ["task:"]),
|
|
5825
|
+
write("publish-task-directory", ["task:"], ["task:"]),
|
|
5826
|
+
write("publish-locator", ["locator:"], ["task:", "locator:"]),
|
|
5827
|
+
commit("commit", ["task:", "locator:"], ["task:", "locator:"])
|
|
5828
|
+
]
|
|
5829
|
+
),
|
|
5830
|
+
workflow_update: definition(
|
|
5831
|
+
"workflow_update",
|
|
5832
|
+
"write-metadata",
|
|
5833
|
+
true,
|
|
5834
|
+
false,
|
|
5835
|
+
[
|
|
5836
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5837
|
+
write("write-metadata", ["task:"], ["task:"]),
|
|
5838
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5839
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5840
|
+
]
|
|
5841
|
+
),
|
|
5842
|
+
requirements_draft: definition(
|
|
5843
|
+
"requirements_draft",
|
|
5844
|
+
"update-metadata",
|
|
5845
|
+
true,
|
|
5846
|
+
false,
|
|
5847
|
+
[
|
|
5848
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5849
|
+
write("write-requirements", ["requirements:", "task:"], ["task:"]),
|
|
5850
|
+
write(
|
|
5851
|
+
"mark-review-verification-stale",
|
|
5852
|
+
["review:", "verification:", "task:"],
|
|
5853
|
+
["task:"]
|
|
5854
|
+
),
|
|
5855
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5856
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5857
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5858
|
+
]
|
|
5859
|
+
),
|
|
5860
|
+
requirements_finalize: definition(
|
|
5861
|
+
"requirements_finalize",
|
|
5862
|
+
"update-metadata",
|
|
5863
|
+
true,
|
|
5864
|
+
false,
|
|
5865
|
+
[
|
|
5866
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5867
|
+
write("write-requirements", ["requirements:", "task:"], ["task:"]),
|
|
5868
|
+
write(
|
|
5869
|
+
"mark-review-verification-stale",
|
|
5870
|
+
["review:", "verification:", "task:"],
|
|
5871
|
+
["task:"]
|
|
5872
|
+
),
|
|
5873
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5874
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5875
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5876
|
+
]
|
|
5877
|
+
),
|
|
5878
|
+
plan_revision: definition("plan_revision", "update-metadata", true, false, [
|
|
5879
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
5880
|
+
write("write-plan", ["plan:", "task:"], ["task:"]),
|
|
5881
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5882
|
+
write(
|
|
5883
|
+
"mark-review-verification-stale",
|
|
5884
|
+
["review:", "verification:", "task:"],
|
|
5885
|
+
["task:"]
|
|
5886
|
+
),
|
|
5887
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5888
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5889
|
+
]),
|
|
5890
|
+
review_remediation: definition(
|
|
5891
|
+
"review_remediation",
|
|
5892
|
+
"update-metadata",
|
|
5893
|
+
true,
|
|
5894
|
+
false,
|
|
5895
|
+
[
|
|
5896
|
+
prepare("validate", ["task:", "review:"], ["task:"]),
|
|
5897
|
+
write("write-review-ledger", ["review:", "task:"], ["task:"]),
|
|
5898
|
+
write("mark-verification-stale", ["verification:", "task:"], ["task:"]),
|
|
5899
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5900
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5901
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5902
|
+
]
|
|
5903
|
+
),
|
|
5904
|
+
verification_record: definition(
|
|
5905
|
+
"verification_record",
|
|
5906
|
+
"update-metadata",
|
|
5907
|
+
true,
|
|
5908
|
+
false,
|
|
5909
|
+
[
|
|
5910
|
+
prepare("validate", ["task:", "verification:"], ["task:"]),
|
|
5911
|
+
write("write-verification-ledger", ["verification:", "task:"], ["task:"]),
|
|
5912
|
+
write("update-metadata", ["task:"], ["task:"]),
|
|
5913
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5914
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5915
|
+
]
|
|
5916
|
+
),
|
|
5917
|
+
task_complete: definition(
|
|
5918
|
+
"task_complete",
|
|
5919
|
+
"write-completed-metadata",
|
|
5920
|
+
true,
|
|
5921
|
+
false,
|
|
5922
|
+
[
|
|
5923
|
+
prepare("validate-completion-gate", ["task:"], ["task:"], {
|
|
5924
|
+
expectedRevisionPrefixes: ["claim:"]
|
|
5925
|
+
}),
|
|
5926
|
+
write("mark-operation-pending", ["task:"], ["task:"]),
|
|
5927
|
+
write("release-or-transfer-claims", ["task:"], ["task:"], {
|
|
5928
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5929
|
+
requiredLockPrefixes: ["claim:"]
|
|
5930
|
+
}),
|
|
5931
|
+
write("write-completed-metadata", ["task:"], ["task:"]),
|
|
5932
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5933
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5934
|
+
]
|
|
5935
|
+
),
|
|
5936
|
+
publish_promote: definition(
|
|
5937
|
+
"publish_promote",
|
|
5938
|
+
"publish-destination",
|
|
5939
|
+
true,
|
|
5940
|
+
true,
|
|
5941
|
+
[
|
|
5942
|
+
prepare("validate-privacy-and-paths", ["task:"], ["task:"]),
|
|
5943
|
+
// The destination staging directory remains private until its atomic
|
|
5944
|
+
// rename, so it is safely compensable just like workflow creation.
|
|
5945
|
+
prepare("stage-destination", ["task:"], ["task:"]),
|
|
5946
|
+
write("mark-source-operation-pending", ["task:"], ["task:"]),
|
|
5947
|
+
write(
|
|
5948
|
+
"publish-destination",
|
|
5949
|
+
["task:", "locator:"],
|
|
5950
|
+
["task:", "locator:"]
|
|
5951
|
+
),
|
|
5952
|
+
write("write-source-successor", ["task:"], ["task:"]),
|
|
5953
|
+
write("publish-destination-locator", ["locator:"], ["locator:"]),
|
|
5954
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5955
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5956
|
+
]
|
|
5957
|
+
),
|
|
5958
|
+
handoff_transition: definition(
|
|
5959
|
+
"handoff_transition",
|
|
5960
|
+
"write-handoff",
|
|
5961
|
+
false,
|
|
5962
|
+
false,
|
|
5963
|
+
[
|
|
5964
|
+
prepare("validate", ["task:", "handoff:"], ["task:", "handoff:"]),
|
|
5965
|
+
write("write-handoff", ["task:", "handoff:"], ["task:", "handoff:"]),
|
|
5966
|
+
commit("commit", ["task:", "handoff:"], ["task:", "handoff:"])
|
|
5967
|
+
]
|
|
5968
|
+
),
|
|
5969
|
+
handoff_accept: definition("handoff_accept", "accept-handoff", true, false, [
|
|
5970
|
+
prepare(
|
|
5971
|
+
"validate",
|
|
5972
|
+
["task:", "handoff:", "checkpoint:"],
|
|
5973
|
+
["task:", "handoff:", "checkpoint:"],
|
|
5974
|
+
{
|
|
5975
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5976
|
+
requiredLockPrefixes: ["claim:"]
|
|
5977
|
+
}
|
|
5978
|
+
),
|
|
5979
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
5980
|
+
write("create-pending-successor-claims", ["task:"], ["task:"], {
|
|
5981
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5982
|
+
requiredLockPrefixes: ["claim:"]
|
|
5983
|
+
}),
|
|
5984
|
+
write("transfer-old-claims", ["task:"], ["task:"], {
|
|
5985
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5986
|
+
requiredLockPrefixes: ["claim:"]
|
|
5987
|
+
}),
|
|
5988
|
+
write("update-owner-and-checkpoint", ["task:", "checkpoint:"], ["task:"]),
|
|
5989
|
+
write("activate-successor-claims", ["task:"], ["task:"], {
|
|
5990
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
5991
|
+
requiredLockPrefixes: ["claim:"]
|
|
5992
|
+
}),
|
|
5993
|
+
write("accept-handoff", ["handoff:", "task:"], ["task:", "handoff:"]),
|
|
5994
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
5995
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
5996
|
+
]),
|
|
5997
|
+
scope_change_reclaim: definition(
|
|
5998
|
+
"scope_change_reclaim",
|
|
5999
|
+
"activate-successor-claims",
|
|
6000
|
+
true,
|
|
6001
|
+
false,
|
|
6002
|
+
[
|
|
6003
|
+
prepare("validate", ["task:"], ["task:"], {
|
|
6004
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6005
|
+
requiredLockPrefixes: ["claim:"]
|
|
6006
|
+
}),
|
|
6007
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6008
|
+
write(
|
|
6009
|
+
"write-scope-changed-checkpoint",
|
|
6010
|
+
["checkpoint:", "task:"],
|
|
6011
|
+
["task:"]
|
|
6012
|
+
),
|
|
6013
|
+
write("create-pending-successor-claims", ["task:"], ["task:"], {
|
|
6014
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6015
|
+
requiredLockPrefixes: ["claim:"]
|
|
6016
|
+
}),
|
|
6017
|
+
write("terminate-old-claims", ["task:"], ["task:"], {
|
|
6018
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6019
|
+
requiredLockPrefixes: ["claim:"]
|
|
6020
|
+
}),
|
|
6021
|
+
write(
|
|
6022
|
+
"mark-review-verification-stale",
|
|
6023
|
+
["review:", "verification:", "task:"],
|
|
6024
|
+
["task:"]
|
|
6025
|
+
),
|
|
6026
|
+
write("update-metadata-scope", ["task:"], ["task:"]),
|
|
6027
|
+
write("activate-successor-claims", ["task:"], ["task:"], {
|
|
6028
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6029
|
+
requiredLockPrefixes: ["claim:"]
|
|
6030
|
+
}),
|
|
6031
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6032
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6033
|
+
]
|
|
6034
|
+
),
|
|
6035
|
+
claim_create: definition(
|
|
6036
|
+
"claim_create",
|
|
6037
|
+
"create-active-claim",
|
|
6038
|
+
false,
|
|
6039
|
+
false,
|
|
6040
|
+
[
|
|
6041
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
6042
|
+
write("create-active-claim", ["claim:", "task:"], ["task:", "claim:"]),
|
|
6043
|
+
commit("commit", ["claim:", "task:"], ["task:", "claim:"])
|
|
6044
|
+
]
|
|
6045
|
+
),
|
|
6046
|
+
claim_renew_release: definition(
|
|
6047
|
+
"claim_renew_release",
|
|
6048
|
+
"update-claim",
|
|
6049
|
+
false,
|
|
6050
|
+
false,
|
|
6051
|
+
[
|
|
6052
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6053
|
+
write("update-claim", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6054
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
6055
|
+
]
|
|
6056
|
+
),
|
|
6057
|
+
claim_transfer: definition(
|
|
6058
|
+
"claim_transfer",
|
|
6059
|
+
"activate-successor-claim",
|
|
6060
|
+
false,
|
|
6061
|
+
false,
|
|
6062
|
+
[
|
|
6063
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6064
|
+
write(
|
|
6065
|
+
"create-pending-successor-claim",
|
|
6066
|
+
["task:", "claim:"],
|
|
6067
|
+
["task:", "claim:"]
|
|
6068
|
+
),
|
|
6069
|
+
write(
|
|
6070
|
+
"transfer-predecessor-claim",
|
|
6071
|
+
["task:", "claim:"],
|
|
6072
|
+
["task:", "claim:"]
|
|
6073
|
+
),
|
|
6074
|
+
write(
|
|
6075
|
+
"activate-successor-claim",
|
|
6076
|
+
["task:", "claim:"],
|
|
6077
|
+
["task:", "claim:"]
|
|
6078
|
+
),
|
|
6079
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
6080
|
+
]
|
|
6081
|
+
),
|
|
6082
|
+
claim_reclaim: definition("claim_reclaim", "expire-claim", false, false, [
|
|
6083
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6084
|
+
write("expire-claim", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6085
|
+
commit("commit", ["task:", "claim:"], ["task:", "claim:"])
|
|
6086
|
+
]),
|
|
6087
|
+
claim_revalidation: definition(
|
|
6088
|
+
"claim_revalidation",
|
|
6089
|
+
"update-claim-validation",
|
|
6090
|
+
true,
|
|
6091
|
+
false,
|
|
6092
|
+
[
|
|
6093
|
+
prepare("validate", ["task:", "claim:"], ["task:", "claim:"]),
|
|
6094
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6095
|
+
write("write-base-changed-checkpoint", ["task:"], ["task:"], {
|
|
6096
|
+
expectedRevisionPrefixes: ["checkpoint:"]
|
|
6097
|
+
}),
|
|
6098
|
+
write(
|
|
6099
|
+
"update-claim-validation",
|
|
6100
|
+
["claim:", "task:"],
|
|
6101
|
+
["task:", "claim:"]
|
|
6102
|
+
),
|
|
6103
|
+
write("complete-task-validation", ["task:"], ["task:"]),
|
|
6104
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6105
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6106
|
+
]
|
|
6107
|
+
),
|
|
6108
|
+
checkpoint_create: definition(
|
|
6109
|
+
"checkpoint_create",
|
|
6110
|
+
"update-metadata-checkpoint-ref",
|
|
6111
|
+
true,
|
|
6112
|
+
false,
|
|
6113
|
+
[
|
|
6114
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
6115
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6116
|
+
write("write-checkpoint", ["checkpoint:", "task:"], ["task:"]),
|
|
6117
|
+
write("update-metadata-checkpoint-ref", ["task:"], ["task:"]),
|
|
6118
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6119
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6120
|
+
]
|
|
6121
|
+
),
|
|
6122
|
+
solo_handoff: definition(
|
|
6123
|
+
"solo_handoff",
|
|
6124
|
+
"write-workflow-assignment",
|
|
6125
|
+
false,
|
|
6126
|
+
false,
|
|
6127
|
+
[
|
|
6128
|
+
prepare("validate", ["task:"], ["task:"]),
|
|
6129
|
+
write("write-workflow-assignment", ["task:"], ["task:"]),
|
|
6130
|
+
// The session pointer is a compensable local projection, rather than
|
|
6131
|
+
// journaled workflow authority. It is intentionally not part of the
|
|
6132
|
+
// operation CAS/lock set: a failed or later-replaced pointer must never
|
|
6133
|
+
// block repair of the durable solo assignment.
|
|
6134
|
+
write("update-session-pointer", ["task:"], ["task:"]),
|
|
6135
|
+
commit("commit", ["task:"], ["task:"])
|
|
6136
|
+
]
|
|
6137
|
+
),
|
|
6138
|
+
reframe: definition("reframe", "commit-reframed-metadata", true, false, [
|
|
6139
|
+
prepare(
|
|
6140
|
+
"validate",
|
|
6141
|
+
[
|
|
6142
|
+
"task:",
|
|
6143
|
+
"requirements:",
|
|
6144
|
+
"plan:",
|
|
6145
|
+
"review:",
|
|
6146
|
+
"verification:",
|
|
6147
|
+
"archive:",
|
|
6148
|
+
"checkpoint:"
|
|
6149
|
+
],
|
|
6150
|
+
["task:", "archive:", "checkpoint:"],
|
|
6151
|
+
{
|
|
6152
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6153
|
+
requiredLockPrefixes: ["claim:"]
|
|
6154
|
+
}
|
|
6155
|
+
),
|
|
6156
|
+
write("mark-task-operation-pending", ["task:"], ["task:"]),
|
|
6157
|
+
write(
|
|
6158
|
+
"archive-requirements-plan",
|
|
6159
|
+
["archive:", "requirements:", "plan:", "task:"],
|
|
6160
|
+
["archive:", "task:"]
|
|
6161
|
+
),
|
|
6162
|
+
write("release-active-claims", ["task:"], ["task:"], {
|
|
6163
|
+
expectedRevisionPrefixes: ["claim:"],
|
|
6164
|
+
requiredLockPrefixes: ["claim:"]
|
|
6165
|
+
}),
|
|
6166
|
+
write("write-requirements-draft", ["requirements:", "task:"], ["task:"]),
|
|
6167
|
+
write(
|
|
6168
|
+
"mark-review-verification-stale",
|
|
6169
|
+
["review:", "verification:", "task:"],
|
|
6170
|
+
["task:"]
|
|
6171
|
+
),
|
|
6172
|
+
write(
|
|
6173
|
+
"write-reframe-checkpoint",
|
|
6174
|
+
["checkpoint:", "task:"],
|
|
6175
|
+
["checkpoint:", "task:"]
|
|
6176
|
+
),
|
|
6177
|
+
write("commit-reframed-metadata", ["task:"], ["task:"]),
|
|
6178
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6179
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6180
|
+
]),
|
|
6181
|
+
child_result_merge: definition(
|
|
6182
|
+
"child_result_merge",
|
|
6183
|
+
"update-parent-metadata",
|
|
6184
|
+
true,
|
|
6185
|
+
false,
|
|
6186
|
+
[
|
|
6187
|
+
prepare("validate-parent-snapshot", ["task:", "checkpoint:"], ["task:"]),
|
|
6188
|
+
write("mark-parent-operation-pending", ["task:"], ["task:"]),
|
|
6189
|
+
write("write-merge-checkpoint", ["checkpoint:", "task:"], ["task:"]),
|
|
6190
|
+
write("update-parent-metadata", ["task:"], ["task:"]),
|
|
6191
|
+
write("update-task-head-fence", ["task_head:", "task:"], ["task:"]),
|
|
6192
|
+
commit("commit", ["task_head:", "task:"], ["task:"])
|
|
6193
|
+
]
|
|
6194
|
+
),
|
|
6195
|
+
task_head_reconcile: definition(
|
|
6196
|
+
"task_head_reconcile",
|
|
6197
|
+
"adopt-task-head-fence",
|
|
6198
|
+
true,
|
|
6199
|
+
false,
|
|
6200
|
+
[
|
|
6201
|
+
prepare(
|
|
6202
|
+
"validate-clean-store-and-git-reachability",
|
|
6203
|
+
["task:", "task_head:"],
|
|
6204
|
+
["task:", "task_head:"]
|
|
6205
|
+
),
|
|
6206
|
+
prepare(
|
|
6207
|
+
"confirm-adoption",
|
|
6208
|
+
["task:", "task_head:"],
|
|
6209
|
+
["task:", "task_head:"]
|
|
6210
|
+
),
|
|
6211
|
+
write(
|
|
6212
|
+
"adopt-task-head-fence",
|
|
6213
|
+
["task_head:", "task:"],
|
|
6214
|
+
["task:", "task_head:"]
|
|
6215
|
+
),
|
|
6216
|
+
commit("commit", ["task_head:", "task:"], ["task:", "task_head:"])
|
|
6217
|
+
]
|
|
6218
|
+
),
|
|
6219
|
+
transport_migrate: definition(
|
|
6220
|
+
"transport_migrate",
|
|
6221
|
+
"switch-config-authority",
|
|
6222
|
+
true,
|
|
6223
|
+
true,
|
|
6224
|
+
[
|
|
6225
|
+
prepare(
|
|
6226
|
+
"freeze-shared-coordination-writes",
|
|
6227
|
+
["config:", "task_head:"],
|
|
6228
|
+
["config:"]
|
|
6229
|
+
),
|
|
6230
|
+
prepare(
|
|
6231
|
+
"validate-old-authority",
|
|
6232
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6233
|
+
["config:"]
|
|
6234
|
+
),
|
|
6235
|
+
write(
|
|
6236
|
+
"stage-new-authority",
|
|
6237
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6238
|
+
["config:"]
|
|
6239
|
+
),
|
|
6240
|
+
write(
|
|
6241
|
+
"establish-new-epoch",
|
|
6242
|
+
["config:", "task_head:", "claim:", "handoff:"],
|
|
6243
|
+
["config:"]
|
|
6244
|
+
),
|
|
6245
|
+
write("switch-config-authority", ["config:"], ["config:"]),
|
|
6246
|
+
commit("commit", ["config:"], ["config:"])
|
|
6247
|
+
]
|
|
6248
|
+
),
|
|
6249
|
+
greenfield_initialize: definition(
|
|
6250
|
+
"greenfield_initialize",
|
|
6251
|
+
"publish-v3-root",
|
|
6252
|
+
false,
|
|
6253
|
+
false,
|
|
6254
|
+
[
|
|
6255
|
+
prepare("verify-no-legacy-authority"),
|
|
6256
|
+
// The named staging root is still private and can be verified then
|
|
6257
|
+
// deleted before the atomic .mancode publication boundary.
|
|
6258
|
+
prepare(
|
|
6259
|
+
"write-initializing-staging-root",
|
|
6260
|
+
["schema:", "config:"],
|
|
6261
|
+
["schema:"]
|
|
6262
|
+
),
|
|
6263
|
+
prepare(
|
|
6264
|
+
"write-v3-config-policy-adapters",
|
|
6265
|
+
["schema:", "config:"],
|
|
6266
|
+
["schema:"]
|
|
6267
|
+
),
|
|
6268
|
+
write("publish-v3-root", ["schema:", "config:"], ["schema:"]),
|
|
6269
|
+
write("register-workspace-binding", ["binding:"], ["binding:"]),
|
|
6270
|
+
write("publish-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6271
|
+
write("activate-v3-manifest", ["schema:"], ["schema:"]),
|
|
6272
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6273
|
+
]
|
|
6274
|
+
),
|
|
6275
|
+
adapter_upgrade: definition(
|
|
6276
|
+
"adapter_upgrade",
|
|
6277
|
+
"replace-managed-adapters",
|
|
6278
|
+
false,
|
|
6279
|
+
false,
|
|
6280
|
+
[
|
|
6281
|
+
prepare("validate", ["adapter:"], ["adapter:"], {
|
|
6282
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6283
|
+
requiredLockPrefixes: ["schema:"]
|
|
6284
|
+
}),
|
|
6285
|
+
write("replace-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6286
|
+
write("update-adapter-inventory", [], [], {
|
|
6287
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6288
|
+
requiredLockPrefixes: ["schema:"]
|
|
6289
|
+
}),
|
|
6290
|
+
commit("verify", ["adapter:"], ["adapter:"], {
|
|
6291
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6292
|
+
requiredLockPrefixes: ["schema:"]
|
|
6293
|
+
}),
|
|
6294
|
+
commit("commit", ["adapter:"], ["adapter:"], {
|
|
6295
|
+
expectedRevisionPrefixes: ["schema:"],
|
|
6296
|
+
requiredLockPrefixes: ["schema:"]
|
|
6297
|
+
})
|
|
6298
|
+
]
|
|
6299
|
+
),
|
|
6300
|
+
project_policy_upgrade: definition(
|
|
6301
|
+
"project_policy_upgrade",
|
|
6302
|
+
"write-manifest",
|
|
6303
|
+
false,
|
|
6304
|
+
false,
|
|
6305
|
+
[
|
|
6306
|
+
prepare("validate", ["schema:"], ["schema:"]),
|
|
6307
|
+
write("write-manifest", ["schema:"], ["schema:"]),
|
|
6308
|
+
write("verify-manifest", ["schema:"], ["schema:"]),
|
|
6309
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6310
|
+
]
|
|
6311
|
+
),
|
|
6312
|
+
privacy_policy_update: definition(
|
|
6313
|
+
"privacy_policy_update",
|
|
6314
|
+
"write-manifest",
|
|
6315
|
+
false,
|
|
6316
|
+
false,
|
|
6317
|
+
[
|
|
6318
|
+
prepare("validate", ["schema:"], ["schema:"]),
|
|
6319
|
+
write("write-remote-policy", ["schema:"], ["schema:"]),
|
|
6320
|
+
write("write-exclusions", ["schema:"], ["schema:"]),
|
|
6321
|
+
write("write-policy", ["schema:"], ["schema:"]),
|
|
6322
|
+
write("write-manifest", ["schema:"], ["schema:"]),
|
|
6323
|
+
commit("commit", ["schema:"], ["schema:"])
|
|
6324
|
+
]
|
|
6325
|
+
),
|
|
6326
|
+
v3_activate: definition("v3_activate", "activate-manifest", true, true, [
|
|
6327
|
+
prepare(
|
|
6328
|
+
"validate-staged-migration",
|
|
6329
|
+
["schema:", "config:", "stage:"],
|
|
6330
|
+
["schema:", "config:", "stage:"],
|
|
6331
|
+
{
|
|
6332
|
+
expectedRevisionPrefixes: ["task:", "task_head:", "adapter:"],
|
|
6333
|
+
requiredLockPrefixes: ["task:", "task_head:", "adapter:"]
|
|
6334
|
+
}
|
|
6335
|
+
),
|
|
6336
|
+
write(
|
|
6337
|
+
"mark-manifest-activating",
|
|
6338
|
+
["schema:", "stage:"],
|
|
6339
|
+
["schema:", "stage:"]
|
|
6340
|
+
),
|
|
6341
|
+
write("replace-managed-adapters", ["adapter:"], ["adapter:"]),
|
|
6342
|
+
write("promote-staged-tasks", [], [], {
|
|
6343
|
+
expectedRevisionPrefixes: ["task:", "task_head:"],
|
|
6344
|
+
requiredLockPrefixes: ["task:", "task_head:"]
|
|
6345
|
+
}),
|
|
6346
|
+
write(
|
|
6347
|
+
"record-adapter-inventory-and-baseline",
|
|
6348
|
+
["schema:", "config:"],
|
|
6349
|
+
["schema:", "config:"]
|
|
6350
|
+
),
|
|
6351
|
+
write("activate-manifest", ["schema:", "stage:"], ["schema:", "stage:"]),
|
|
6352
|
+
commit("commit", ["schema:", "stage:"], ["schema:", "stage:"])
|
|
6353
|
+
])
|
|
6354
|
+
};
|
|
6355
|
+
var OPERATION_CRASH_FIXTURES = Object.fromEntries(
|
|
6356
|
+
Object.values(OPERATION_DEFINITIONS).map((definition2) => [
|
|
6357
|
+
definition2.type,
|
|
6358
|
+
[
|
|
6359
|
+
{
|
|
6360
|
+
operationType: definition2.type,
|
|
6361
|
+
crashAfter: "prepared",
|
|
6362
|
+
expectedRecovery: "safe_abort"
|
|
6363
|
+
},
|
|
6364
|
+
...definition2.steps.map((step) => ({
|
|
6365
|
+
operationType: definition2.type,
|
|
6366
|
+
crashAfter: step.id,
|
|
6367
|
+
expectedRecovery: step.crashRecovery
|
|
6368
|
+
}))
|
|
6369
|
+
]
|
|
6370
|
+
])
|
|
6371
|
+
);
|
|
6372
|
+
function getOperationDefinition(operationType) {
|
|
6373
|
+
return OPERATION_DEFINITIONS[operationType];
|
|
6374
|
+
}
|
|
6375
|
+
function assertOperationJournalMatchesDefinition(journal) {
|
|
6376
|
+
const definition2 = getOperationDefinition(journal.type);
|
|
6377
|
+
assertOperationAuthorizationAction(journal);
|
|
6378
|
+
const actualSteps = journal.steps.map((step) => step.id);
|
|
6379
|
+
const expectedSteps = definition2.steps.map((step) => step.id);
|
|
6380
|
+
if (actualSteps.length !== expectedSteps.length || actualSteps.some((step, index) => step !== expectedSteps[index])) {
|
|
6381
|
+
throw new Error(
|
|
6382
|
+
`operation ${journal.type} steps do not match its machine-readable definition`
|
|
6383
|
+
);
|
|
6384
|
+
}
|
|
6385
|
+
const expectedRevisionPrefixes = new Set(
|
|
6386
|
+
definition2.steps.flatMap((step) => step.expectedRevisionPrefixes)
|
|
6387
|
+
);
|
|
6388
|
+
const lockPrefixes = new Set(
|
|
6389
|
+
definition2.steps.flatMap((step) => step.requiredLockPrefixes)
|
|
6390
|
+
);
|
|
6391
|
+
const optionalExpectedRevisionPrefixes = new Set(
|
|
6392
|
+
definition2.steps.flatMap((step) => step.optionalExpectedRevisionPrefixes)
|
|
6393
|
+
);
|
|
6394
|
+
const optionalLockPrefixes = new Set(
|
|
6395
|
+
definition2.steps.flatMap((step) => step.optionalRequiredLockPrefixes)
|
|
6396
|
+
);
|
|
6397
|
+
const sharedTask = journal.entityLocks.some(
|
|
6398
|
+
(key) => key.startsWith("task:shared:")
|
|
6399
|
+
);
|
|
6400
|
+
if (!sharedTask) {
|
|
6401
|
+
expectedRevisionPrefixes.delete("task_head:");
|
|
6402
|
+
}
|
|
6403
|
+
assertPrefixCoverage(
|
|
6404
|
+
Object.keys(journal.expectedRevisions),
|
|
6405
|
+
expectedRevisionPrefixes,
|
|
6406
|
+
"expected revisions"
|
|
6407
|
+
);
|
|
6408
|
+
assertPrefixCoverage(journal.entityLocks, lockPrefixes, "entity locks");
|
|
6409
|
+
assertOptionalPrefixPairs(
|
|
6410
|
+
Object.keys(journal.expectedRevisions),
|
|
6411
|
+
journal.entityLocks,
|
|
6412
|
+
optionalExpectedRevisionPrefixes,
|
|
6413
|
+
optionalLockPrefixes
|
|
6414
|
+
);
|
|
6415
|
+
if (definition2.requiresSharedTaskHeadFence && sharedTask) {
|
|
6416
|
+
assertPrefixCoverage(
|
|
6417
|
+
Object.keys(journal.expectedRevisions),
|
|
6418
|
+
/* @__PURE__ */ new Set(["task_head:"]),
|
|
6419
|
+
"expected revisions"
|
|
6420
|
+
);
|
|
6421
|
+
assertPrefixCoverage(
|
|
6422
|
+
journal.entityLocks,
|
|
6423
|
+
/* @__PURE__ */ new Set(["task_head:"]),
|
|
6424
|
+
"entity locks"
|
|
6425
|
+
);
|
|
6426
|
+
}
|
|
6427
|
+
}
|
|
6428
|
+
function assertOperationAuthorizationAction(journal) {
|
|
6429
|
+
const definition2 = getOperationDefinition(journal.type);
|
|
6430
|
+
if (!definition2.authorizationActions.includes(journal.authorizationBasis.action)) {
|
|
6431
|
+
throw new Error("MANCODE_OPERATION_AUTHORIZATION_ACTION_MISMATCH");
|
|
6432
|
+
}
|
|
6433
|
+
}
|
|
6434
|
+
function definition(type, primaryCommitStep, requiresSharedTaskHeadFence, allowsSecondaryReservations, steps) {
|
|
6435
|
+
if (!steps.some((step) => step.id === primaryCommitStep)) {
|
|
6436
|
+
throw new Error(`operation ${type} has no primary commit step`);
|
|
6437
|
+
}
|
|
6438
|
+
for (const step of steps) {
|
|
6439
|
+
for (const prefix of step.optionalExpectedRevisionPrefixes) {
|
|
6440
|
+
if (step.expectedRevisionPrefixes.includes(prefix)) {
|
|
6441
|
+
throw new Error(
|
|
6442
|
+
`operation ${type} repeats optional expected revision prefix ${prefix}`
|
|
6443
|
+
);
|
|
6444
|
+
}
|
|
6445
|
+
}
|
|
6446
|
+
for (const prefix of step.optionalRequiredLockPrefixes) {
|
|
6447
|
+
if (step.requiredLockPrefixes.includes(prefix)) {
|
|
6448
|
+
throw new Error(
|
|
6449
|
+
`operation ${type} repeats optional lock prefix ${prefix}`
|
|
6450
|
+
);
|
|
6451
|
+
}
|
|
6452
|
+
}
|
|
6453
|
+
}
|
|
6454
|
+
return {
|
|
6455
|
+
schemaVersion: 1,
|
|
6456
|
+
type,
|
|
6457
|
+
authorizationActions: OPERATION_AUTHORIZATION_ACTIONS[type],
|
|
6458
|
+
primaryCommitStep,
|
|
6459
|
+
requiresSharedTaskHeadFence,
|
|
6460
|
+
allowsSecondaryReservations,
|
|
6461
|
+
steps
|
|
6462
|
+
};
|
|
6463
|
+
}
|
|
6464
|
+
function assertPrefixCoverage(values, prefixes, label) {
|
|
6465
|
+
for (const prefix of prefixes) {
|
|
6466
|
+
if (!values.some((value) => value.startsWith(prefix))) {
|
|
6467
|
+
throw new Error(`operation journal ${label} are missing ${prefix}`);
|
|
6468
|
+
}
|
|
6469
|
+
}
|
|
6470
|
+
}
|
|
6471
|
+
function assertOptionalPrefixPairs(expectedRevisions, entityLocks, optionalExpectedRevisionPrefixes, optionalLockPrefixes) {
|
|
6472
|
+
for (const prefix of optionalExpectedRevisionPrefixes) {
|
|
6473
|
+
if (expectedRevisions.some((value) => value.startsWith(prefix)) && !entityLocks.some((value) => value.startsWith(prefix))) {
|
|
6474
|
+
throw new Error(
|
|
6475
|
+
`operation journal entity locks are missing optional ${prefix}`
|
|
6476
|
+
);
|
|
6477
|
+
}
|
|
6478
|
+
}
|
|
6479
|
+
for (const prefix of optionalLockPrefixes) {
|
|
6480
|
+
if (entityLocks.some((value) => value.startsWith(prefix)) && !expectedRevisions.some((value) => value.startsWith(prefix))) {
|
|
6481
|
+
throw new Error(
|
|
6482
|
+
`operation journal expected revisions are missing optional ${prefix}`
|
|
6483
|
+
);
|
|
6484
|
+
}
|
|
6485
|
+
}
|
|
6486
|
+
}
|
|
6487
|
+
|
|
6488
|
+
// src/runtime/operation-store.ts
|
|
6489
|
+
async function createPreparedOperationJournal(store, journal) {
|
|
6490
|
+
assertOperationAuthorizationAction(journal);
|
|
6491
|
+
if (journal.state !== "prepared") {
|
|
6492
|
+
throw new Error("only a prepared operation journal may be created");
|
|
6493
|
+
}
|
|
6494
|
+
if (journal.primaryStoreId !== store.storeId) {
|
|
6495
|
+
throw new Error(
|
|
6496
|
+
"operation journal primaryStoreId does not match its home store"
|
|
6497
|
+
);
|
|
6498
|
+
}
|
|
6499
|
+
assertOperationReservationTopology(journal);
|
|
6500
|
+
if (journal.type !== "transport_migrate" && store.kind !== "checkout_local") {
|
|
6501
|
+
const migrationInProgress = (await listUnfinishedOperationJournals(store)).some((candidate) => candidate.type === "transport_migrate");
|
|
6502
|
+
if (migrationInProgress) {
|
|
6503
|
+
throw new Error("MANCODE_TRANSPORT_MIGRATION_FROZEN");
|
|
6504
|
+
}
|
|
6505
|
+
}
|
|
6506
|
+
const directory = operationDirectory(store);
|
|
6507
|
+
const target = operationJournalPath(store, journal.operationId);
|
|
6508
|
+
await mkdir3(directory, { recursive: true });
|
|
6509
|
+
try {
|
|
6510
|
+
await writeFile3(target, serialize2(journal), {
|
|
6511
|
+
encoding: "utf8",
|
|
6512
|
+
flag: "wx"
|
|
6513
|
+
});
|
|
6514
|
+
return journal;
|
|
6515
|
+
} catch (error) {
|
|
6516
|
+
if (!isAlreadyExists3(error)) throw error;
|
|
6517
|
+
const existing = await readOperationJournal(store, journal.operationId);
|
|
6518
|
+
if (existing !== null && operationJournalDigest(existing) === operationJournalDigest(journal)) {
|
|
6519
|
+
return existing;
|
|
6520
|
+
}
|
|
6521
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CONFLICT");
|
|
6522
|
+
}
|
|
6523
|
+
}
|
|
6524
|
+
async function prepareOperationStores(input) {
|
|
6525
|
+
const { primaryStore, journal } = input;
|
|
6526
|
+
validateSecondaryStores(primaryStore, journal, input.secondaryStores);
|
|
6527
|
+
await createPreparedOperationJournal(primaryStore, journal);
|
|
6528
|
+
const createdAt = (input.now ?? /* @__PURE__ */ new Date()).toISOString();
|
|
6529
|
+
const reservations = [];
|
|
6530
|
+
for (const secondaryStore of input.secondaryStores) {
|
|
6531
|
+
const reservation = createOperationReservation(
|
|
6532
|
+
journal,
|
|
6533
|
+
secondaryStore.storeId,
|
|
6534
|
+
createdAt
|
|
6535
|
+
);
|
|
6536
|
+
reservations.push(
|
|
6537
|
+
await writeOperationReservation(secondaryStore, reservation)
|
|
6538
|
+
);
|
|
6539
|
+
}
|
|
6540
|
+
return reservations;
|
|
6541
|
+
}
|
|
6542
|
+
async function readOperationJournal(store, operationId) {
|
|
6543
|
+
try {
|
|
6544
|
+
assertUlid(operationId, "operation journal operationId");
|
|
6545
|
+
const raw = await readFile4(
|
|
6546
|
+
operationJournalPath(store, operationId),
|
|
6547
|
+
"utf8"
|
|
6548
|
+
);
|
|
6549
|
+
return parseOperationJournal(JSON.parse(raw));
|
|
6550
|
+
} catch (error) {
|
|
6551
|
+
if (isNotFound4(error)) return null;
|
|
6552
|
+
if (error instanceof SyntaxError) {
|
|
6553
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CORRUPT");
|
|
6554
|
+
}
|
|
6555
|
+
throw error;
|
|
6556
|
+
}
|
|
6557
|
+
}
|
|
6558
|
+
async function updateOperationJournal(store, next, options) {
|
|
6559
|
+
const previous = await readOperationJournal(store, next.operationId);
|
|
6560
|
+
if (previous === null) throw new Error("MANCODE_OPERATION_JOURNAL_NOT_FOUND");
|
|
6561
|
+
if (previous.primaryStoreId !== store.storeId || next.primaryStoreId !== store.storeId) {
|
|
6562
|
+
throw new Error(
|
|
6563
|
+
"operation journal must be updated in its primary home store"
|
|
6564
|
+
);
|
|
6565
|
+
}
|
|
6566
|
+
assertOperationJournalTransition(previous, next, options);
|
|
6567
|
+
await atomicWriteOperationJournal(store, next);
|
|
6568
|
+
return next;
|
|
6569
|
+
}
|
|
6570
|
+
async function listUnfinishedOperationJournals(store) {
|
|
6571
|
+
let entries;
|
|
6572
|
+
try {
|
|
6573
|
+
entries = await readdir2(operationDirectory(store));
|
|
6574
|
+
} catch (error) {
|
|
6575
|
+
if (isNotFound4(error)) return [];
|
|
6576
|
+
throw error;
|
|
6577
|
+
}
|
|
6578
|
+
const journals = [];
|
|
6579
|
+
for (const entry of entries) {
|
|
6580
|
+
if (!entry.endsWith(".json")) continue;
|
|
6581
|
+
const operationId = entry.slice(0, -".json".length);
|
|
6582
|
+
try {
|
|
6583
|
+
assertUlid(operationId, "operation journal filename");
|
|
6584
|
+
} catch {
|
|
6585
|
+
throw new Error("MANCODE_OPERATION_JOURNAL_CORRUPT");
|
|
6586
|
+
}
|
|
6587
|
+
const journal = await readOperationJournal(store, operationId);
|
|
6588
|
+
if (journal !== null && journal.state !== "committed" && journal.state !== "aborted") {
|
|
6589
|
+
journals.push(journal);
|
|
6590
|
+
}
|
|
6591
|
+
}
|
|
6592
|
+
return journals.sort(
|
|
6593
|
+
(left, right) => Buffer.from(left.operationId, "utf8").compare(
|
|
6594
|
+
Buffer.from(right.operationId, "utf8")
|
|
6595
|
+
)
|
|
6596
|
+
);
|
|
6597
|
+
}
|
|
6598
|
+
function operationJournalPath(store, operationId) {
|
|
6599
|
+
assertUlid(operationId, "operation journal operationId");
|
|
6600
|
+
return path5.join(operationDirectory(store), `${operationId}.json`);
|
|
6601
|
+
}
|
|
6602
|
+
async function atomicWriteOperationJournal(store, journal) {
|
|
6603
|
+
const target = operationJournalPath(store, journal.operationId);
|
|
6604
|
+
const temporary = path5.join(
|
|
6605
|
+
operationDirectory(store),
|
|
6606
|
+
`.${journal.operationId}.${process.pid}.${Date.now()}.tmp`
|
|
6607
|
+
);
|
|
6608
|
+
await writeFile3(temporary, serialize2(journal), {
|
|
6609
|
+
encoding: "utf8",
|
|
6610
|
+
flag: "wx"
|
|
6611
|
+
});
|
|
6612
|
+
await replaceFileAtomically(temporary, target);
|
|
6613
|
+
}
|
|
6614
|
+
function validateSecondaryStores(primaryStore, journal, secondaryStores) {
|
|
6615
|
+
if (journal.primaryStoreId !== primaryStore.storeId) {
|
|
6616
|
+
throw new Error(
|
|
6617
|
+
"operation journal primaryStoreId does not match primaryStore"
|
|
6618
|
+
);
|
|
6619
|
+
}
|
|
6620
|
+
const expectedStoreIds = new Set(
|
|
6621
|
+
journal.secondaryReservations.map((reservation) => reservation.storeId)
|
|
6622
|
+
);
|
|
6623
|
+
const suppliedStoreIds = /* @__PURE__ */ new Set();
|
|
6624
|
+
for (const store of secondaryStores) {
|
|
6625
|
+
if (store.storeId === primaryStore.storeId || suppliedStoreIds.has(store.storeId)) {
|
|
6626
|
+
throw new Error(
|
|
6627
|
+
"operation secondary stores must be unique and exclude primaryStore"
|
|
6628
|
+
);
|
|
6629
|
+
}
|
|
6630
|
+
suppliedStoreIds.add(store.storeId);
|
|
6631
|
+
}
|
|
6632
|
+
if (expectedStoreIds.size !== suppliedStoreIds.size || [...expectedStoreIds].some((storeId) => !suppliedStoreIds.has(storeId))) {
|
|
6633
|
+
throw new Error(
|
|
6634
|
+
"operation secondary stores do not match the journal reservations"
|
|
6635
|
+
);
|
|
6636
|
+
}
|
|
6637
|
+
}
|
|
6638
|
+
function serialize2(journal) {
|
|
6639
|
+
return `${JSON.stringify(journal, null, 2)}
|
|
6640
|
+
`;
|
|
6641
|
+
}
|
|
6642
|
+
function isAlreadyExists3(error) {
|
|
6643
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
6644
|
+
}
|
|
6645
|
+
function isNotFound4(error) {
|
|
6646
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6647
|
+
}
|
|
6648
|
+
|
|
6649
|
+
// src/runtime/project-write-barrier.ts
|
|
6650
|
+
var PROJECT_SCHEMA_LOCK = "schema:project";
|
|
6651
|
+
async function acquireProjectWriteBarrier(runtime, operationId, now) {
|
|
6652
|
+
const localStore = resolveLocalEntityHomeStore(
|
|
6653
|
+
runtime.entityHomeStoreContext
|
|
6654
|
+
);
|
|
6655
|
+
const [lock] = await acquireEntityLocks(
|
|
6656
|
+
localStore,
|
|
6657
|
+
operationId,
|
|
6658
|
+
[PROJECT_SCHEMA_LOCK],
|
|
6659
|
+
{ now }
|
|
6660
|
+
);
|
|
6661
|
+
if (lock === void 0) {
|
|
6662
|
+
throw new Error("MANCODE_LOCK_HELD");
|
|
6663
|
+
}
|
|
6664
|
+
try {
|
|
6665
|
+
const unfinished = await listUnfinishedOperationJournals(localStore);
|
|
6666
|
+
if (unfinished.some(
|
|
6667
|
+
(journal) => journal.entityLocks.includes(PROJECT_SCHEMA_LOCK)
|
|
6668
|
+
)) {
|
|
6669
|
+
throw new Error("MANCODE_OPERATION_REPAIR_REQUIRED");
|
|
6670
|
+
}
|
|
6671
|
+
return lock;
|
|
6672
|
+
} catch (error) {
|
|
6673
|
+
await lock.release().catch(() => void 0);
|
|
6674
|
+
throw error;
|
|
6675
|
+
}
|
|
6676
|
+
}
|
|
6677
|
+
|
|
6678
|
+
// src/context/privacy-policy.ts
|
|
6679
|
+
import { lstat as lstat3, readFile as readFile5, readdir as readdir3 } from "fs/promises";
|
|
6680
|
+
import path6 from "path";
|
|
6681
|
+
|
|
6682
|
+
// src/context/manifest.ts
|
|
6683
|
+
var ACTIVATION_STATES = /* @__PURE__ */ new Set([
|
|
6684
|
+
"initializing",
|
|
6685
|
+
"dual_read",
|
|
6686
|
+
"activating",
|
|
6687
|
+
"v3_active",
|
|
6688
|
+
"repair_required"
|
|
6689
|
+
]);
|
|
6690
|
+
var MANAGED_ADAPTERS = [
|
|
6691
|
+
"claude-code",
|
|
6692
|
+
"codex",
|
|
6693
|
+
"cursor",
|
|
6694
|
+
"copilot",
|
|
6695
|
+
"zcode",
|
|
6696
|
+
"kimi-code",
|
|
6697
|
+
"qoder",
|
|
6698
|
+
"dsh"
|
|
6699
|
+
];
|
|
6700
|
+
var VERSION_PATTERN = /^\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?$/;
|
|
6701
|
+
var DIGEST_PATTERN13 = /^sha256:[a-f0-9]{64}$/;
|
|
6702
|
+
function parseSchemaManifest(value) {
|
|
6703
|
+
assertRecord(value, "schema manifest");
|
|
6704
|
+
if (value.manifestVersion !== 1 && value.manifestVersion !== 2 && value.manifestVersion !== 3) {
|
|
6705
|
+
throw new Error(
|
|
6706
|
+
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${String(value.manifestVersion)} supported=1,2,3 requiredWriter=0.6.5`
|
|
6707
|
+
);
|
|
6708
|
+
}
|
|
6709
|
+
const manifestVersion = value.manifestVersion;
|
|
6710
|
+
assertKnownKeys(
|
|
6711
|
+
value,
|
|
6712
|
+
[
|
|
6713
|
+
"manifestVersion",
|
|
6714
|
+
"layoutVersion",
|
|
6715
|
+
"epoch",
|
|
6716
|
+
"activationState",
|
|
6717
|
+
"minReaderVersion",
|
|
6718
|
+
"minWriterVersion",
|
|
6719
|
+
"activatedAt",
|
|
6720
|
+
"legacyBaseline",
|
|
6721
|
+
"managedAdapters",
|
|
6722
|
+
"lastOperationId",
|
|
6723
|
+
...manifestVersion >= 2 ? ["workflowPolicyDefaults"] : [],
|
|
6724
|
+
...manifestVersion === 3 ? ["privacyPolicy"] : []
|
|
6725
|
+
],
|
|
6726
|
+
"schema manifest"
|
|
6727
|
+
);
|
|
6728
|
+
if (value.layoutVersion !== 3) {
|
|
6729
|
+
throw new Error("schema manifest must use layoutVersion 3");
|
|
6730
|
+
}
|
|
6731
|
+
assertUlid(value.epoch, "schema manifest epoch");
|
|
6732
|
+
if (typeof value.activationState !== "string" || !ACTIVATION_STATES.has(value.activationState)) {
|
|
6733
|
+
throw new Error("schema manifest activationState is invalid");
|
|
6734
|
+
}
|
|
6735
|
+
const common = {
|
|
6736
|
+
layoutVersion: 3,
|
|
6737
|
+
epoch: value.epoch,
|
|
6738
|
+
activationState: value.activationState,
|
|
6739
|
+
minReaderVersion: parseVersion(value.minReaderVersion, "minReaderVersion"),
|
|
6740
|
+
minWriterVersion: parseVersion(value.minWriterVersion, "minWriterVersion"),
|
|
6741
|
+
activatedAt: parseTimestampOrNull3(value.activatedAt, "activatedAt"),
|
|
6742
|
+
legacyBaseline: parseLegacyBaseline(value.legacyBaseline),
|
|
6743
|
+
managedAdapters: parseManagedAdapters(value.managedAdapters),
|
|
6744
|
+
lastOperationId: parseUlidOrNull8(value.lastOperationId, "lastOperationId")
|
|
6745
|
+
};
|
|
6746
|
+
const manifest = manifestVersion === 1 ? { manifestVersion: 1, ...common } : manifestVersion === 2 ? {
|
|
6747
|
+
manifestVersion: 2,
|
|
6748
|
+
...common,
|
|
6749
|
+
workflowPolicyDefaults: parseWorkflowPolicyDefaults(
|
|
6750
|
+
value.workflowPolicyDefaults
|
|
6751
|
+
)
|
|
6752
|
+
} : {
|
|
6753
|
+
manifestVersion: 3,
|
|
6754
|
+
...common,
|
|
6755
|
+
workflowPolicyDefaults: parsePrivacyManifestPlanningDefaults(
|
|
6756
|
+
value.workflowPolicyDefaults
|
|
6757
|
+
),
|
|
6758
|
+
privacyPolicy: parsePrivacyPolicyReference(value.privacyPolicy)
|
|
6759
|
+
};
|
|
6760
|
+
assertManifestStateShape(manifest);
|
|
6761
|
+
if (manifest.manifestVersion >= 2 && (compareVersions(manifest.minReaderVersion, "0.4.0") < 0 || compareVersions(manifest.minWriterVersion, "0.4.0") < 0)) {
|
|
6762
|
+
throw new Error(
|
|
6763
|
+
"schema manifest V2 requires minReaderVersion and minWriterVersion 0.4.0 or newer"
|
|
6764
|
+
);
|
|
6765
|
+
}
|
|
6766
|
+
if (manifest.manifestVersion === 3 && (compareVersions(manifest.minReaderVersion, "0.6.5") < 0 || compareVersions(manifest.minWriterVersion, "0.6.5") < 0))
|
|
6767
|
+
throw new Error(
|
|
6768
|
+
"schema manifest V3 requires minReaderVersion and minWriterVersion 0.6.5 or newer"
|
|
6769
|
+
);
|
|
6770
|
+
return manifest;
|
|
6771
|
+
}
|
|
6772
|
+
function serializeSchemaManifest(value) {
|
|
6773
|
+
return `${JSON.stringify(parseSchemaManifest(value), null, 2)}
|
|
6774
|
+
`;
|
|
6775
|
+
}
|
|
6776
|
+
function managedAdapterNames(inventory) {
|
|
6777
|
+
return MANAGED_ADAPTERS.filter((adapter) => inventory[adapter] !== void 0);
|
|
6778
|
+
}
|
|
6779
|
+
function assertSchemaManifestTransition(previous, next) {
|
|
6780
|
+
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) {
|
|
6781
|
+
throw new Error(
|
|
6782
|
+
"schema manifest identity and legacy baseline are immutable"
|
|
6783
|
+
);
|
|
6784
|
+
}
|
|
6785
|
+
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))
|
|
6786
|
+
throw new Error("MANCODE_PRIVACY_MANIFEST_TRANSITION_REQUIRED");
|
|
6787
|
+
if (previous.activationState === next.activationState) return;
|
|
6788
|
+
if (!allowedManifestTransitions(previous.activationState).has(
|
|
6789
|
+
next.activationState
|
|
6790
|
+
)) {
|
|
6791
|
+
throw new Error(
|
|
6792
|
+
`invalid schema manifest transition: ${previous.activationState} -> ${next.activationState}`
|
|
6793
|
+
);
|
|
6794
|
+
}
|
|
6795
|
+
}
|
|
6796
|
+
function assertSchemaManifestPrivacyTransition(previous, next) {
|
|
6797
|
+
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(
|
|
6798
|
+
previous.managedAdapters,
|
|
6799
|
+
next.managedAdapters
|
|
6800
|
+
) || 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))
|
|
6801
|
+
throw new Error("MANCODE_PRIVACY_MANIFEST_TRANSITION_INVALID");
|
|
6802
|
+
}
|
|
6803
|
+
function parsePrivacyManifestPlanningDefaults(value) {
|
|
6804
|
+
assertRecord(value, "workflow policy defaults");
|
|
6805
|
+
assertKnownKeys(value, ["planning"], "workflow policy defaults");
|
|
6806
|
+
if (value.planning !== 1 && value.planning !== 2)
|
|
6807
|
+
throw new Error("schema manifest planning policy must be 1 or 2");
|
|
6808
|
+
return { planning: value.planning };
|
|
6809
|
+
}
|
|
6810
|
+
function parsePrivacyPolicyReference(value) {
|
|
6811
|
+
assertRecord(value, "privacy policy reference");
|
|
6812
|
+
assertKnownKeys(value, ["revision", "digest"], "privacy policy reference");
|
|
6813
|
+
if (!Number.isSafeInteger(value.revision) || value.revision < 1 || typeof value.digest !== "string" || !DIGEST_PATTERN13.test(value.digest))
|
|
6814
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REFERENCE_INVALID");
|
|
6815
|
+
return { revision: value.revision, digest: value.digest };
|
|
6816
|
+
}
|
|
6817
|
+
function assertSchemaManifestPolicyUpgrade(previous, next) {
|
|
6818
|
+
const legacyUpgrade = previous.manifestVersion === 1 && next.manifestVersion === 2;
|
|
6819
|
+
const privacyUpgrade = previous.manifestVersion === 3 && next.manifestVersion === 3 && previous.workflowPolicyDefaults.planning === 1 && previous.privacyPolicy.revision === next.privacyPolicy.revision && previous.privacyPolicy.digest === next.privacyPolicy.digest;
|
|
6820
|
+
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(
|
|
6821
|
+
previous.managedAdapters,
|
|
6822
|
+
next.managedAdapters
|
|
6823
|
+
) || compareVersions(next.minReaderVersion, previous.minReaderVersion) < 0 || compareVersions(next.minWriterVersion, previous.minWriterVersion) < 0 || next.workflowPolicyDefaults.planning !== 2 || next.lastOperationId === null || next.lastOperationId === previous.lastOperationId) {
|
|
6824
|
+
throw new Error("invalid schema manifest Policy 2 upgrade");
|
|
6825
|
+
}
|
|
6826
|
+
}
|
|
6827
|
+
function assertActivationRollbackManifestTransition(previous, next) {
|
|
6828
|
+
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) {
|
|
6829
|
+
throw new Error("invalid activation rollback manifest transition");
|
|
6830
|
+
}
|
|
6831
|
+
}
|
|
6832
|
+
function parseWorkflowPolicyDefaults(value) {
|
|
6833
|
+
assertRecord(value, "schema manifest workflowPolicyDefaults");
|
|
6834
|
+
assertKnownKeys(
|
|
6835
|
+
value,
|
|
6836
|
+
["planning"],
|
|
6837
|
+
"schema manifest workflowPolicyDefaults"
|
|
5722
6838
|
);
|
|
6839
|
+
if (value.planning !== 2) {
|
|
6840
|
+
throw new Error(
|
|
6841
|
+
`MANCODE_POLICY_VERSION_UNSUPPORTED: component=planning observed=${String(value.planning)} supported=2 requiredWriter=0.4.0`
|
|
6842
|
+
);
|
|
6843
|
+
}
|
|
6844
|
+
return { planning: 2 };
|
|
6845
|
+
}
|
|
6846
|
+
function parseVersion(value, label) {
|
|
6847
|
+
if (typeof value !== "string" || !VERSION_PATTERN.test(value)) {
|
|
6848
|
+
throw new Error(`schema manifest ${label} must be a semantic version`);
|
|
6849
|
+
}
|
|
6850
|
+
return value;
|
|
6851
|
+
}
|
|
6852
|
+
function parseTimestampOrNull3(value, label) {
|
|
6853
|
+
if (value === null) return null;
|
|
6854
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
6855
|
+
throw new Error(
|
|
6856
|
+
`schema manifest ${label} must be an ISO timestamp or null`
|
|
6857
|
+
);
|
|
6858
|
+
}
|
|
6859
|
+
return value;
|
|
5723
6860
|
}
|
|
5724
|
-
function
|
|
5725
|
-
|
|
6861
|
+
function parseLegacyBaseline(value) {
|
|
6862
|
+
if (value === null) return null;
|
|
6863
|
+
assertRecord(value, "schema manifest legacyBaseline");
|
|
5726
6864
|
assertKnownKeys(
|
|
5727
6865
|
value,
|
|
5728
|
-
["
|
|
5729
|
-
"
|
|
6866
|
+
["stateDigest", "workflowIndexDigest"],
|
|
6867
|
+
"schema manifest legacyBaseline"
|
|
5730
6868
|
);
|
|
5731
|
-
if (value.
|
|
5732
|
-
throw new Error(
|
|
6869
|
+
if (typeof value.stateDigest !== "string" || !DIGEST_PATTERN13.test(value.stateDigest) || typeof value.workflowIndexDigest !== "string" || !DIGEST_PATTERN13.test(value.workflowIndexDigest)) {
|
|
6870
|
+
throw new Error(
|
|
6871
|
+
"schema manifest legacyBaseline must contain sha256 digests"
|
|
6872
|
+
);
|
|
6873
|
+
}
|
|
6874
|
+
return {
|
|
6875
|
+
stateDigest: value.stateDigest,
|
|
6876
|
+
workflowIndexDigest: value.workflowIndexDigest
|
|
6877
|
+
};
|
|
6878
|
+
}
|
|
6879
|
+
function parseManagedAdapters(value) {
|
|
6880
|
+
assertRecord(value, "schema manifest managedAdapters");
|
|
6881
|
+
assertKnownKeys(value, MANAGED_ADAPTERS, "schema manifest managedAdapters");
|
|
6882
|
+
const adapters = {};
|
|
6883
|
+
for (const adapter of MANAGED_ADAPTERS) {
|
|
6884
|
+
const version = value[adapter];
|
|
6885
|
+
if (version === void 0) continue;
|
|
6886
|
+
if (typeof version !== "string" || !version.trim()) {
|
|
6887
|
+
throw new Error(
|
|
6888
|
+
`schema manifest managedAdapters.${adapter} must be a non-empty version`
|
|
6889
|
+
);
|
|
6890
|
+
}
|
|
6891
|
+
adapters[adapter] = version;
|
|
6892
|
+
}
|
|
6893
|
+
return adapters;
|
|
6894
|
+
}
|
|
6895
|
+
function parseUlidOrNull8(value, label) {
|
|
6896
|
+
if (value === null) return null;
|
|
6897
|
+
assertUlid(value, `schema manifest ${label}`);
|
|
6898
|
+
return value;
|
|
6899
|
+
}
|
|
6900
|
+
function assertManifestStateShape(manifest) {
|
|
6901
|
+
if (manifest.activationState === "initializing" && manifest.legacyBaseline !== null) {
|
|
6902
|
+
throw new Error(
|
|
6903
|
+
"greenfield initializing manifests must not have a legacy baseline"
|
|
6904
|
+
);
|
|
6905
|
+
}
|
|
6906
|
+
if ((manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.legacyBaseline === null) {
|
|
6907
|
+
throw new Error(
|
|
6908
|
+
`${manifest.activationState} manifests require a legacy baseline`
|
|
6909
|
+
);
|
|
6910
|
+
}
|
|
6911
|
+
if (manifest.activationState === "v3_active" && manifest.activatedAt === null) {
|
|
6912
|
+
throw new Error("v3_active manifests require activatedAt");
|
|
6913
|
+
}
|
|
6914
|
+
if ((manifest.activationState === "initializing" || manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.activatedAt !== null) {
|
|
6915
|
+
throw new Error(
|
|
6916
|
+
`${manifest.activationState} manifests must not have activatedAt`
|
|
6917
|
+
);
|
|
6918
|
+
}
|
|
6919
|
+
}
|
|
6920
|
+
function managedAdapterInventoriesMatch(left, right) {
|
|
6921
|
+
const leftKeys = managedAdapterNames(left);
|
|
6922
|
+
const rightKeys = managedAdapterNames(right);
|
|
6923
|
+
return leftKeys.length === rightKeys.length && leftKeys.every(
|
|
6924
|
+
(adapter, index) => adapter === rightKeys[index] && left[adapter] === right[adapter]
|
|
6925
|
+
);
|
|
6926
|
+
}
|
|
6927
|
+
function compareVersions(left, right) {
|
|
6928
|
+
const [leftCore = "", leftPrerelease] = left.split("-", 2);
|
|
6929
|
+
const [rightCore = "", rightPrerelease] = right.split("-", 2);
|
|
6930
|
+
const leftParts = leftCore.split(".").map(Number);
|
|
6931
|
+
const rightParts = rightCore.split(".").map(Number);
|
|
6932
|
+
for (let index = 0; index < 3; index += 1) {
|
|
6933
|
+
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
6934
|
+
if (delta !== 0) return delta;
|
|
6935
|
+
}
|
|
6936
|
+
if (leftPrerelease === rightPrerelease) return 0;
|
|
6937
|
+
if (leftPrerelease === void 0) return 1;
|
|
6938
|
+
if (rightPrerelease === void 0) return -1;
|
|
6939
|
+
return comparePrerelease(leftPrerelease, rightPrerelease);
|
|
6940
|
+
}
|
|
6941
|
+
function comparePrerelease(left, right) {
|
|
6942
|
+
const leftParts = left.split(".");
|
|
6943
|
+
const rightParts = right.split(".");
|
|
6944
|
+
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
6945
|
+
const leftPart = leftParts[index];
|
|
6946
|
+
const rightPart = rightParts[index];
|
|
6947
|
+
if (leftPart === void 0) return -1;
|
|
6948
|
+
if (rightPart === void 0) return 1;
|
|
6949
|
+
if (leftPart === rightPart) continue;
|
|
6950
|
+
const leftNumeric = /^\d+$/.test(leftPart);
|
|
6951
|
+
const rightNumeric = /^\d+$/.test(rightPart);
|
|
6952
|
+
if (leftNumeric && rightNumeric)
|
|
6953
|
+
return Number(leftPart) - Number(rightPart);
|
|
6954
|
+
if (leftNumeric) return -1;
|
|
6955
|
+
if (rightNumeric) return 1;
|
|
6956
|
+
return leftPart.localeCompare(rightPart, "en");
|
|
6957
|
+
}
|
|
6958
|
+
return 0;
|
|
6959
|
+
}
|
|
6960
|
+
function sameLegacyBaseline(left, right) {
|
|
6961
|
+
return left === right || left !== null && right !== null && left.stateDigest === right.stateDigest && left.workflowIndexDigest === right.workflowIndexDigest;
|
|
6962
|
+
}
|
|
6963
|
+
function allowedManifestTransitions(from) {
|
|
6964
|
+
switch (from) {
|
|
6965
|
+
case "initializing":
|
|
6966
|
+
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6967
|
+
case "dual_read":
|
|
6968
|
+
return /* @__PURE__ */ new Set(["activating", "repair_required"]);
|
|
6969
|
+
case "activating":
|
|
6970
|
+
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6971
|
+
case "repair_required":
|
|
6972
|
+
return /* @__PURE__ */ new Set(["v3_active"]);
|
|
6973
|
+
case "v3_active":
|
|
6974
|
+
return /* @__PURE__ */ new Set(["repair_required"]);
|
|
5733
6975
|
}
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
6976
|
+
}
|
|
6977
|
+
|
|
6978
|
+
// src/context/privacy-policy.ts
|
|
6979
|
+
var PRIVACY_MIN_VERSION = "0.6.5";
|
|
6980
|
+
var PRIVACY_POLICY_FILE = "shared/context/privacy-policy.json";
|
|
6981
|
+
var PRIVACY_EXCLUSIONS_FILE = "shared/context/privacy-exclusions.json";
|
|
6982
|
+
function parsePrivacyPolicyCandidate(value) {
|
|
6983
|
+
assertRecord(value, "privacy policy candidate");
|
|
6984
|
+
assertKnownKeys(
|
|
6985
|
+
value,
|
|
6986
|
+
["schemaVersion", "enabled", "rulesetVersion", "enabledRuleIds"],
|
|
6987
|
+
"privacy policy candidate"
|
|
6988
|
+
);
|
|
6989
|
+
if (value.schemaVersion !== 1 || typeof value.enabled !== "boolean")
|
|
6990
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
6991
|
+
if (value.rulesetVersion !== RULESET_VERSION)
|
|
6992
|
+
throw new Error("MANCODE_PRIVACY_RULESET_UNSUPPORTED");
|
|
6993
|
+
if (!Array.isArray(value.enabledRuleIds) || value.enabledRuleIds.some(
|
|
6994
|
+
(id) => typeof id !== "string" || !DEFAULT_RULE_IDS.includes(id)
|
|
6995
|
+
) || new Set(value.enabledRuleIds).size !== value.enabledRuleIds.length)
|
|
6996
|
+
throw new Error("MANCODE_PRIVACY_RULE_UNSUPPORTED");
|
|
6997
|
+
if (value.enabled && value.enabledRuleIds.length === 0)
|
|
6998
|
+
throw new Error("MANCODE_PRIVACY_EMPTY_RULESET");
|
|
6999
|
+
return {
|
|
7000
|
+
schemaVersion: 1,
|
|
7001
|
+
enabled: value.enabled,
|
|
7002
|
+
rulesetVersion: value.rulesetVersion,
|
|
7003
|
+
enabledRuleIds: [...value.enabledRuleIds].sort()
|
|
7004
|
+
};
|
|
7005
|
+
}
|
|
7006
|
+
function parsePrivacyPolicy(value) {
|
|
7007
|
+
assertRecord(value, "privacy policy");
|
|
7008
|
+
assertKnownKeys(
|
|
7009
|
+
value,
|
|
7010
|
+
[
|
|
7011
|
+
"schemaVersion",
|
|
7012
|
+
"revision",
|
|
7013
|
+
"workspaceId",
|
|
7014
|
+
"enabled",
|
|
7015
|
+
"rulesetVersion",
|
|
7016
|
+
"enabledRuleIds",
|
|
7017
|
+
"targets",
|
|
7018
|
+
"exclusions",
|
|
7019
|
+
"lastOperationId",
|
|
7020
|
+
"updatedAt"
|
|
7021
|
+
],
|
|
7022
|
+
"privacy policy"
|
|
7023
|
+
);
|
|
7024
|
+
const candidate = parsePrivacyPolicyCandidate({
|
|
7025
|
+
schemaVersion: value.schemaVersion,
|
|
7026
|
+
enabled: value.enabled,
|
|
7027
|
+
rulesetVersion: value.rulesetVersion,
|
|
7028
|
+
enabledRuleIds: value.enabledRuleIds
|
|
7029
|
+
});
|
|
7030
|
+
assertUlid(value.workspaceId, "privacy workspaceId");
|
|
7031
|
+
assertUlid(value.lastOperationId, "privacy lastOperationId");
|
|
7032
|
+
if (!Array.isArray(value.targets) || value.targets.length !== 2 || value.targets[0] !== "shared_write" || value.targets[1] !== "context_output")
|
|
7033
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7034
|
+
assertRecord(value.exclusions, "privacy exclusions reference");
|
|
7035
|
+
assertKnownKeys(
|
|
7036
|
+
value.exclusions,
|
|
7037
|
+
["revision", "digest"],
|
|
7038
|
+
"privacy exclusions reference"
|
|
7039
|
+
);
|
|
7040
|
+
return {
|
|
7041
|
+
...candidate,
|
|
7042
|
+
revision: revision(value.revision),
|
|
7043
|
+
workspaceId: value.workspaceId,
|
|
7044
|
+
targets: ["shared_write", "context_output"],
|
|
7045
|
+
exclusions: {
|
|
7046
|
+
revision: revision(value.exclusions.revision),
|
|
7047
|
+
digest: digest(value.exclusions.digest)
|
|
7048
|
+
},
|
|
7049
|
+
lastOperationId: value.lastOperationId,
|
|
7050
|
+
updatedAt: timestamp(value.updatedAt)
|
|
7051
|
+
};
|
|
7052
|
+
}
|
|
7053
|
+
function parsePrivacyExclusions(value) {
|
|
7054
|
+
assertRecord(value, "privacy exclusions");
|
|
7055
|
+
assertKnownKeys(
|
|
7056
|
+
value,
|
|
7057
|
+
[
|
|
7058
|
+
"schemaVersion",
|
|
7059
|
+
"revision",
|
|
7060
|
+
"workspaceId",
|
|
7061
|
+
"entries",
|
|
7062
|
+
"lastOperationId",
|
|
7063
|
+
"updatedAt"
|
|
7064
|
+
],
|
|
7065
|
+
"privacy exclusions"
|
|
7066
|
+
);
|
|
7067
|
+
if (value.schemaVersion !== 1 || !Array.isArray(value.entries))
|
|
7068
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSIONS_INVALID");
|
|
7069
|
+
assertUlid(value.workspaceId, "privacy exclusions workspaceId");
|
|
7070
|
+
assertUlid(value.lastOperationId, "privacy exclusions operationId");
|
|
7071
|
+
const entries = value.entries.map((entry) => {
|
|
7072
|
+
assertRecord(entry, "privacy exclusion");
|
|
7073
|
+
assertKnownKeys(
|
|
7074
|
+
entry,
|
|
7075
|
+
["kind", "relativePath", "entityDigest"],
|
|
7076
|
+
"privacy exclusion"
|
|
7077
|
+
);
|
|
7078
|
+
const ulid = "[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}";
|
|
7079
|
+
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;
|
|
7080
|
+
if (pattern === null || typeof entry.relativePath !== "string" || !pattern.test(entry.relativePath))
|
|
7081
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSION_PATH_INVALID");
|
|
7082
|
+
return {
|
|
7083
|
+
kind: entry.kind,
|
|
7084
|
+
relativePath: entry.relativePath,
|
|
7085
|
+
entityDigest: digest(entry.entityDigest)
|
|
7086
|
+
};
|
|
7087
|
+
});
|
|
7088
|
+
if (new Set(entries.map((entry) => entry.relativePath)).size !== entries.length)
|
|
7089
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSIONS_DUPLICATE");
|
|
7090
|
+
return {
|
|
7091
|
+
schemaVersion: 1,
|
|
7092
|
+
revision: revision(value.revision),
|
|
7093
|
+
workspaceId: value.workspaceId,
|
|
7094
|
+
entries: entries.sort(
|
|
7095
|
+
(a, b) => a.relativePath.localeCompare(b.relativePath, "en")
|
|
7096
|
+
),
|
|
7097
|
+
lastOperationId: value.lastOperationId,
|
|
7098
|
+
updatedAt: timestamp(value.updatedAt)
|
|
7099
|
+
};
|
|
7100
|
+
}
|
|
7101
|
+
function createInitialPrivacyPolicy(input) {
|
|
7102
|
+
const exclusions = parsePrivacyExclusions({
|
|
7103
|
+
schemaVersion: 1,
|
|
7104
|
+
revision: 1,
|
|
7105
|
+
workspaceId: input.workspaceId,
|
|
7106
|
+
entries: [],
|
|
7107
|
+
lastOperationId: input.operationId,
|
|
7108
|
+
updatedAt: input.now
|
|
7109
|
+
});
|
|
7110
|
+
const policy = parsePrivacyPolicy({
|
|
7111
|
+
schemaVersion: 1,
|
|
7112
|
+
revision: 1,
|
|
7113
|
+
workspaceId: input.workspaceId,
|
|
7114
|
+
enabled: input.enabled ?? true,
|
|
7115
|
+
rulesetVersion: RULESET_VERSION,
|
|
7116
|
+
enabledRuleIds: [...DEFAULT_RULE_IDS],
|
|
7117
|
+
targets: ["shared_write", "context_output"],
|
|
7118
|
+
exclusions: { revision: 1, digest: digestCanonicalJson(exclusions) },
|
|
7119
|
+
lastOperationId: input.operationId,
|
|
7120
|
+
updatedAt: input.now
|
|
7121
|
+
});
|
|
7122
|
+
return { policy, exclusions, digest: digestCanonicalJson(policy) };
|
|
7123
|
+
}
|
|
7124
|
+
function assertPrivacyPolicyTransition(previous, next) {
|
|
7125
|
+
if (next.revision !== (previous?.revision ?? 0) + 1 || previous !== null && (previous.workspaceId !== next.workspaceId || previous.lastOperationId === next.lastOperationId))
|
|
7126
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REVISION_CONFLICT");
|
|
7127
|
+
}
|
|
7128
|
+
function assertPrivacyExclusionsTransition(previous, next) {
|
|
7129
|
+
if (next.revision !== (previous?.revision ?? 0) + 1 || previous !== null && previous.workspaceId !== next.workspaceId)
|
|
7130
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REVISION_CONFLICT");
|
|
7131
|
+
if (previous?.entries.some(
|
|
7132
|
+
(entry) => !next.entries.some(
|
|
7133
|
+
(nextEntry) => nextEntry.relativePath === entry.relativePath && nextEntry.entityDigest === entry.entityDigest
|
|
7134
|
+
)
|
|
7135
|
+
))
|
|
7136
|
+
throw new Error("MANCODE_PRIVACY_EXCLUSION_REMOVAL_FORBIDDEN");
|
|
7137
|
+
}
|
|
7138
|
+
async function readPrivacyPolicySnapshot(root, manifest) {
|
|
7139
|
+
await assertNoPendingPrivacyPolicyOperation(root);
|
|
7140
|
+
const current = manifest ?? parseSchemaManifest(
|
|
7141
|
+
JSON.parse(await readPrivacyAuthorityFile(root, "schema.json"))
|
|
5737
7142
|
);
|
|
5738
|
-
if (
|
|
5739
|
-
|
|
7143
|
+
if (current.manifestVersion !== 3) return null;
|
|
7144
|
+
const [policy, exclusions, config] = await Promise.all([
|
|
7145
|
+
readPrivacyAuthorityFile(root, PRIVACY_POLICY_FILE).then(
|
|
7146
|
+
(content) => parsePrivacyPolicy(JSON.parse(content))
|
|
7147
|
+
),
|
|
7148
|
+
readPrivacyAuthorityFile(root, PRIVACY_EXCLUSIONS_FILE).then(
|
|
7149
|
+
(content) => parsePrivacyExclusions(JSON.parse(content))
|
|
7150
|
+
),
|
|
7151
|
+
readPrivacyAuthorityFile(root, "shared/config.json").then(
|
|
7152
|
+
(content) => parseProjectConfig(JSON.parse(content))
|
|
7153
|
+
)
|
|
7154
|
+
]);
|
|
7155
|
+
const policyDigest = digestCanonicalJson(policy);
|
|
7156
|
+
if (policy.workspaceId !== config.workspaceId)
|
|
7157
|
+
throw new Error("MANCODE_PRIVACY_POLICY_WORKSPACE_MISMATCH");
|
|
7158
|
+
if (current.privacyPolicy.revision !== policy.revision || current.privacyPolicy.digest !== policyDigest || policy.workspaceId !== exclusions.workspaceId || policy.exclusions.revision !== exclusions.revision || policy.exclusions.digest !== digestCanonicalJson(exclusions))
|
|
7159
|
+
throw new Error("MANCODE_PRIVACY_POLICY_DIGEST_MISMATCH");
|
|
7160
|
+
return { policy, exclusions, digest: policyDigest };
|
|
7161
|
+
}
|
|
7162
|
+
async function assertNoPendingPrivacyPolicyOperation(root) {
|
|
7163
|
+
const relative = "local/runtime/operations";
|
|
7164
|
+
let entries;
|
|
7165
|
+
try {
|
|
7166
|
+
const directory = path6.join(root, ".mancode", relative);
|
|
7167
|
+
const stat2 = await lstat3(directory);
|
|
7168
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
7169
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7170
|
+
entries = await readdir3(directory);
|
|
7171
|
+
} catch (error) {
|
|
7172
|
+
if (error !== null && typeof error === "object" && "code" in error && error.code === "ENOENT")
|
|
7173
|
+
return;
|
|
7174
|
+
throw error;
|
|
7175
|
+
}
|
|
7176
|
+
for (const entry of entries) {
|
|
7177
|
+
if (!/^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}\.json$/.test(entry)) continue;
|
|
7178
|
+
const journal = JSON.parse(
|
|
7179
|
+
await readPrivacyAuthorityFile(root, `${relative}/${entry}`)
|
|
7180
|
+
);
|
|
7181
|
+
if (journal.type === "privacy_policy_update" && journal.state !== "committed" && journal.state !== "aborted")
|
|
7182
|
+
throw new Error("MANCODE_PRIVACY_POLICY_REPAIR_REQUIRED");
|
|
7183
|
+
}
|
|
7184
|
+
}
|
|
7185
|
+
async function readPrivacyPolicyStatus(root) {
|
|
7186
|
+
try {
|
|
7187
|
+
const snapshot = await readPrivacyPolicySnapshot(root);
|
|
7188
|
+
return {
|
|
7189
|
+
schemaVersion: 1,
|
|
7190
|
+
state: snapshot === null ? "unconfigured" : snapshot.policy.enabled ? "enabled" : "disabled",
|
|
7191
|
+
enabled: snapshot?.policy.enabled ?? false,
|
|
7192
|
+
revision: snapshot?.policy.revision ?? 0,
|
|
7193
|
+
digest: snapshot?.digest ?? null,
|
|
7194
|
+
rulesetVersion: snapshot?.policy.rulesetVersion ?? RULESET_VERSION,
|
|
7195
|
+
excludedEntities: snapshot?.exclusions.entries.length ?? 0,
|
|
7196
|
+
scope: "project",
|
|
7197
|
+
error: null
|
|
7198
|
+
};
|
|
7199
|
+
} catch {
|
|
7200
|
+
return {
|
|
7201
|
+
schemaVersion: 1,
|
|
7202
|
+
state: "error",
|
|
7203
|
+
enabled: null,
|
|
7204
|
+
revision: null,
|
|
7205
|
+
digest: null,
|
|
7206
|
+
rulesetVersion: null,
|
|
7207
|
+
excludedEntities: null,
|
|
7208
|
+
scope: "project",
|
|
7209
|
+
error: "MANCODE_PRIVACY_POLICY_UNAVAILABLE"
|
|
7210
|
+
};
|
|
7211
|
+
}
|
|
7212
|
+
}
|
|
7213
|
+
async function readPrivacyAuthorityFile(root, relative) {
|
|
7214
|
+
let current = path6.join(path6.resolve(root), ".mancode");
|
|
7215
|
+
for (const part of relative.split("/")) {
|
|
7216
|
+
const parent = await lstat3(current);
|
|
7217
|
+
if (!parent.isDirectory() || parent.isSymbolicLink())
|
|
7218
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7219
|
+
current = path6.join(current, part);
|
|
5740
7220
|
}
|
|
5741
|
-
|
|
5742
|
-
|
|
7221
|
+
const stat2 = await lstat3(current);
|
|
7222
|
+
if (!stat2.isFile() || stat2.isSymbolicLink() || stat2.size > 8 * 1024 * 1024)
|
|
7223
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7224
|
+
return readFile5(current, "utf8");
|
|
7225
|
+
}
|
|
7226
|
+
function revision(value) {
|
|
7227
|
+
if (!Number.isSafeInteger(value) || value < 1)
|
|
7228
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7229
|
+
return value;
|
|
7230
|
+
}
|
|
7231
|
+
function digest(value) {
|
|
7232
|
+
if (typeof value !== "string" || !/^sha256:[a-f0-9]{64}$/.test(value))
|
|
7233
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7234
|
+
return value;
|
|
7235
|
+
}
|
|
7236
|
+
function timestamp(value) {
|
|
7237
|
+
if (typeof value !== "string" || !Number.isFinite(Date.parse(value)))
|
|
7238
|
+
throw new Error("MANCODE_PRIVACY_POLICY_INVALID");
|
|
7239
|
+
return value;
|
|
7240
|
+
}
|
|
7241
|
+
|
|
7242
|
+
// src/context/privacy-guard.ts
|
|
7243
|
+
function containsEnhancedSensitiveText(value, ruleIds) {
|
|
7244
|
+
const stack = [[value]];
|
|
7245
|
+
let visited = 0;
|
|
7246
|
+
while (stack.length > 0) {
|
|
7247
|
+
if (++visited > 1e5) throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
7248
|
+
const [item, key] = stack.pop();
|
|
7249
|
+
if (typeof item === "string") {
|
|
7250
|
+
const scan = scanSensitiveText(
|
|
7251
|
+
key === void 0 || item.length === 0 ? item : `${key}: ${item}`,
|
|
7252
|
+
ruleIds
|
|
7253
|
+
);
|
|
7254
|
+
if (scan.status !== "complete")
|
|
7255
|
+
throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
7256
|
+
if (scan.findings.length > 0) return true;
|
|
7257
|
+
} else if (Array.isArray(item)) {
|
|
7258
|
+
stack.push(...item.map((content) => [content, key]));
|
|
7259
|
+
} else if (item !== null && typeof item === "object") {
|
|
7260
|
+
for (const [key2, content] of Object.entries(item))
|
|
7261
|
+
stack.push([key2], [content, key2]);
|
|
7262
|
+
}
|
|
5743
7263
|
}
|
|
5744
|
-
|
|
5745
|
-
return { mode: value.mode, remote, epoch };
|
|
7264
|
+
return false;
|
|
5746
7265
|
}
|
|
5747
|
-
function
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
7266
|
+
function isPrivacyExcluded(snapshot, value) {
|
|
7267
|
+
if (snapshot === null || snapshot === void 0 || snapshot.exclusions.entries.length === 0)
|
|
7268
|
+
return false;
|
|
7269
|
+
const entityDigest = digestCanonicalJson(value);
|
|
7270
|
+
return snapshot.exclusions.entries.some(
|
|
7271
|
+
(entry) => entry.entityDigest === entityDigest
|
|
5753
7272
|
);
|
|
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
|
-
};
|
|
5768
7273
|
}
|
|
5769
|
-
function
|
|
5770
|
-
if (
|
|
5771
|
-
throw new Error(
|
|
5772
|
-
|
|
5773
|
-
);
|
|
7274
|
+
function assertPrivacyValueAllowed(snapshot, value) {
|
|
7275
|
+
if (isPrivacyExcluded(snapshot, value))
|
|
7276
|
+
throw new Error("MANCODE_PRIVACY_ENTITY_EXCLUDED");
|
|
7277
|
+
if (snapshot?.policy.enabled && containsEnhancedSensitiveText(value, snapshot.policy.enabledRuleIds))
|
|
7278
|
+
throw new Error("MANCODE_PRIVACY_BLOCKED");
|
|
7279
|
+
}
|
|
7280
|
+
async function assertSharedPrivacyValue(root, value) {
|
|
7281
|
+
assertPrivacyValueAllowed(await readSharedPrivacySnapshot(root), value);
|
|
7282
|
+
}
|
|
7283
|
+
async function readSharedPrivacySnapshot(root) {
|
|
7284
|
+
if (!await hasProjectAuthority(root)) return null;
|
|
7285
|
+
return readPrivacyPolicySnapshot(root);
|
|
7286
|
+
}
|
|
7287
|
+
async function acquireSharedPrivacyWriteBarrier(root, operationId) {
|
|
7288
|
+
if (!await hasProjectAuthority(root)) return null;
|
|
7289
|
+
const runtime = await readProjectRuntimeContext(root);
|
|
7290
|
+
const deadline = performance.now() + 5e3;
|
|
7291
|
+
for (; ; ) {
|
|
7292
|
+
try {
|
|
7293
|
+
return await acquireProjectWriteBarrier(runtime, operationId, /* @__PURE__ */ new Date());
|
|
7294
|
+
} catch (error) {
|
|
7295
|
+
if (!(error instanceof Error) || error.message !== "MANCODE_LOCK_HELD" || performance.now() >= deadline)
|
|
7296
|
+
throw error;
|
|
7297
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
7298
|
+
}
|
|
5774
7299
|
}
|
|
5775
7300
|
}
|
|
5776
|
-
function
|
|
5777
|
-
|
|
5778
|
-
|
|
7301
|
+
async function withSharedPrivacyWrite(root, operationId, value, write2) {
|
|
7302
|
+
await assertSharedPrivacyValue(root, value);
|
|
7303
|
+
const barrier = await acquireSharedPrivacyWriteBarrier(root, operationId);
|
|
7304
|
+
try {
|
|
7305
|
+
await assertSharedPrivacyValue(root, value);
|
|
7306
|
+
return await write2();
|
|
7307
|
+
} finally {
|
|
7308
|
+
await barrier?.release();
|
|
5779
7309
|
}
|
|
5780
7310
|
}
|
|
5781
|
-
function
|
|
5782
|
-
|
|
5783
|
-
|
|
7311
|
+
async function assertSharedTaskWriteAtRoot(taskRoot, fileName, content) {
|
|
7312
|
+
const workflows = path7.dirname(taskRoot);
|
|
7313
|
+
const shared = path7.dirname(workflows);
|
|
7314
|
+
const authority = path7.dirname(shared);
|
|
7315
|
+
if (path7.basename(workflows) !== "workflows" || path7.basename(shared) !== "shared" || path7.basename(authority) !== ".mancode")
|
|
7316
|
+
return;
|
|
7317
|
+
await assertSharedPrivacyValue(
|
|
7318
|
+
path7.dirname(authority),
|
|
7319
|
+
privacyContentValue(fileName, content)
|
|
7320
|
+
);
|
|
7321
|
+
}
|
|
7322
|
+
function assertPrivacyRecoveryActionsAllowed(snapshot, actions) {
|
|
7323
|
+
for (const action of actions) {
|
|
7324
|
+
switch (action.kind) {
|
|
7325
|
+
case "task_authority_file":
|
|
7326
|
+
if (action.taskRef.namespace === "shared")
|
|
7327
|
+
assertPrivacyValueAllowed(
|
|
7328
|
+
snapshot,
|
|
7329
|
+
privacyContentValue(action.fileName, action.targetContent)
|
|
7330
|
+
);
|
|
7331
|
+
break;
|
|
7332
|
+
case "workflow_task_directory":
|
|
7333
|
+
case "migration_task_directory":
|
|
7334
|
+
if (action.taskRef.namespace === "shared") {
|
|
7335
|
+
for (const file of action.files)
|
|
7336
|
+
assertPrivacyValueAllowed(
|
|
7337
|
+
snapshot,
|
|
7338
|
+
privacyContentValue(file.fileName, file.content)
|
|
7339
|
+
);
|
|
7340
|
+
if (action.kind === "migration_task_directory")
|
|
7341
|
+
for (const report of action.reports)
|
|
7342
|
+
assertPrivacyValueAllowed(snapshot, report.content);
|
|
7343
|
+
}
|
|
7344
|
+
break;
|
|
7345
|
+
case "task_archive":
|
|
7346
|
+
if (action.taskRef.namespace === "shared") {
|
|
7347
|
+
assertPrivacyValueAllowed(
|
|
7348
|
+
snapshot,
|
|
7349
|
+
privacyContentValue(
|
|
7350
|
+
"requirements.json",
|
|
7351
|
+
action.requirementsContent
|
|
7352
|
+
)
|
|
7353
|
+
);
|
|
7354
|
+
if (action.planContent !== null)
|
|
7355
|
+
assertPrivacyValueAllowed(snapshot, action.planContent);
|
|
7356
|
+
}
|
|
7357
|
+
break;
|
|
7358
|
+
case "checkpoint":
|
|
7359
|
+
if (action.checkpoint.taskRef.namespace === "shared")
|
|
7360
|
+
assertPrivacyValueAllowed(snapshot, action.checkpoint);
|
|
7361
|
+
break;
|
|
7362
|
+
case "claim":
|
|
7363
|
+
if (action.claim.taskRef.namespace === "shared")
|
|
7364
|
+
assertPrivacyValueAllowed(snapshot, action.claim);
|
|
7365
|
+
break;
|
|
7366
|
+
case "handoff":
|
|
7367
|
+
if (action.handoff.taskRef.namespace === "shared")
|
|
7368
|
+
assertPrivacyValueAllowed(snapshot, action.handoff);
|
|
7369
|
+
break;
|
|
7370
|
+
default:
|
|
7371
|
+
break;
|
|
7372
|
+
}
|
|
5784
7373
|
}
|
|
5785
|
-
return value;
|
|
5786
7374
|
}
|
|
5787
|
-
function
|
|
5788
|
-
|
|
5789
|
-
|
|
7375
|
+
async function hasProjectAuthority(root) {
|
|
7376
|
+
try {
|
|
7377
|
+
const stat2 = await lstat4(path7.join(root, ".mancode", "schema.json"));
|
|
7378
|
+
if (!stat2.isFile() || stat2.isSymbolicLink())
|
|
7379
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7380
|
+
return true;
|
|
7381
|
+
} catch (error) {
|
|
7382
|
+
if (error === null || typeof error !== "object" || !("code" in error) || error.code !== "ENOENT")
|
|
7383
|
+
throw error;
|
|
7384
|
+
try {
|
|
7385
|
+
await lstat4(path7.join(root, ".mancode", "shared", "config.json"));
|
|
7386
|
+
} catch (configError) {
|
|
7387
|
+
if (configError !== null && typeof configError === "object" && "code" in configError && configError.code === "ENOENT")
|
|
7388
|
+
return false;
|
|
7389
|
+
throw configError;
|
|
7390
|
+
}
|
|
7391
|
+
throw new Error("MANCODE_PRIVACY_POLICY_UNAVAILABLE");
|
|
5790
7392
|
}
|
|
5791
|
-
return value;
|
|
5792
7393
|
}
|
|
5793
|
-
function
|
|
5794
|
-
if (
|
|
5795
|
-
|
|
5796
|
-
|
|
7394
|
+
function privacyContentValue(fileName, content) {
|
|
7395
|
+
if (!fileName.endsWith(".json")) return content;
|
|
7396
|
+
try {
|
|
7397
|
+
return JSON.parse(content);
|
|
7398
|
+
} catch {
|
|
7399
|
+
throw new Error("MANCODE_PRIVACY_SHARED_JSON_INVALID");
|
|
7400
|
+
}
|
|
5797
7401
|
}
|
|
5798
|
-
function
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
7402
|
+
async function scanPrivacyActivation(root, ruleIds) {
|
|
7403
|
+
const files = await collectSharedFiles(root, "shared");
|
|
7404
|
+
const digests = [];
|
|
7405
|
+
const blockers = [];
|
|
7406
|
+
const exclusions = [];
|
|
7407
|
+
let scannedFiles = 0;
|
|
7408
|
+
for (const relative of files) {
|
|
7409
|
+
if (relative === PRIVACY_POLICY_FILE || relative === PRIVACY_EXCLUSIONS_FILE)
|
|
7410
|
+
continue;
|
|
7411
|
+
const content = await readPrivacyAuthorityFile(root, relative);
|
|
7412
|
+
digests.push({ path: relative, digest: digestCanonicalJson(content) });
|
|
7413
|
+
const fileIndex = scannedFiles++;
|
|
7414
|
+
let value;
|
|
7415
|
+
try {
|
|
7416
|
+
value = privacyContentValue(relative, content);
|
|
7417
|
+
} catch {
|
|
7418
|
+
blockers.push({ fileIndex, reason: "invalid_json" });
|
|
7419
|
+
continue;
|
|
7420
|
+
}
|
|
7421
|
+
if (!containsEnhancedSensitiveText(value, ruleIds)) continue;
|
|
7422
|
+
const ulid = "[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}";
|
|
7423
|
+
const kind = new RegExp(`^shared/memory/decisions/${ulid}\\.json$`).test(
|
|
7424
|
+
relative
|
|
7425
|
+
) ? "confirmed_decision" : new RegExp(
|
|
7426
|
+
`^shared/workflows/${ulid}/checkpoints/${ulid}\\.json$`
|
|
7427
|
+
).test(relative) ? "checkpoint" : null;
|
|
7428
|
+
if (kind !== null)
|
|
7429
|
+
exclusions.push({
|
|
7430
|
+
kind,
|
|
7431
|
+
relativePath: relative,
|
|
7432
|
+
entityDigest: digestCanonicalJson(value)
|
|
7433
|
+
});
|
|
7434
|
+
else blockers.push({ fileIndex, reason: "sensitive_content" });
|
|
5802
7435
|
}
|
|
5803
|
-
return
|
|
7436
|
+
return {
|
|
7437
|
+
fingerprint: digestCanonicalJson(digests),
|
|
7438
|
+
scannedFiles,
|
|
7439
|
+
blockers,
|
|
7440
|
+
exclusions
|
|
7441
|
+
};
|
|
5804
7442
|
}
|
|
5805
|
-
function
|
|
5806
|
-
|
|
5807
|
-
|
|
7443
|
+
async function collectSharedFiles(root, relative) {
|
|
7444
|
+
const target = path7.join(root, ".mancode", relative);
|
|
7445
|
+
const stat2 = await lstat4(target);
|
|
7446
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink())
|
|
7447
|
+
throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7448
|
+
const files = [];
|
|
7449
|
+
for (const entry of (await readdir4(target, { withFileTypes: true })).sort(
|
|
7450
|
+
(a, b) => a.name.localeCompare(b.name, "en")
|
|
7451
|
+
)) {
|
|
7452
|
+
if (entry.isSymbolicLink()) throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7453
|
+
const child = `${relative}/${entry.name}`;
|
|
7454
|
+
if (entry.isDirectory())
|
|
7455
|
+
files.push(...await collectSharedFiles(root, child));
|
|
7456
|
+
else if (entry.isFile()) files.push(child);
|
|
7457
|
+
else throw new Error("MANCODE_ARTIFACT_PATH_UNSAFE");
|
|
7458
|
+
if (files.length > 1e5)
|
|
7459
|
+
throw new Error("MANCODE_PRIVACY_SCAN_INCOMPLETE");
|
|
5808
7460
|
}
|
|
5809
|
-
return
|
|
7461
|
+
return files;
|
|
5810
7462
|
}
|
|
5811
7463
|
|
|
5812
7464
|
// 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
7465
|
var DECISION_FILENAME = /^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}\.json$/;
|
|
5816
7466
|
function createConfirmedDecision(input) {
|
|
5817
7467
|
assertUlid(input.decisionId, "confirmed decision decisionId");
|
|
@@ -5891,8 +7541,8 @@ function confirmedDecisionDigest(decision) {
|
|
|
5891
7541
|
return digestCanonicalJson(parseConfirmedDecision(decision));
|
|
5892
7542
|
}
|
|
5893
7543
|
function confirmedDecisionDirectory(projectRoot) {
|
|
5894
|
-
return
|
|
5895
|
-
|
|
7544
|
+
return path8.join(
|
|
7545
|
+
path8.resolve(projectRoot),
|
|
5896
7546
|
".mancode",
|
|
5897
7547
|
"shared",
|
|
5898
7548
|
"memory",
|
|
@@ -5901,421 +7551,162 @@ function confirmedDecisionDirectory(projectRoot) {
|
|
|
5901
7551
|
}
|
|
5902
7552
|
function confirmedDecisionPath(projectRoot, decisionId) {
|
|
5903
7553
|
assertUlid(decisionId, "confirmed decision decisionId");
|
|
5904
|
-
return
|
|
7554
|
+
return path8.join(
|
|
5905
7555
|
confirmedDecisionDirectory(projectRoot),
|
|
5906
7556
|
`${decisionId}.json`
|
|
5907
7557
|
);
|
|
5908
7558
|
}
|
|
5909
7559
|
async function publishConfirmedDecision(projectRoot, decision) {
|
|
7560
|
+
const parsed = parseConfirmedDecision(decision);
|
|
7561
|
+
return withSharedPrivacyWrite(
|
|
7562
|
+
projectRoot,
|
|
7563
|
+
parsed.operationId,
|
|
7564
|
+
parsed,
|
|
7565
|
+
() => publishConfirmedDecisionUnlocked(projectRoot, parsed)
|
|
7566
|
+
);
|
|
7567
|
+
}
|
|
7568
|
+
async function publishConfirmedDecisionUnlocked(projectRoot, decision) {
|
|
5910
7569
|
const parsed = parseConfirmedDecision(decision);
|
|
5911
7570
|
const directory = confirmedDecisionDirectory(projectRoot);
|
|
5912
7571
|
await ensureSafeDirectory(projectRoot, directory);
|
|
5913
7572
|
const target = confirmedDecisionPath(projectRoot, parsed.decisionId);
|
|
5914
7573
|
try {
|
|
5915
|
-
await
|
|
7574
|
+
await writeFile4(target, `${JSON.stringify(parsed, null, 2)}
|
|
5916
7575
|
`, {
|
|
5917
7576
|
encoding: "utf8",
|
|
5918
7577
|
flag: "wx"
|
|
5919
7578
|
});
|
|
5920
7579
|
return parsed;
|
|
5921
7580
|
} catch (error) {
|
|
5922
|
-
if (!
|
|
5923
|
-
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 parseSchemaManifestV2(value) {
|
|
6130
|
-
const manifest = parseSchemaManifest(value);
|
|
6131
|
-
if (manifest.manifestVersion !== 2) {
|
|
6132
|
-
throw new Error(
|
|
6133
|
-
`MANCODE_MANIFEST_VERSION_UNSUPPORTED: observed=${manifest.manifestVersion} supported=2 requiredWriter=0.4.0`
|
|
7581
|
+
if (!isAlreadyExists4(error)) throw error;
|
|
7582
|
+
const existing = await readConfirmedDecision(
|
|
7583
|
+
projectRoot,
|
|
7584
|
+
parsed.decisionId
|
|
6134
7585
|
);
|
|
7586
|
+
if (existing !== null && confirmedDecisionDigest(existing) === confirmedDecisionDigest(parsed)) {
|
|
7587
|
+
return existing;
|
|
7588
|
+
}
|
|
7589
|
+
throw new Error("MANCODE_CONFIRMED_DECISION_ID_CONFLICT");
|
|
6135
7590
|
}
|
|
6136
|
-
return manifest;
|
|
6137
7591
|
}
|
|
6138
|
-
function
|
|
6139
|
-
|
|
6140
|
-
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6147
|
-
|
|
6148
|
-
throw new Error(
|
|
6149
|
-
`invalid schema manifest transition: ${previous.activationState} -> ${next.activationState}`
|
|
7592
|
+
async function readConfirmedDecision(projectRoot, decisionId) {
|
|
7593
|
+
assertUlid(decisionId, "confirmed decision decisionId");
|
|
7594
|
+
try {
|
|
7595
|
+
return parseConfirmedDecision(
|
|
7596
|
+
JSON.parse(
|
|
7597
|
+
await readSafeText(
|
|
7598
|
+
confirmedDecisionDirectory(projectRoot),
|
|
7599
|
+
`${decisionId}.json`
|
|
7600
|
+
)
|
|
7601
|
+
)
|
|
6150
7602
|
);
|
|
7603
|
+
} catch (error) {
|
|
7604
|
+
if (isNotFound5(error)) return null;
|
|
7605
|
+
if (error instanceof SyntaxError) {
|
|
7606
|
+
throw new Error("MANCODE_CONFIRMED_DECISION_CORRUPT");
|
|
7607
|
+
}
|
|
7608
|
+
throw error;
|
|
6151
7609
|
}
|
|
6152
7610
|
}
|
|
6153
|
-
function
|
|
6154
|
-
|
|
6155
|
-
|
|
6156
|
-
|
|
6157
|
-
|
|
6158
|
-
|
|
6159
|
-
}
|
|
6160
|
-
|
|
6161
|
-
|
|
6162
|
-
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) {
|
|
6163
|
-
throw new Error("invalid activation rollback manifest transition");
|
|
7611
|
+
async function listConfirmedDecisions(projectRoot) {
|
|
7612
|
+
const directory = confirmedDecisionDirectory(projectRoot);
|
|
7613
|
+
let entries;
|
|
7614
|
+
try {
|
|
7615
|
+
await assertSafeDirectory(directory);
|
|
7616
|
+
entries = await readdir5(directory);
|
|
7617
|
+
} catch (error) {
|
|
7618
|
+
if (isNotFound5(error)) return [];
|
|
7619
|
+
throw error;
|
|
6164
7620
|
}
|
|
6165
|
-
|
|
6166
|
-
|
|
6167
|
-
|
|
6168
|
-
|
|
6169
|
-
|
|
6170
|
-
|
|
6171
|
-
|
|
6172
|
-
|
|
6173
|
-
|
|
6174
|
-
throw new Error(
|
|
6175
|
-
`MANCODE_POLICY_VERSION_UNSUPPORTED: component=planning observed=${String(value.planning)} supported=2 requiredWriter=0.4.0`
|
|
7621
|
+
const decisions = [];
|
|
7622
|
+
for (const entry of entries.sort(compareUtf83)) {
|
|
7623
|
+
if (!entry.endsWith(".json")) continue;
|
|
7624
|
+
if (!DECISION_FILENAME.test(entry)) {
|
|
7625
|
+
throw new Error("MANCODE_CONTEXT_COLLECTION_ENTRY_INVALID");
|
|
7626
|
+
}
|
|
7627
|
+
const decision = await readConfirmedDecision(
|
|
7628
|
+
projectRoot,
|
|
7629
|
+
entry.slice(0, -".json".length)
|
|
6176
7630
|
);
|
|
7631
|
+
if (decision === null) {
|
|
7632
|
+
throw new Error("MANCODE_CONTEXT_COLLECTION_CHANGED_DURING_READ");
|
|
7633
|
+
}
|
|
7634
|
+
decisions.push(decision);
|
|
6177
7635
|
}
|
|
6178
|
-
return
|
|
7636
|
+
return decisions.sort(
|
|
7637
|
+
(left, right) => compareUtf83(left.decisionId, right.decisionId)
|
|
7638
|
+
);
|
|
6179
7639
|
}
|
|
6180
|
-
function
|
|
6181
|
-
if (typeof value !== "string" || !
|
|
6182
|
-
throw new Error(
|
|
7640
|
+
function parseDecisionText(value, label, maxLength) {
|
|
7641
|
+
if (typeof value !== "string" || !value.trim() || value.includes("\0") || value.length > maxLength) {
|
|
7642
|
+
throw new Error(`${label} is invalid`);
|
|
6183
7643
|
}
|
|
7644
|
+
assertSharedTextSafe(value, label);
|
|
6184
7645
|
return value;
|
|
6185
7646
|
}
|
|
6186
|
-
function
|
|
6187
|
-
if (value === null) return null;
|
|
7647
|
+
function parseTimestamp13(value, label) {
|
|
6188
7648
|
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
6189
|
-
throw new Error(
|
|
6190
|
-
`schema manifest ${label} must be an ISO timestamp or null`
|
|
6191
|
-
);
|
|
7649
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
6192
7650
|
}
|
|
6193
7651
|
return value;
|
|
6194
7652
|
}
|
|
6195
|
-
function
|
|
6196
|
-
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
"schema manifest legacyBaseline"
|
|
6202
|
-
);
|
|
6203
|
-
if (typeof value.stateDigest !== "string" || !DIGEST_PATTERN13.test(value.stateDigest) || typeof value.workflowIndexDigest !== "string" || !DIGEST_PATTERN13.test(value.workflowIndexDigest)) {
|
|
6204
|
-
throw new Error(
|
|
6205
|
-
"schema manifest legacyBaseline must contain sha256 digests"
|
|
6206
|
-
);
|
|
7653
|
+
async function ensureSafeDirectory(projectRoot, directory) {
|
|
7654
|
+
const root = path8.resolve(projectRoot);
|
|
7655
|
+
const target = path8.resolve(directory);
|
|
7656
|
+
const relative = path8.relative(root, target);
|
|
7657
|
+
if (!relative || path8.isAbsolute(relative) || relative.split(path8.sep).some((segment) => segment === "..")) {
|
|
7658
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6207
7659
|
}
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
const adapters = {};
|
|
6217
|
-
for (const adapter of MANAGED_ADAPTERS) {
|
|
6218
|
-
const version = value[adapter];
|
|
6219
|
-
if (version === void 0) continue;
|
|
6220
|
-
if (typeof version !== "string" || !version.trim()) {
|
|
6221
|
-
throw new Error(
|
|
6222
|
-
`schema manifest managedAdapters.${adapter} must be a non-empty version`
|
|
6223
|
-
);
|
|
7660
|
+
await assertSafeDirectory(root);
|
|
7661
|
+
let current = root;
|
|
7662
|
+
for (const segment of relative.split(path8.sep)) {
|
|
7663
|
+
current = path8.join(current, segment);
|
|
7664
|
+
try {
|
|
7665
|
+
await mkdir4(current);
|
|
7666
|
+
} catch (error) {
|
|
7667
|
+
if (!isAlreadyExists4(error)) throw error;
|
|
6224
7668
|
}
|
|
6225
|
-
|
|
7669
|
+
await assertSafeDirectory(current);
|
|
6226
7670
|
}
|
|
6227
|
-
return adapters;
|
|
6228
|
-
}
|
|
6229
|
-
function parseUlidOrNull9(value, label) {
|
|
6230
|
-
if (value === null) return null;
|
|
6231
|
-
assertUlid(value, `schema manifest ${label}`);
|
|
6232
|
-
return value;
|
|
6233
7671
|
}
|
|
6234
|
-
function
|
|
6235
|
-
|
|
6236
|
-
|
|
6237
|
-
|
|
6238
|
-
);
|
|
6239
|
-
}
|
|
6240
|
-
if ((manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.legacyBaseline === null) {
|
|
6241
|
-
throw new Error(
|
|
6242
|
-
`${manifest.activationState} manifests require a legacy baseline`
|
|
6243
|
-
);
|
|
6244
|
-
}
|
|
6245
|
-
if (manifest.activationState === "v3_active" && manifest.activatedAt === null) {
|
|
6246
|
-
throw new Error("v3_active manifests require activatedAt");
|
|
6247
|
-
}
|
|
6248
|
-
if ((manifest.activationState === "initializing" || manifest.activationState === "dual_read" || manifest.activationState === "activating") && manifest.activatedAt !== null) {
|
|
6249
|
-
throw new Error(
|
|
6250
|
-
`${manifest.activationState} manifests must not have activatedAt`
|
|
6251
|
-
);
|
|
7672
|
+
async function assertSafeDirectory(directory) {
|
|
7673
|
+
const stat2 = await lstat5(directory);
|
|
7674
|
+
if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
|
|
7675
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6252
7676
|
}
|
|
6253
7677
|
}
|
|
6254
|
-
function
|
|
6255
|
-
|
|
6256
|
-
const
|
|
6257
|
-
|
|
6258
|
-
|
|
6259
|
-
|
|
6260
|
-
}
|
|
6261
|
-
function compareVersions(left, right) {
|
|
6262
|
-
const [leftCore = "", leftPrerelease] = left.split("-", 2);
|
|
6263
|
-
const [rightCore = "", rightPrerelease] = right.split("-", 2);
|
|
6264
|
-
const leftParts = leftCore.split(".").map(Number);
|
|
6265
|
-
const rightParts = rightCore.split(".").map(Number);
|
|
6266
|
-
for (let index = 0; index < 3; index += 1) {
|
|
6267
|
-
const delta = (leftParts[index] ?? 0) - (rightParts[index] ?? 0);
|
|
6268
|
-
if (delta !== 0) return delta;
|
|
7678
|
+
async function readSafeText(directory, filename) {
|
|
7679
|
+
await assertSafeDirectory(directory);
|
|
7680
|
+
const target = path8.join(directory, filename);
|
|
7681
|
+
const before = await lstat5(target);
|
|
7682
|
+
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7683
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6269
7684
|
}
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
|
|
6275
|
-
function comparePrerelease(left, right) {
|
|
6276
|
-
const leftParts = left.split(".");
|
|
6277
|
-
const rightParts = right.split(".");
|
|
6278
|
-
for (let index = 0; index < Math.max(leftParts.length, rightParts.length); index += 1) {
|
|
6279
|
-
const leftPart = leftParts[index];
|
|
6280
|
-
const rightPart = rightParts[index];
|
|
6281
|
-
if (leftPart === void 0) return -1;
|
|
6282
|
-
if (rightPart === void 0) return 1;
|
|
6283
|
-
if (leftPart === rightPart) continue;
|
|
6284
|
-
const leftNumeric = /^\d+$/.test(leftPart);
|
|
6285
|
-
const rightNumeric = /^\d+$/.test(rightPart);
|
|
6286
|
-
if (leftNumeric && rightNumeric)
|
|
6287
|
-
return Number(leftPart) - Number(rightPart);
|
|
6288
|
-
if (leftNumeric) return -1;
|
|
6289
|
-
if (rightNumeric) return 1;
|
|
6290
|
-
return leftPart.localeCompare(rightPart, "en");
|
|
7685
|
+
const content = await readFile6(target, "utf8");
|
|
7686
|
+
await assertSafeDirectory(directory);
|
|
7687
|
+
const after = await lstat5(target);
|
|
7688
|
+
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino) {
|
|
7689
|
+
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
6291
7690
|
}
|
|
6292
|
-
return
|
|
7691
|
+
return content;
|
|
6293
7692
|
}
|
|
6294
|
-
function
|
|
6295
|
-
return left
|
|
7693
|
+
function compareUtf83(left, right) {
|
|
7694
|
+
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
6296
7695
|
}
|
|
6297
|
-
function
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
return /* @__PURE__ */ new Set(["activating", "repair_required"]);
|
|
6303
|
-
case "activating":
|
|
6304
|
-
return /* @__PURE__ */ new Set(["v3_active", "repair_required"]);
|
|
6305
|
-
case "repair_required":
|
|
6306
|
-
return /* @__PURE__ */ new Set(["v3_active"]);
|
|
6307
|
-
case "v3_active":
|
|
6308
|
-
return /* @__PURE__ */ new Set(["repair_required"]);
|
|
6309
|
-
}
|
|
7696
|
+
function isAlreadyExists4(error) {
|
|
7697
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
7698
|
+
}
|
|
7699
|
+
function isNotFound5(error) {
|
|
7700
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
6310
7701
|
}
|
|
6311
7702
|
|
|
6312
7703
|
// src/context/project-facts.ts
|
|
6313
|
-
import { mkdir as
|
|
6314
|
-
import
|
|
7704
|
+
import { mkdir as mkdir5, readFile as readFile8, rename as rename3, rm as rm2, writeFile as writeFile5 } from "fs/promises";
|
|
7705
|
+
import path10 from "path";
|
|
6315
7706
|
|
|
6316
7707
|
// src/system/project-profile.ts
|
|
6317
|
-
import { readFile as
|
|
6318
|
-
import
|
|
7708
|
+
import { readFile as readFile7, readdir as readdir6, stat } from "fs/promises";
|
|
7709
|
+
import path9 from "path";
|
|
6319
7710
|
var PROJECT_MANIFESTS = [
|
|
6320
7711
|
"package.json",
|
|
6321
7712
|
"pyproject.toml",
|
|
@@ -6373,29 +7764,29 @@ var PROJECT_SOURCE_ROOTS = [
|
|
|
6373
7764
|
var PROJECT_MANIFEST_NAMES = new Set(PROJECT_MANIFESTS);
|
|
6374
7765
|
var PROJECT_SOURCE_ROOT_NAMES = new Set(PROJECT_SOURCE_ROOTS);
|
|
6375
7766
|
async function hasProjectEvidence(projectRoot) {
|
|
6376
|
-
const entries = await
|
|
7767
|
+
const entries = await readdir6(projectRoot, { withFileTypes: true }).catch(
|
|
6377
7768
|
() => []
|
|
6378
7769
|
);
|
|
6379
7770
|
return entries.some((entry) => {
|
|
6380
7771
|
if (entry.isDirectory()) return PROJECT_SOURCE_ROOT_NAMES.has(entry.name);
|
|
6381
7772
|
if (!entry.isFile()) return false;
|
|
6382
|
-
return PROJECT_MANIFEST_NAMES.has(entry.name) || PROJECT_SOURCE_EXTENSIONS.has(
|
|
7773
|
+
return PROJECT_MANIFEST_NAMES.has(entry.name) || PROJECT_SOURCE_EXTENSIONS.has(path9.extname(entry.name).toLowerCase());
|
|
6383
7774
|
});
|
|
6384
7775
|
}
|
|
6385
7776
|
async function detectProjectProfile(projectRoot) {
|
|
6386
|
-
const entries = await
|
|
7777
|
+
const entries = await readdir6(projectRoot).catch(() => []);
|
|
6387
7778
|
const manifests = PROJECT_MANIFESTS.filter((file) => entries.includes(file));
|
|
6388
7779
|
const sourceRoots = await existingDirs(projectRoot, [
|
|
6389
7780
|
...PROJECT_SOURCE_ROOTS,
|
|
6390
7781
|
"data",
|
|
6391
7782
|
"notebooks"
|
|
6392
7783
|
]);
|
|
6393
|
-
const packageJson = await readJson(
|
|
7784
|
+
const packageJson = await readJson(path9.join(projectRoot, "package.json"));
|
|
6394
7785
|
const [pubspec, pythonManifests] = await Promise.all([
|
|
6395
|
-
readText(
|
|
7786
|
+
readText(path9.join(projectRoot, "pubspec.yaml")),
|
|
6396
7787
|
Promise.all([
|
|
6397
|
-
readText(
|
|
6398
|
-
readText(
|
|
7788
|
+
readText(path9.join(projectRoot, "pyproject.toml")),
|
|
7789
|
+
readText(path9.join(projectRoot, "requirements.txt"))
|
|
6399
7790
|
]).then((parts) => parts.filter(Boolean).join("\n"))
|
|
6400
7791
|
]);
|
|
6401
7792
|
const deps = Object.keys({
|
|
@@ -6404,7 +7795,7 @@ async function detectProjectProfile(projectRoot) {
|
|
|
6404
7795
|
...packageJson?.peerDependencies
|
|
6405
7796
|
});
|
|
6406
7797
|
const flutter = isFlutterProject(pubspec);
|
|
6407
|
-
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(
|
|
7798
|
+
const shadcn = entries.includes("components.json") || deps.some((dep) => dep.startsWith("@radix-ui/")) && await isDirectory(path9.join(projectRoot, "src", "components", "ui"));
|
|
6408
7799
|
const languages = inferLanguages(manifests, entries);
|
|
6409
7800
|
const frameworks = inferFrameworks(
|
|
6410
7801
|
deps,
|
|
@@ -6447,7 +7838,7 @@ async function existingDirs(root, candidates) {
|
|
|
6447
7838
|
const found = [];
|
|
6448
7839
|
for (const candidate of candidates) {
|
|
6449
7840
|
try {
|
|
6450
|
-
if ((await stat(
|
|
7841
|
+
if ((await stat(path9.join(root, candidate))).isDirectory())
|
|
6451
7842
|
found.push(candidate);
|
|
6452
7843
|
} catch {
|
|
6453
7844
|
}
|
|
@@ -6463,14 +7854,14 @@ async function isDirectory(candidate) {
|
|
|
6463
7854
|
}
|
|
6464
7855
|
async function readJson(file) {
|
|
6465
7856
|
try {
|
|
6466
|
-
return JSON.parse(await
|
|
7857
|
+
return JSON.parse(await readFile7(file, "utf-8"));
|
|
6467
7858
|
} catch {
|
|
6468
7859
|
return null;
|
|
6469
7860
|
}
|
|
6470
7861
|
}
|
|
6471
7862
|
async function readText(file) {
|
|
6472
7863
|
try {
|
|
6473
|
-
return await
|
|
7864
|
+
return await readFile7(file, "utf-8");
|
|
6474
7865
|
} catch {
|
|
6475
7866
|
return "";
|
|
6476
7867
|
}
|
|
@@ -6693,8 +8084,8 @@ function parseProjectFacts(value) {
|
|
|
6693
8084
|
if (value.schemaVersion !== 1) {
|
|
6694
8085
|
throw new Error("project facts schemaVersion must be 1");
|
|
6695
8086
|
}
|
|
6696
|
-
const
|
|
6697
|
-
if (typeof
|
|
8087
|
+
const revision2 = value.revision;
|
|
8088
|
+
if (typeof revision2 !== "number" || !Number.isSafeInteger(revision2) || revision2 < 1) {
|
|
6698
8089
|
throw new Error("project facts revision must be a positive integer");
|
|
6699
8090
|
}
|
|
6700
8091
|
if (value.trust !== "detected") {
|
|
@@ -6703,20 +8094,20 @@ function parseProjectFacts(value) {
|
|
|
6703
8094
|
const uiLibrary = parseTextOrNull(value.uiLibrary, "project facts uiLibrary");
|
|
6704
8095
|
return {
|
|
6705
8096
|
schemaVersion: 1,
|
|
6706
|
-
revision,
|
|
8097
|
+
revision: revision2,
|
|
6707
8098
|
trust: "detected",
|
|
6708
8099
|
profile: parseProjectProfile(value.profile),
|
|
6709
8100
|
uiLibrary,
|
|
6710
8101
|
detectedAt: parseTimestamp14(value.detectedAt, "project facts detectedAt"),
|
|
6711
|
-
lastOperationId:
|
|
8102
|
+
lastOperationId: parseUlidOrNull9(
|
|
6712
8103
|
value.lastOperationId,
|
|
6713
8104
|
"project facts lastOperationId"
|
|
6714
8105
|
)
|
|
6715
8106
|
};
|
|
6716
8107
|
}
|
|
6717
8108
|
function projectFactsPath(projectRoot) {
|
|
6718
|
-
return
|
|
6719
|
-
|
|
8109
|
+
return path10.join(
|
|
8110
|
+
path10.resolve(projectRoot),
|
|
6720
8111
|
".mancode",
|
|
6721
8112
|
"shared",
|
|
6722
8113
|
"context",
|
|
@@ -6724,22 +8115,31 @@ function projectFactsPath(projectRoot) {
|
|
|
6724
8115
|
);
|
|
6725
8116
|
}
|
|
6726
8117
|
async function writeProjectFacts(projectRoot, facts) {
|
|
8118
|
+
const parsed = parseProjectFacts(facts);
|
|
8119
|
+
return withSharedPrivacyWrite(
|
|
8120
|
+
projectRoot,
|
|
8121
|
+
parsed.lastOperationId ?? createUlid(),
|
|
8122
|
+
parsed,
|
|
8123
|
+
() => writeProjectFactsUnlocked(projectRoot, parsed)
|
|
8124
|
+
);
|
|
8125
|
+
}
|
|
8126
|
+
async function writeProjectFactsUnlocked(projectRoot, facts) {
|
|
6727
8127
|
const parsed = parseProjectFacts(facts);
|
|
6728
8128
|
const target = projectFactsPath(projectRoot);
|
|
6729
|
-
await
|
|
6730
|
-
const temporary =
|
|
6731
|
-
|
|
6732
|
-
`.${
|
|
8129
|
+
await mkdir5(path10.dirname(target), { recursive: true });
|
|
8130
|
+
const temporary = path10.join(
|
|
8131
|
+
path10.dirname(target),
|
|
8132
|
+
`.${path10.basename(target)}.${process.pid}.${Date.now()}.tmp`
|
|
6733
8133
|
);
|
|
6734
8134
|
try {
|
|
6735
|
-
await
|
|
8135
|
+
await writeFile5(temporary, `${JSON.stringify(parsed, null, 2)}
|
|
6736
8136
|
`, {
|
|
6737
8137
|
encoding: "utf8",
|
|
6738
8138
|
flag: "wx"
|
|
6739
8139
|
});
|
|
6740
|
-
await
|
|
8140
|
+
await rename3(temporary, target);
|
|
6741
8141
|
} finally {
|
|
6742
|
-
await
|
|
8142
|
+
await rm2(temporary, { force: true }).catch(() => void 0);
|
|
6743
8143
|
}
|
|
6744
8144
|
return parsed;
|
|
6745
8145
|
}
|
|
@@ -6812,7 +8212,7 @@ function parseTextArray(value, label) {
|
|
|
6812
8212
|
}
|
|
6813
8213
|
function parseSafeRelativePathArray(value, label) {
|
|
6814
8214
|
return parseTextArray(value, label).map((item) => {
|
|
6815
|
-
if (
|
|
8215
|
+
if (path10.isAbsolute(item) || item.includes("\\") || item.split("/").some((part) => !part || part === "." || part === "..")) {
|
|
6816
8216
|
throw new Error(`${label} must contain safe relative paths`);
|
|
6817
8217
|
}
|
|
6818
8218
|
return item;
|
|
@@ -6835,17 +8235,17 @@ function parseTimestamp14(value, label) {
|
|
|
6835
8235
|
}
|
|
6836
8236
|
return value;
|
|
6837
8237
|
}
|
|
6838
|
-
function
|
|
8238
|
+
function parseUlidOrNull9(value, label) {
|
|
6839
8239
|
if (value === null) return null;
|
|
6840
8240
|
assertUlid(value, label);
|
|
6841
8241
|
return value;
|
|
6842
8242
|
}
|
|
6843
8243
|
|
|
6844
8244
|
// src/context/task-locator.ts
|
|
6845
|
-
import { lstat as
|
|
6846
|
-
import
|
|
8245
|
+
import { lstat as lstat6, readFile as readFile9, realpath } from "fs/promises";
|
|
8246
|
+
import path11 from "path";
|
|
6847
8247
|
async function locateTask(projectRoot, requested) {
|
|
6848
|
-
const root =
|
|
8248
|
+
const root = path11.resolve(projectRoot);
|
|
6849
8249
|
if (typeof requested !== "string") {
|
|
6850
8250
|
const taskRef = parseTaskRefValue(requested);
|
|
6851
8251
|
return locateExplicitTask(root, taskRef);
|
|
@@ -6872,8 +8272,8 @@ async function locateTask(projectRoot, requested) {
|
|
|
6872
8272
|
}
|
|
6873
8273
|
function taskRootPath(projectRoot, taskRef) {
|
|
6874
8274
|
const parsed = parseTaskRefValue(taskRef);
|
|
6875
|
-
return
|
|
6876
|
-
|
|
8275
|
+
return path11.join(
|
|
8276
|
+
path11.resolve(projectRoot),
|
|
6877
8277
|
".mancode",
|
|
6878
8278
|
parsed.namespace,
|
|
6879
8279
|
"workflows",
|
|
@@ -6889,7 +8289,7 @@ async function locateExplicitTask(projectRoot, taskRef) {
|
|
|
6889
8289
|
}
|
|
6890
8290
|
async function isDirectoryWithoutSymlink(target) {
|
|
6891
8291
|
try {
|
|
6892
|
-
const entry = await
|
|
8292
|
+
const entry = await lstat6(target);
|
|
6893
8293
|
return entry.isDirectory() && !entry.isSymbolicLink();
|
|
6894
8294
|
} catch {
|
|
6895
8295
|
return false;
|
|
@@ -6907,13 +8307,13 @@ var V3ContextStore = class {
|
|
|
6907
8307
|
if (typeof projectRoot !== "string" || !projectRoot.trim()) {
|
|
6908
8308
|
throw new Error("context store projectRoot is required");
|
|
6909
8309
|
}
|
|
6910
|
-
this.projectRoot =
|
|
8310
|
+
this.projectRoot = path12.resolve(projectRoot);
|
|
6911
8311
|
}
|
|
6912
8312
|
async locateTask(requested) {
|
|
6913
8313
|
const location = await locateTask(this.projectRoot, requested);
|
|
6914
8314
|
await assertSafeDirectoryWithin(
|
|
6915
8315
|
this.projectRoot,
|
|
6916
|
-
|
|
8316
|
+
path12.relative(this.projectRoot, location.taskRoot)
|
|
6917
8317
|
);
|
|
6918
8318
|
return location;
|
|
6919
8319
|
}
|
|
@@ -6992,33 +8392,45 @@ var V3ContextStore = class {
|
|
|
6992
8392
|
),
|
|
6993
8393
|
this.readRequiredJson(
|
|
6994
8394
|
this.mancodeRoot(),
|
|
6995
|
-
|
|
8395
|
+
path12.join("shared", "config.json"),
|
|
6996
8396
|
parseProjectConfig
|
|
6997
8397
|
),
|
|
6998
8398
|
this.readRequiredJson(
|
|
6999
8399
|
this.mancodeRoot(),
|
|
7000
|
-
|
|
8400
|
+
path12.join("shared", "team", "policy.json"),
|
|
7001
8401
|
parseTeamPolicy
|
|
7002
8402
|
),
|
|
7003
8403
|
readOptionalJsonWithin(
|
|
7004
8404
|
this.mancodeRoot(),
|
|
7005
|
-
|
|
8405
|
+
path12.join("shared", "context", "project.json"),
|
|
7006
8406
|
parseProjectFacts
|
|
7007
8407
|
),
|
|
7008
8408
|
listConfirmedDecisions(this.projectRoot)
|
|
7009
8409
|
]);
|
|
8410
|
+
const privacy = await readPrivacyPolicySnapshot(this.projectRoot, manifest);
|
|
8411
|
+
if (privacy !== null && privacy.policy.workspaceId !== config.workspaceId)
|
|
8412
|
+
throw new Error("MANCODE_PRIVACY_POLICY_WORKSPACE_MISMATCH");
|
|
8413
|
+
const verifiedManifest = await this.readRequiredJson(
|
|
8414
|
+
this.mancodeRoot(),
|
|
8415
|
+
"schema.json",
|
|
8416
|
+
parseSchemaManifest
|
|
8417
|
+
);
|
|
8418
|
+
if (digestCanonicalJson(manifest) !== digestCanonicalJson(verifiedManifest))
|
|
8419
|
+
throw new Error("MANCODE_PROJECT_SNAPSHOT_CHANGED");
|
|
7010
8420
|
return {
|
|
7011
8421
|
manifest,
|
|
7012
8422
|
config,
|
|
7013
8423
|
policy,
|
|
7014
8424
|
projectFacts,
|
|
7015
8425
|
confirmedDecisions,
|
|
8426
|
+
privacy,
|
|
7016
8427
|
fingerprint: digestCanonicalJson({
|
|
7017
8428
|
manifest,
|
|
7018
8429
|
config,
|
|
7019
8430
|
policy,
|
|
7020
8431
|
projectFacts,
|
|
7021
|
-
confirmedDecisions
|
|
8432
|
+
confirmedDecisions,
|
|
8433
|
+
...privacy === null ? {} : { privacy }
|
|
7022
8434
|
})
|
|
7023
8435
|
};
|
|
7024
8436
|
}
|
|
@@ -7028,7 +8440,7 @@ var V3ContextStore = class {
|
|
|
7028
8440
|
const parentRoot = taskRootPath(this.projectRoot, parentRef);
|
|
7029
8441
|
await assertSafeDirectoryWithin(
|
|
7030
8442
|
this.projectRoot,
|
|
7031
|
-
|
|
8443
|
+
path12.relative(this.projectRoot, parentRoot)
|
|
7032
8444
|
);
|
|
7033
8445
|
const metadata = await this.readRequiredJson(
|
|
7034
8446
|
parentRoot,
|
|
@@ -7094,12 +8506,12 @@ var V3ContextStore = class {
|
|
|
7094
8506
|
async listWorkflowMetadata() {
|
|
7095
8507
|
const workflows = [];
|
|
7096
8508
|
for (const namespace of ["local", "shared"]) {
|
|
7097
|
-
const directory =
|
|
8509
|
+
const directory = path12.join(".mancode", namespace, "workflows");
|
|
7098
8510
|
const entries = await readDirectoryEntriesWithin(
|
|
7099
8511
|
this.projectRoot,
|
|
7100
8512
|
directory
|
|
7101
8513
|
);
|
|
7102
|
-
for (const taskId of entries.sort(
|
|
8514
|
+
for (const taskId of entries.sort(compareUtf84)) {
|
|
7103
8515
|
try {
|
|
7104
8516
|
assertUlid(taskId, "workflow directory");
|
|
7105
8517
|
} catch {
|
|
@@ -7109,7 +8521,7 @@ var V3ContextStore = class {
|
|
|
7109
8521
|
const taskRoot = taskRootPath(this.projectRoot, taskRef);
|
|
7110
8522
|
await assertSafeDirectoryWithin(
|
|
7111
8523
|
this.projectRoot,
|
|
7112
|
-
|
|
8524
|
+
path12.relative(this.projectRoot, taskRoot)
|
|
7113
8525
|
);
|
|
7114
8526
|
const metadata = await this.readRequiredJson(
|
|
7115
8527
|
taskRoot,
|
|
@@ -7123,7 +8535,7 @@ var V3ContextStore = class {
|
|
|
7123
8535
|
}
|
|
7124
8536
|
}
|
|
7125
8537
|
return workflows.sort(
|
|
7126
|
-
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) ||
|
|
8538
|
+
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || compareUtf84(left.taskRef.namespace, right.taskRef.namespace) || compareUtf84(left.taskRef.taskId, right.taskRef.taskId)
|
|
7127
8539
|
);
|
|
7128
8540
|
}
|
|
7129
8541
|
/**
|
|
@@ -7136,11 +8548,11 @@ var V3ContextStore = class {
|
|
|
7136
8548
|
return (await this.listWorkflowMetadata()).filter(
|
|
7137
8549
|
(metadata) => metadata.parent !== null && sameTaskRef(metadata.parent.taskRef, parent) && !isTerminalWorkflowStatus(metadata.status)
|
|
7138
8550
|
).map((metadata) => metadata.taskRef).sort(
|
|
7139
|
-
(left, right) =>
|
|
8551
|
+
(left, right) => compareUtf84(left.namespace, right.namespace) || compareUtf84(left.taskId, right.taskId)
|
|
7140
8552
|
);
|
|
7141
8553
|
}
|
|
7142
8554
|
mancodeRoot() {
|
|
7143
|
-
return
|
|
8555
|
+
return path12.join(this.projectRoot, ".mancode");
|
|
7144
8556
|
}
|
|
7145
8557
|
async readOptionalPlan(taskRef, taskRoot) {
|
|
7146
8558
|
const content = await readOptionalTextWithin(taskRoot, "plan.md");
|
|
@@ -7160,7 +8572,7 @@ var V3ContextStore = class {
|
|
|
7160
8572
|
}
|
|
7161
8573
|
const checkpoint = await this.readRequiredJson(
|
|
7162
8574
|
taskRoot,
|
|
7163
|
-
|
|
8575
|
+
path12.join("checkpoints", `${ref.artifactId}.json`),
|
|
7164
8576
|
parseCheckpoint
|
|
7165
8577
|
);
|
|
7166
8578
|
if (!sameTaskRef(checkpoint.taskRef, metadata.taskRef)) {
|
|
@@ -7171,8 +8583,8 @@ var V3ContextStore = class {
|
|
|
7171
8583
|
async readTaskHeadFence(homeStore, taskRef) {
|
|
7172
8584
|
const value = await readOptionalJsonWithin(
|
|
7173
8585
|
homeStore.root,
|
|
7174
|
-
|
|
7175
|
-
|
|
8586
|
+
path12.join(
|
|
8587
|
+
path12.relative(homeStore.root, taskHeadDirectory(homeStore)),
|
|
7176
8588
|
`${taskRef.taskId}.json`
|
|
7177
8589
|
),
|
|
7178
8590
|
parseTaskHeadFence
|
|
@@ -7184,16 +8596,16 @@ var V3ContextStore = class {
|
|
|
7184
8596
|
return value;
|
|
7185
8597
|
}
|
|
7186
8598
|
async readClaims(homeStore, taskRef) {
|
|
7187
|
-
const directory =
|
|
8599
|
+
const directory = path12.relative(homeStore.root, claimDirectory(homeStore));
|
|
7188
8600
|
const claims = await readJsonCollectionWithin(
|
|
7189
8601
|
homeStore.root,
|
|
7190
8602
|
directory,
|
|
7191
8603
|
parseClaim
|
|
7192
8604
|
);
|
|
7193
|
-
return claims.filter((claim) => sameTaskRef(claim.taskRef, taskRef)).sort((left, right) =>
|
|
8605
|
+
return claims.filter((claim) => sameTaskRef(claim.taskRef, taskRef)).sort((left, right) => compareUtf84(left.claimId, right.claimId));
|
|
7194
8606
|
}
|
|
7195
8607
|
async readHandoffs(homeStore, taskRef) {
|
|
7196
|
-
const directory =
|
|
8608
|
+
const directory = path12.relative(
|
|
7197
8609
|
homeStore.root,
|
|
7198
8610
|
handoffDirectory(homeStore)
|
|
7199
8611
|
);
|
|
@@ -7203,7 +8615,7 @@ var V3ContextStore = class {
|
|
|
7203
8615
|
parseHandoff
|
|
7204
8616
|
);
|
|
7205
8617
|
return handoffs.filter((handoff) => sameTaskRef(handoff.taskRef, taskRef)).sort(
|
|
7206
|
-
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) ||
|
|
8618
|
+
(left, right) => Date.parse(right.updatedAt) - Date.parse(left.updatedAt) || compareUtf84(left.handoffId, right.handoffId)
|
|
7207
8619
|
);
|
|
7208
8620
|
}
|
|
7209
8621
|
async readPendingOperations(taskRef, homeStore) {
|
|
@@ -7211,12 +8623,12 @@ var V3ContextStore = class {
|
|
|
7211
8623
|
const [journals, reservations, gitRefWorkflowRepairs] = await Promise.all([
|
|
7212
8624
|
readJsonCollectionWithin(
|
|
7213
8625
|
homeStore.root,
|
|
7214
|
-
|
|
8626
|
+
path12.relative(homeStore.root, operationDirectory(homeStore)),
|
|
7215
8627
|
parseOperationJournal
|
|
7216
8628
|
),
|
|
7217
8629
|
readJsonCollectionWithin(
|
|
7218
8630
|
homeStore.root,
|
|
7219
|
-
|
|
8631
|
+
path12.relative(homeStore.root, reservationDirectory(homeStore)),
|
|
7220
8632
|
parseOperationReservation
|
|
7221
8633
|
),
|
|
7222
8634
|
listUnfinishedGitRefWorkflowRepairs(this.projectRoot)
|
|
@@ -7242,14 +8654,14 @@ var V3ContextStore = class {
|
|
|
7242
8654
|
entityKeys: [taskKey]
|
|
7243
8655
|
}));
|
|
7244
8656
|
return [...primary, ...secondary, ...gitRefWorkflow].sort(
|
|
7245
|
-
(left, right) =>
|
|
8657
|
+
(left, right) => compareUtf84(left.operationId, right.operationId) || left.source.localeCompare(right.source, "en")
|
|
7246
8658
|
);
|
|
7247
8659
|
}
|
|
7248
8660
|
async readRequiredJson(root, relativePath, parser) {
|
|
7249
8661
|
try {
|
|
7250
8662
|
return parser(JSON.parse(await readTextWithin(root, relativePath)));
|
|
7251
8663
|
} catch (error) {
|
|
7252
|
-
if (
|
|
8664
|
+
if (isNotFound6(error)) {
|
|
7253
8665
|
throw new Error(`MANCODE_CONTEXT_ENTITY_UNAVAILABLE: ${relativePath}`);
|
|
7254
8666
|
}
|
|
7255
8667
|
if (error instanceof SyntaxError) {
|
|
@@ -7284,7 +8696,7 @@ async function readOptionalJsonWithin(root, relativePath, parser) {
|
|
|
7284
8696
|
try {
|
|
7285
8697
|
return parser(JSON.parse(await readTextWithin(root, relativePath)));
|
|
7286
8698
|
} catch (error) {
|
|
7287
|
-
if (
|
|
8699
|
+
if (isNotFound6(error)) return null;
|
|
7288
8700
|
if (error instanceof SyntaxError) {
|
|
7289
8701
|
throw new Error(`MANCODE_CONTEXT_ENTITY_CORRUPT: ${relativePath}`);
|
|
7290
8702
|
}
|
|
@@ -7295,14 +8707,14 @@ async function readJsonCollectionWithin(root, relativeDirectory, parser) {
|
|
|
7295
8707
|
const entries = await readDirectoryEntriesWithin(root, relativeDirectory);
|
|
7296
8708
|
const jsonEntries = entries.filter((entry) => entry.endsWith(JSON_SUFFIX));
|
|
7297
8709
|
const values = [];
|
|
7298
|
-
for (const entry of jsonEntries.sort(
|
|
8710
|
+
for (const entry of jsonEntries.sort(compareUtf84)) {
|
|
7299
8711
|
const stem = entry.slice(0, -JSON_SUFFIX.length);
|
|
7300
8712
|
if (!/^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}$/.test(stem)) {
|
|
7301
8713
|
throw new Error("MANCODE_CONTEXT_COLLECTION_ENTRY_INVALID");
|
|
7302
8714
|
}
|
|
7303
8715
|
const value = await readOptionalJsonWithin(
|
|
7304
8716
|
root,
|
|
7305
|
-
|
|
8717
|
+
path12.join(relativeDirectory, entry),
|
|
7306
8718
|
parser
|
|
7307
8719
|
);
|
|
7308
8720
|
if (value === null) {
|
|
@@ -7316,31 +8728,31 @@ async function readOptionalTextWithin(root, relativePath) {
|
|
|
7316
8728
|
try {
|
|
7317
8729
|
return await readTextWithin(root, relativePath);
|
|
7318
8730
|
} catch (error) {
|
|
7319
|
-
if (
|
|
8731
|
+
if (isNotFound6(error)) return null;
|
|
7320
8732
|
throw error;
|
|
7321
8733
|
}
|
|
7322
8734
|
}
|
|
7323
8735
|
async function readTextWithin(root, relativePath) {
|
|
7324
|
-
const absoluteRoot =
|
|
8736
|
+
const absoluteRoot = path12.resolve(root);
|
|
7325
8737
|
const segments = safeRelativeSegments(relativePath);
|
|
7326
8738
|
await assertSafeDirectoryWithin(absoluteRoot, ".");
|
|
7327
8739
|
const parentSegments = segments.slice(0, -1);
|
|
7328
8740
|
let current = absoluteRoot;
|
|
7329
8741
|
for (const segment of parentSegments) {
|
|
7330
|
-
current =
|
|
8742
|
+
current = path12.join(current, segment);
|
|
7331
8743
|
await assertSafeDirectoryAt(current);
|
|
7332
8744
|
}
|
|
7333
|
-
const target =
|
|
7334
|
-
const before = await
|
|
8745
|
+
const target = path12.join(absoluteRoot, ...segments);
|
|
8746
|
+
const before = await lstat7(target);
|
|
7335
8747
|
if (!before.isFile() || before.isSymbolicLink()) {
|
|
7336
8748
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7337
8749
|
}
|
|
7338
|
-
const content = await
|
|
8750
|
+
const content = await readFile10(target, "utf8");
|
|
7339
8751
|
await assertSafeDirectoryWithin(
|
|
7340
8752
|
absoluteRoot,
|
|
7341
|
-
parentSegments.length === 0 ? "." :
|
|
8753
|
+
parentSegments.length === 0 ? "." : path12.join(...parentSegments)
|
|
7342
8754
|
);
|
|
7343
|
-
const after = await
|
|
8755
|
+
const after = await lstat7(target);
|
|
7344
8756
|
if (!after.isFile() || after.isSymbolicLink() || before.dev !== after.dev || before.ino !== after.ino) {
|
|
7345
8757
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7346
8758
|
}
|
|
@@ -7349,43 +8761,43 @@ async function readTextWithin(root, relativePath) {
|
|
|
7349
8761
|
async function readDirectoryEntriesWithin(root, relativeDirectory) {
|
|
7350
8762
|
try {
|
|
7351
8763
|
const directory = await assertSafeDirectoryWithin(root, relativeDirectory);
|
|
7352
|
-
return await
|
|
8764
|
+
return await readdir7(directory);
|
|
7353
8765
|
} catch (error) {
|
|
7354
|
-
if (
|
|
8766
|
+
if (isNotFound6(error)) return [];
|
|
7355
8767
|
throw error;
|
|
7356
8768
|
}
|
|
7357
8769
|
}
|
|
7358
8770
|
async function assertSafeDirectoryWithin(root, relativeDirectory) {
|
|
7359
|
-
const absoluteRoot =
|
|
8771
|
+
const absoluteRoot = path12.resolve(root);
|
|
7360
8772
|
const segments = relativeDirectory === "." ? [] : safeRelativeSegments(relativeDirectory);
|
|
7361
8773
|
await assertSafeDirectoryAt(absoluteRoot);
|
|
7362
8774
|
let current = absoluteRoot;
|
|
7363
8775
|
for (const segment of segments) {
|
|
7364
|
-
current =
|
|
8776
|
+
current = path12.join(current, segment);
|
|
7365
8777
|
await assertSafeDirectoryAt(current);
|
|
7366
8778
|
}
|
|
7367
8779
|
return current;
|
|
7368
8780
|
}
|
|
7369
8781
|
async function assertSafeDirectoryAt(target) {
|
|
7370
|
-
const stat2 = await
|
|
8782
|
+
const stat2 = await lstat7(target);
|
|
7371
8783
|
if (!stat2.isDirectory() || stat2.isSymbolicLink()) {
|
|
7372
8784
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7373
8785
|
}
|
|
7374
8786
|
}
|
|
7375
8787
|
function safeRelativeSegments(relativePath) {
|
|
7376
|
-
if (typeof relativePath !== "string" || !relativePath || relativePath.includes("\0") ||
|
|
8788
|
+
if (typeof relativePath !== "string" || !relativePath || relativePath.includes("\0") || path12.isAbsolute(relativePath)) {
|
|
7377
8789
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7378
8790
|
}
|
|
7379
|
-
const segments = relativePath.split(
|
|
8791
|
+
const segments = relativePath.split(path12.sep);
|
|
7380
8792
|
if (segments.some((segment) => !segment || segment === "." || segment === "..")) {
|
|
7381
8793
|
throw new Error("MANCODE_CONTEXT_PATH_UNSAFE");
|
|
7382
8794
|
}
|
|
7383
8795
|
return segments;
|
|
7384
8796
|
}
|
|
7385
|
-
function
|
|
8797
|
+
function compareUtf84(left, right) {
|
|
7386
8798
|
return Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"));
|
|
7387
8799
|
}
|
|
7388
|
-
function
|
|
8800
|
+
function isNotFound6(error) {
|
|
7389
8801
|
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
7390
8802
|
}
|
|
7391
8803
|
function isTerminalWorkflowStatus(status) {
|
|
@@ -7396,38 +8808,19 @@ function storedTaskAggregateDigest(snapshot) {
|
|
|
7396
8808
|
}
|
|
7397
8809
|
|
|
7398
8810
|
export {
|
|
7399
|
-
isUlid,
|
|
7400
|
-
assertUlid,
|
|
7401
|
-
createUlid,
|
|
7402
|
-
assertRecord,
|
|
7403
|
-
assertKnownKeys,
|
|
7404
8811
|
parseSchemaManifest,
|
|
7405
8812
|
serializeSchemaManifest,
|
|
7406
8813
|
managedAdapterNames,
|
|
7407
|
-
parseSchemaManifestV2,
|
|
7408
8814
|
assertSchemaManifestTransition,
|
|
8815
|
+
assertSchemaManifestPrivacyTransition,
|
|
7409
8816
|
assertSchemaManifestPolicyUpgrade,
|
|
7410
8817
|
assertActivationRollbackManifestTransition,
|
|
7411
8818
|
managedAdapterInventoriesMatch,
|
|
7412
|
-
formatTaskRef,
|
|
7413
|
-
parseTaskRef,
|
|
7414
|
-
parseTaskRefValue,
|
|
7415
|
-
sameTaskRef,
|
|
7416
|
-
resolveTaskEntityHomeStore,
|
|
7417
|
-
resolveLocalEntityHomeStore,
|
|
7418
|
-
resolveCoordinationEntityHomeStore,
|
|
7419
|
-
operationDirectory,
|
|
7420
|
-
lockDirectory,
|
|
7421
|
-
claimDirectory,
|
|
7422
|
-
handoffDirectory,
|
|
7423
|
-
taskHeadDirectory,
|
|
7424
8819
|
gitRefWorkflowRepairJournalPath,
|
|
7425
8820
|
listGitRefWorkflowRepairJournalSummaries,
|
|
7426
8821
|
listUnfinishedGitRefWorkflowRepairs,
|
|
7427
|
-
canonicalizeJson,
|
|
7428
|
-
digestCanonicalJson,
|
|
7429
|
-
sortUtf8StringSet,
|
|
7430
8822
|
scanSharedText,
|
|
8823
|
+
redactSharedText,
|
|
7431
8824
|
assertSharedTextSafe,
|
|
7432
8825
|
assertSafeSharedRelativePath,
|
|
7433
8826
|
parseCapabilityLevel,
|
|
@@ -7436,14 +8829,10 @@ export {
|
|
|
7436
8829
|
parseAuthorizationBasis,
|
|
7437
8830
|
assertRepairUsesOriginalAuthorization,
|
|
7438
8831
|
parseOperationJournal,
|
|
7439
|
-
assertOperationJournalTransition,
|
|
7440
|
-
operationJournalDigest,
|
|
7441
8832
|
operationReservationJournalDigest,
|
|
7442
8833
|
assertOperationReservationTopology,
|
|
7443
8834
|
withOperationReservationDigests,
|
|
7444
8835
|
parseOperationReservation,
|
|
7445
|
-
createOperationReservation,
|
|
7446
|
-
writeOperationReservation,
|
|
7447
8836
|
readOperationReservation,
|
|
7448
8837
|
removeOperationReservation,
|
|
7449
8838
|
parseCheckpoint,
|
|
@@ -7478,6 +8867,8 @@ export {
|
|
|
7478
8867
|
workflowMetadataDigest,
|
|
7479
8868
|
assertWorkflowMetadataTransition,
|
|
7480
8869
|
assertParentWorkflowRelation,
|
|
8870
|
+
isActiveSoloHandoff,
|
|
8871
|
+
assertSoloHandoffSession,
|
|
7481
8872
|
buildTaskAggregateManifest,
|
|
7482
8873
|
taskAggregateDigest,
|
|
7483
8874
|
parseTaskAggregateManifest,
|
|
@@ -7490,13 +8881,41 @@ export {
|
|
|
7490
8881
|
normalizeClaimScope,
|
|
7491
8882
|
parseHandoff,
|
|
7492
8883
|
assertHandoffTransition,
|
|
7493
|
-
|
|
7494
|
-
|
|
7495
|
-
|
|
7496
|
-
|
|
7497
|
-
|
|
7498
|
-
|
|
7499
|
-
|
|
8884
|
+
acquireLocalLock,
|
|
8885
|
+
acquireEntityLocks,
|
|
8886
|
+
acquireOperationEntityLocks,
|
|
8887
|
+
readLocalLock,
|
|
8888
|
+
replaceFileAtomically,
|
|
8889
|
+
getOperationDefinition,
|
|
8890
|
+
assertOperationJournalMatchesDefinition,
|
|
8891
|
+
createPreparedOperationJournal,
|
|
8892
|
+
prepareOperationStores,
|
|
8893
|
+
readOperationJournal,
|
|
8894
|
+
updateOperationJournal,
|
|
8895
|
+
listUnfinishedOperationJournals,
|
|
8896
|
+
PROJECT_SCHEMA_LOCK,
|
|
8897
|
+
acquireProjectWriteBarrier,
|
|
8898
|
+
PRIVACY_MIN_VERSION,
|
|
8899
|
+
PRIVACY_POLICY_FILE,
|
|
8900
|
+
PRIVACY_EXCLUSIONS_FILE,
|
|
8901
|
+
parsePrivacyPolicyCandidate,
|
|
8902
|
+
parsePrivacyPolicy,
|
|
8903
|
+
parsePrivacyExclusions,
|
|
8904
|
+
createInitialPrivacyPolicy,
|
|
8905
|
+
assertPrivacyPolicyTransition,
|
|
8906
|
+
assertPrivacyExclusionsTransition,
|
|
8907
|
+
readPrivacyPolicySnapshot,
|
|
8908
|
+
readPrivacyPolicyStatus,
|
|
8909
|
+
readPrivacyAuthorityFile,
|
|
8910
|
+
containsEnhancedSensitiveText,
|
|
8911
|
+
isPrivacyExcluded,
|
|
8912
|
+
assertPrivacyValueAllowed,
|
|
8913
|
+
assertSharedPrivacyValue,
|
|
8914
|
+
acquireSharedPrivacyWriteBarrier,
|
|
8915
|
+
withSharedPrivacyWrite,
|
|
8916
|
+
assertSharedTaskWriteAtRoot,
|
|
8917
|
+
assertPrivacyRecoveryActionsAllowed,
|
|
8918
|
+
scanPrivacyActivation,
|
|
7500
8919
|
createConfirmedDecision,
|
|
7501
8920
|
publishConfirmedDecision,
|
|
7502
8921
|
readConfirmedDecision,
|
|
@@ -7511,4 +8930,4 @@ export {
|
|
|
7511
8930
|
V3ContextStore,
|
|
7512
8931
|
storedTaskAggregateDigest
|
|
7513
8932
|
};
|
|
7514
|
-
//# sourceMappingURL=chunk-
|
|
8933
|
+
//# sourceMappingURL=chunk-D5ABGAF4.js.map
|