mancode 0.6.4 → 0.6.5

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