mancode 0.6.3 → 0.6.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.en.md +87 -10
- package/README.md +60 -10
- package/dist/{chunk-NG72XZ3R.js → chunk-E2K22WYH.js} +2323 -1008
- package/dist/chunk-E2K22WYH.js.map +1 -0
- package/dist/chunk-GI24QVXE.js +1099 -0
- package/dist/chunk-GI24QVXE.js.map +1 -0
- package/dist/chunk-IRZQYMHD.js +334 -0
- package/dist/chunk-IRZQYMHD.js.map +1 -0
- package/dist/chunk-THOE33LU.js +1124 -0
- package/dist/chunk-THOE33LU.js.map +1 -0
- package/dist/{chunk-RDPFQODS.js → chunk-WJ6WARGG.js} +495 -250
- package/dist/chunk-WJ6WARGG.js.map +1 -0
- package/dist/chunk-WRBNOPFA.js +1235 -0
- package/dist/chunk-WRBNOPFA.js.map +1 -0
- package/dist/cli.js +22917 -21503
- package/dist/cli.js.map +1 -1
- package/dist/gateway/worker.d.ts +2 -0
- package/dist/gateway/worker.js +181 -0
- package/dist/gateway/worker.js.map +1 -0
- package/dist/privacy-gateway-Q4GD2JXF.js +20 -0
- package/dist/store-GSLSLZ7D.js +11 -0
- package/dist/{v3-adapter-7KIOYYWS.js → v3-adapter-T3IKK3LU.js} +4 -2
- package/dist/v3-adapter-T3IKK3LU.js.map +1 -0
- package/docs/README.md +34 -0
- package/docs/privacy-guide.md +76 -0
- package/docs/privacy-implementation-plan.md +101 -0
- package/docs/privacy-rule-sources.md +22 -0
- package/docs/privacy-upstream-license.txt +661 -0
- package/package.json +8 -3
- package/dist/chunk-NG72XZ3R.js.map +0 -1
- package/dist/chunk-RDPFQODS.js.map +0 -1
- package/dist/store-C3G3HN3N.js +0 -9
- /package/dist/{store-C3G3HN3N.js.map → privacy-gateway-Q4GD2JXF.js.map} +0 -0
- /package/dist/{v3-adapter-7KIOYYWS.js.map → store-GSLSLZ7D.js.map} +0 -0
|
@@ -0,0 +1,1235 @@
|
|
|
1
|
+
// src/context/ids.ts
|
|
2
|
+
import { randomBytes } from "crypto";
|
|
3
|
+
var CROCKFORD_BASE32 = "0123456789ABCDEFGHJKMNPQRSTVWXYZ";
|
|
4
|
+
var ULID_PATTERN = /^[0-7][0-9A-HJKMNPQRSTVWXYZ]{25}$/;
|
|
5
|
+
var MAX_ULID_TIMESTAMP = 2 ** 48 - 1;
|
|
6
|
+
function isUlid(value) {
|
|
7
|
+
return typeof value === "string" && ULID_PATTERN.test(value);
|
|
8
|
+
}
|
|
9
|
+
function assertUlid(value, label = "ULID") {
|
|
10
|
+
if (!isUlid(value)) {
|
|
11
|
+
throw new Error(`${label} must be a canonical ULID`);
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
function createUlid(now = Date.now(), entropy = randomBytes(10)) {
|
|
15
|
+
if (!Number.isSafeInteger(now) || now < 0 || now > MAX_ULID_TIMESTAMP) {
|
|
16
|
+
throw new Error("ULID timestamp must fit in 48 bits");
|
|
17
|
+
}
|
|
18
|
+
if (entropy.length !== 10) {
|
|
19
|
+
throw new Error("ULID entropy must contain exactly 10 bytes");
|
|
20
|
+
}
|
|
21
|
+
const timestamp = encodeBase32(BigInt(now), 10);
|
|
22
|
+
const random = encodeBase32(
|
|
23
|
+
BigInt(`0x${Buffer.from(entropy).toString("hex")}`),
|
|
24
|
+
16
|
|
25
|
+
);
|
|
26
|
+
return `${timestamp}${random}`;
|
|
27
|
+
}
|
|
28
|
+
function encodeBase32(value, length) {
|
|
29
|
+
let remaining = value;
|
|
30
|
+
let encoded = "";
|
|
31
|
+
for (let index = 0; index < length; index += 1) {
|
|
32
|
+
encoded = `${CROCKFORD_BASE32[Number(remaining & 31n)]}${encoded}`;
|
|
33
|
+
remaining >>= 5n;
|
|
34
|
+
}
|
|
35
|
+
if (remaining !== 0n) {
|
|
36
|
+
throw new Error("value does not fit in requested base32 length");
|
|
37
|
+
}
|
|
38
|
+
return encoded;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// src/context/validation.ts
|
|
42
|
+
function isRecord(value) {
|
|
43
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
const prototype = Object.getPrototypeOf(value);
|
|
47
|
+
return prototype === Object.prototype || prototype === null;
|
|
48
|
+
}
|
|
49
|
+
function assertRecord(value, label) {
|
|
50
|
+
if (!isRecord(value)) {
|
|
51
|
+
throw new Error(`${label} must be an object`);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
function assertKnownKeys(value, allowedKeys, label) {
|
|
55
|
+
const allowed = new Set(allowedKeys);
|
|
56
|
+
const unknown = Object.keys(value).filter((key) => !allowed.has(key));
|
|
57
|
+
if (unknown.length > 0) {
|
|
58
|
+
throw new Error(
|
|
59
|
+
`${label} contains unknown field(s): ${unknown.join(", ")}`
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// src/context/task-ref.ts
|
|
65
|
+
var TASK_REF_PATTERN = /^(local|shared):([0-7][0-9A-HJKMNPQRSTVWXYZ]{25})$/;
|
|
66
|
+
function formatTaskRef(taskRef) {
|
|
67
|
+
assertTaskRef(taskRef);
|
|
68
|
+
return `${taskRef.namespace}:${taskRef.taskId}`;
|
|
69
|
+
}
|
|
70
|
+
function parseTaskRef(input) {
|
|
71
|
+
if (typeof input !== "string") {
|
|
72
|
+
throw new Error("TaskRef must be a string in namespace:ULID form");
|
|
73
|
+
}
|
|
74
|
+
const match = TASK_REF_PATTERN.exec(input);
|
|
75
|
+
if (!match) {
|
|
76
|
+
throw new Error("TaskRef must use local:<ULID> or shared:<ULID>");
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
namespace: match[1],
|
|
80
|
+
taskId: match[2]
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function parseTaskRefValue(value) {
|
|
84
|
+
assertRecord(value, "TaskRef");
|
|
85
|
+
assertKnownKeys(value, ["namespace", "taskId"], "TaskRef");
|
|
86
|
+
if (value.namespace !== "local" && value.namespace !== "shared") {
|
|
87
|
+
throw new Error("TaskRef namespace must be local or shared");
|
|
88
|
+
}
|
|
89
|
+
assertUlid(value.taskId, "TaskRef taskId");
|
|
90
|
+
return {
|
|
91
|
+
namespace: value.namespace,
|
|
92
|
+
taskId: value.taskId
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
function assertTaskRef(value) {
|
|
96
|
+
parseTaskRefValue(value);
|
|
97
|
+
}
|
|
98
|
+
function sameTaskRef(left, right) {
|
|
99
|
+
return left.namespace === right.namespace && left.taskId === right.taskId;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// src/runtime/entity-home-store.ts
|
|
103
|
+
import path from "path";
|
|
104
|
+
function resolveTaskEntityHomeStore(context, taskRef) {
|
|
105
|
+
const normalized = normalizeContext(context);
|
|
106
|
+
const task = parseTaskRefValue(taskRef);
|
|
107
|
+
return task.namespace === "local" ? localTaskHomeStore(normalized) : coordinationEntityHomeStore(normalized);
|
|
108
|
+
}
|
|
109
|
+
function resolveLocalEntityHomeStore(context) {
|
|
110
|
+
return localTaskHomeStore(normalizeContext(context));
|
|
111
|
+
}
|
|
112
|
+
function resolveCoordinationEntityHomeStore(context) {
|
|
113
|
+
return coordinationEntityHomeStore(normalizeContext(context));
|
|
114
|
+
}
|
|
115
|
+
function operationDirectory(store) {
|
|
116
|
+
return path.join(store.root, "operations");
|
|
117
|
+
}
|
|
118
|
+
function reservationDirectory(store) {
|
|
119
|
+
return path.join(store.root, "reservations");
|
|
120
|
+
}
|
|
121
|
+
function lockDirectory(store) {
|
|
122
|
+
return path.join(store.root, "locks");
|
|
123
|
+
}
|
|
124
|
+
function claimDirectory(store) {
|
|
125
|
+
assertCoordinationStore(store, "claim");
|
|
126
|
+
return path.join(store.root, "claims");
|
|
127
|
+
}
|
|
128
|
+
function handoffDirectory(store) {
|
|
129
|
+
assertCoordinationStore(store, "handoff");
|
|
130
|
+
return path.join(store.root, "handoffs");
|
|
131
|
+
}
|
|
132
|
+
function taskHeadDirectory(store) {
|
|
133
|
+
assertCoordinationStore(store, "task head fence");
|
|
134
|
+
return path.join(store.root, "task-heads");
|
|
135
|
+
}
|
|
136
|
+
function normalizeContext(context) {
|
|
137
|
+
assertUlid(context.workspaceId, "entity home store workspaceId");
|
|
138
|
+
assertUlid(context.checkoutId, "entity home store checkoutId");
|
|
139
|
+
if (typeof context.projectRoot !== "string" || !context.projectRoot.trim()) {
|
|
140
|
+
throw new Error("entity home store projectRoot is required");
|
|
141
|
+
}
|
|
142
|
+
if (context.gitCommonDir === null) {
|
|
143
|
+
if (context.repositoryBindingId !== null) {
|
|
144
|
+
assertUlid(
|
|
145
|
+
context.repositoryBindingId,
|
|
146
|
+
"entity home store repositoryBindingId"
|
|
147
|
+
);
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
...context,
|
|
151
|
+
projectRoot: path.resolve(context.projectRoot),
|
|
152
|
+
gitCommonDir: null
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (typeof context.gitCommonDir !== "string" || !context.gitCommonDir.trim()) {
|
|
156
|
+
throw new Error("entity home store gitCommonDir must be a path or null");
|
|
157
|
+
}
|
|
158
|
+
if (context.repositoryBindingId === null) {
|
|
159
|
+
throw new Error(
|
|
160
|
+
"git coordination requires an entity home store repositoryBindingId"
|
|
161
|
+
);
|
|
162
|
+
}
|
|
163
|
+
assertUlid(
|
|
164
|
+
context.repositoryBindingId,
|
|
165
|
+
"entity home store repositoryBindingId"
|
|
166
|
+
);
|
|
167
|
+
return {
|
|
168
|
+
...context,
|
|
169
|
+
projectRoot: path.resolve(context.projectRoot),
|
|
170
|
+
gitCommonDir: path.resolve(context.gitCommonDir)
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function localTaskHomeStore(context) {
|
|
174
|
+
return {
|
|
175
|
+
kind: "checkout_local",
|
|
176
|
+
storeId: `checkout:${context.checkoutId}:${context.workspaceId}`,
|
|
177
|
+
root: path.join(context.projectRoot, ".mancode", "local", "runtime"),
|
|
178
|
+
workspaceId: context.workspaceId,
|
|
179
|
+
checkoutId: context.checkoutId,
|
|
180
|
+
repositoryBindingId: context.repositoryBindingId
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
function coordinationEntityHomeStore(context) {
|
|
184
|
+
if (context.gitCommonDir === null) {
|
|
185
|
+
return {
|
|
186
|
+
kind: "non_git_shared",
|
|
187
|
+
storeId: `non-git:${context.workspaceId}`,
|
|
188
|
+
root: path.join(
|
|
189
|
+
context.projectRoot,
|
|
190
|
+
".mancode",
|
|
191
|
+
"runtime",
|
|
192
|
+
"non-git",
|
|
193
|
+
context.workspaceId
|
|
194
|
+
),
|
|
195
|
+
workspaceId: context.workspaceId,
|
|
196
|
+
checkoutId: null,
|
|
197
|
+
repositoryBindingId: context.repositoryBindingId
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
if (context.repositoryBindingId === null) {
|
|
201
|
+
throw new Error("git coordination requires a repositoryBindingId");
|
|
202
|
+
}
|
|
203
|
+
return {
|
|
204
|
+
kind: "workspace_common_dir",
|
|
205
|
+
storeId: `workspace:${context.repositoryBindingId}:${context.workspaceId}`,
|
|
206
|
+
root: path.join(
|
|
207
|
+
context.gitCommonDir,
|
|
208
|
+
"mancode",
|
|
209
|
+
"workspaces",
|
|
210
|
+
context.workspaceId
|
|
211
|
+
),
|
|
212
|
+
workspaceId: context.workspaceId,
|
|
213
|
+
checkoutId: null,
|
|
214
|
+
repositoryBindingId: context.repositoryBindingId
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
function assertCoordinationStore(store, entity) {
|
|
218
|
+
if (store.kind === "checkout_local") {
|
|
219
|
+
throw new Error(
|
|
220
|
+
`${entity} requires a shared coordination entity home store`
|
|
221
|
+
);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// src/context/canonical.ts
|
|
226
|
+
import { createHash } from "crypto";
|
|
227
|
+
function canonicalizeJson(value, options = {}) {
|
|
228
|
+
return canonicalizeValue(value, options.numberPolicy ?? "safe-integer");
|
|
229
|
+
}
|
|
230
|
+
function digestCanonicalJson(value, options = {}) {
|
|
231
|
+
const canonical = canonicalizeJson(value, options);
|
|
232
|
+
return `sha256:${createHash("sha256").update(canonical, "utf8").digest("hex")}`;
|
|
233
|
+
}
|
|
234
|
+
function sortUtf8StringSet(values) {
|
|
235
|
+
const unique = /* @__PURE__ */ new Set();
|
|
236
|
+
for (const value of values) {
|
|
237
|
+
assertCanonicalString(value, "set item");
|
|
238
|
+
unique.add(value);
|
|
239
|
+
}
|
|
240
|
+
return [...unique].sort(
|
|
241
|
+
(left, right) => Buffer.from(left, "utf8").compare(Buffer.from(right, "utf8"))
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
function canonicalizeValue(value, numberPolicy) {
|
|
245
|
+
if (value === null) return "null";
|
|
246
|
+
if (typeof value === "string") {
|
|
247
|
+
assertCanonicalString(value, "string");
|
|
248
|
+
return JSON.stringify(value);
|
|
249
|
+
}
|
|
250
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
251
|
+
if (typeof value === "number") {
|
|
252
|
+
if (!Number.isFinite(value) || Object.is(value, -0)) {
|
|
253
|
+
throw new Error(
|
|
254
|
+
"canonical JSON numbers must be finite and must not be negative zero"
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
if (numberPolicy === "safe-integer" && !Number.isSafeInteger(value)) {
|
|
258
|
+
throw new Error(
|
|
259
|
+
"canonical JSON numbers must be safe integers for this schema"
|
|
260
|
+
);
|
|
261
|
+
}
|
|
262
|
+
return JSON.stringify(value);
|
|
263
|
+
}
|
|
264
|
+
if (Array.isArray(value)) {
|
|
265
|
+
assertCanonicalArray(value);
|
|
266
|
+
return `[${value.map((item) => canonicalizeValue(item, numberPolicy)).join(",")}]`;
|
|
267
|
+
}
|
|
268
|
+
if (isPlainObject(value)) {
|
|
269
|
+
assertCanonicalObject(value);
|
|
270
|
+
const keys = Object.keys(value).sort();
|
|
271
|
+
return `{${keys.map((key) => {
|
|
272
|
+
assertCanonicalString(key, "object key");
|
|
273
|
+
return `${JSON.stringify(key)}:${canonicalizeValue(value[key], numberPolicy)}`;
|
|
274
|
+
}).join(",")}}`;
|
|
275
|
+
}
|
|
276
|
+
throw new Error("canonical JSON only accepts plain JSON values");
|
|
277
|
+
}
|
|
278
|
+
function assertCanonicalArray(value) {
|
|
279
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
280
|
+
if (!Object.hasOwn(value, index)) {
|
|
281
|
+
throw new Error("canonical JSON arrays must not be sparse");
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
const ownKeys = Object.keys(value);
|
|
285
|
+
if (ownKeys.some((key) => !/^(0|[1-9]\d*)$/.test(key))) {
|
|
286
|
+
throw new Error("canonical JSON arrays must not have non-index properties");
|
|
287
|
+
}
|
|
288
|
+
if (Object.getOwnPropertyNames(value).some(
|
|
289
|
+
(key) => key !== "length" && !ownKeys.includes(key)
|
|
290
|
+
) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
291
|
+
throw new Error("canonical JSON arrays must not have hidden properties");
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
function assertCanonicalObject(value) {
|
|
295
|
+
const ownKeys = Object.keys(value);
|
|
296
|
+
if (Object.getOwnPropertyNames(value).some((key) => !ownKeys.includes(key)) || Object.getOwnPropertySymbols(value).length > 0) {
|
|
297
|
+
throw new Error("canonical JSON objects must not have hidden properties");
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
function isPlainObject(value) {
|
|
301
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
302
|
+
return false;
|
|
303
|
+
}
|
|
304
|
+
const prototype = Object.getPrototypeOf(value);
|
|
305
|
+
return prototype === Object.prototype || prototype === null;
|
|
306
|
+
}
|
|
307
|
+
function assertCanonicalString(value, label) {
|
|
308
|
+
if (value.includes("\0")) {
|
|
309
|
+
throw new Error(`canonical JSON ${label} must not contain NUL`);
|
|
310
|
+
}
|
|
311
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
312
|
+
const codeUnit = value.charCodeAt(index);
|
|
313
|
+
if (codeUnit < 55296 || codeUnit > 57343) continue;
|
|
314
|
+
const next = value.charCodeAt(index + 1);
|
|
315
|
+
const isHigh = codeUnit <= 56319;
|
|
316
|
+
const isLowNext = next >= 56320 && next <= 57343;
|
|
317
|
+
if (!isHigh || !isLowNext) {
|
|
318
|
+
throw new Error(
|
|
319
|
+
`canonical JSON ${label} must not contain a lone surrogate`
|
|
320
|
+
);
|
|
321
|
+
}
|
|
322
|
+
index += 1;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// src/team/policy.ts
|
|
327
|
+
var V3_LAYOUT_VERSION = 3;
|
|
328
|
+
function parseProjectConfig(value) {
|
|
329
|
+
assertRecord(value, "project config");
|
|
330
|
+
assertKnownKeys(
|
|
331
|
+
value,
|
|
332
|
+
[
|
|
333
|
+
"schemaVersion",
|
|
334
|
+
"revision",
|
|
335
|
+
"workspaceId",
|
|
336
|
+
"transport",
|
|
337
|
+
"lastOperationId",
|
|
338
|
+
"updatedAt"
|
|
339
|
+
],
|
|
340
|
+
"project config"
|
|
341
|
+
);
|
|
342
|
+
if (value.schemaVersion !== 1) {
|
|
343
|
+
throw new Error("project config schemaVersion must be 1");
|
|
344
|
+
}
|
|
345
|
+
assertUlid(value.workspaceId, "project config workspaceId");
|
|
346
|
+
return {
|
|
347
|
+
schemaVersion: 1,
|
|
348
|
+
revision: parsePositiveInteger(value.revision, "project config revision"),
|
|
349
|
+
workspaceId: value.workspaceId,
|
|
350
|
+
transport: parseTransport(value.transport),
|
|
351
|
+
lastOperationId: parseUlidOrNull(
|
|
352
|
+
value.lastOperationId,
|
|
353
|
+
"project config lastOperationId"
|
|
354
|
+
),
|
|
355
|
+
updatedAt: parseTimestamp(value.updatedAt, "project config updatedAt")
|
|
356
|
+
};
|
|
357
|
+
}
|
|
358
|
+
function parseTeamPolicy(value) {
|
|
359
|
+
assertRecord(value, "team policy");
|
|
360
|
+
assertKnownKeys(
|
|
361
|
+
value,
|
|
362
|
+
[
|
|
363
|
+
"schemaVersion",
|
|
364
|
+
"revision",
|
|
365
|
+
"workspaceId",
|
|
366
|
+
"policy",
|
|
367
|
+
"recentDays",
|
|
368
|
+
"defaultVisibility",
|
|
369
|
+
"shareConfirmedDecisions",
|
|
370
|
+
"retention",
|
|
371
|
+
"lastOperationId",
|
|
372
|
+
"updatedAt"
|
|
373
|
+
],
|
|
374
|
+
"team policy"
|
|
375
|
+
);
|
|
376
|
+
if (value.schemaVersion !== 1) {
|
|
377
|
+
throw new Error("team policy schemaVersion must be 1");
|
|
378
|
+
}
|
|
379
|
+
assertUlid(value.workspaceId, "team policy workspaceId");
|
|
380
|
+
if (value.policy !== "on" && value.policy !== "off" && value.policy !== "auto") {
|
|
381
|
+
throw new Error("team policy policy is invalid");
|
|
382
|
+
}
|
|
383
|
+
if (value.defaultVisibility !== "local" && value.defaultVisibility !== "shared") {
|
|
384
|
+
throw new Error("team policy defaultVisibility is invalid");
|
|
385
|
+
}
|
|
386
|
+
if (typeof value.shareConfirmedDecisions !== "boolean") {
|
|
387
|
+
throw new Error("team policy shareConfirmedDecisions must be boolean");
|
|
388
|
+
}
|
|
389
|
+
return {
|
|
390
|
+
schemaVersion: 1,
|
|
391
|
+
revision: parsePositiveInteger(value.revision, "team policy revision"),
|
|
392
|
+
workspaceId: value.workspaceId,
|
|
393
|
+
policy: value.policy,
|
|
394
|
+
recentDays: parseNonNegativeInteger(
|
|
395
|
+
value.recentDays,
|
|
396
|
+
"team policy recentDays"
|
|
397
|
+
),
|
|
398
|
+
defaultVisibility: value.defaultVisibility,
|
|
399
|
+
shareConfirmedDecisions: value.shareConfirmedDecisions,
|
|
400
|
+
retention: parseRetention(value.retention),
|
|
401
|
+
lastOperationId: parseUlidOrNull(
|
|
402
|
+
value.lastOperationId,
|
|
403
|
+
"team policy lastOperationId"
|
|
404
|
+
),
|
|
405
|
+
updatedAt: parseTimestamp(value.updatedAt, "team policy updatedAt")
|
|
406
|
+
};
|
|
407
|
+
}
|
|
408
|
+
function projectConfigIdentityDigest(config) {
|
|
409
|
+
return digestCanonicalJson({
|
|
410
|
+
workspaceId: config.workspaceId,
|
|
411
|
+
configSchemaVersion: config.schemaVersion,
|
|
412
|
+
layoutVersion: V3_LAYOUT_VERSION
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
function projectConfigDigest(config) {
|
|
416
|
+
return digestCanonicalJson(parseProjectConfig(config));
|
|
417
|
+
}
|
|
418
|
+
function assertConfigPolicyConsistency(config, policy) {
|
|
419
|
+
if (config.workspaceId !== policy.workspaceId) {
|
|
420
|
+
throw new Error("project config and team policy workspaceId must match");
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
function assertProjectConfigTransition(previous, next, kind) {
|
|
424
|
+
assertConfigIdentityStable(previous, next);
|
|
425
|
+
assertRevisionIncrease(
|
|
426
|
+
previous.revision,
|
|
427
|
+
next.revision,
|
|
428
|
+
"project config revision"
|
|
429
|
+
);
|
|
430
|
+
const transportChanged = previous.transport.mode !== next.transport.mode || previous.transport.remote !== next.transport.remote || previous.transport.epoch !== next.transport.epoch;
|
|
431
|
+
if (transportChanged && kind === "ordinary") {
|
|
432
|
+
throw new Error(
|
|
433
|
+
"project config transport may only change through transport_set or transport_migrate"
|
|
434
|
+
);
|
|
435
|
+
}
|
|
436
|
+
if (!transportChanged && kind !== "ordinary") {
|
|
437
|
+
throw new Error(
|
|
438
|
+
"transport mutation requires a changed project config transport"
|
|
439
|
+
);
|
|
440
|
+
}
|
|
441
|
+
if (kind !== "ordinary" && next.transport.epoch !== previous.transport.epoch + 1) {
|
|
442
|
+
throw new Error(
|
|
443
|
+
"transport mutation must increase the authority epoch exactly once"
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
function assertTeamPolicyTransition(previous, next) {
|
|
448
|
+
if (previous.schemaVersion !== next.schemaVersion || previous.workspaceId !== next.workspaceId) {
|
|
449
|
+
throw new Error("team policy schemaVersion and workspaceId are immutable");
|
|
450
|
+
}
|
|
451
|
+
assertRevisionIncrease(
|
|
452
|
+
previous.revision,
|
|
453
|
+
next.revision,
|
|
454
|
+
"team policy revision"
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
function parseTransport(value) {
|
|
458
|
+
assertRecord(value, "project config transport");
|
|
459
|
+
assertKnownKeys(
|
|
460
|
+
value,
|
|
461
|
+
["mode", "remote", "epoch"],
|
|
462
|
+
"project config transport"
|
|
463
|
+
);
|
|
464
|
+
if (value.mode !== "local" && value.mode !== "git-ref") {
|
|
465
|
+
throw new Error("project config transport mode is invalid");
|
|
466
|
+
}
|
|
467
|
+
const remote = parseNonEmptyStringOrNull(
|
|
468
|
+
value.remote,
|
|
469
|
+
"project config transport remote"
|
|
470
|
+
);
|
|
471
|
+
if (value.mode === "local" && remote !== null) {
|
|
472
|
+
throw new Error("local project config transport must not set a remote");
|
|
473
|
+
}
|
|
474
|
+
if (value.mode === "git-ref" && remote === null) {
|
|
475
|
+
throw new Error("git-ref project config transport requires a remote");
|
|
476
|
+
}
|
|
477
|
+
const epoch = value.epoch === void 0 ? 1 : parsePositiveInteger(value.epoch, "project config transport epoch");
|
|
478
|
+
return { mode: value.mode, remote, epoch };
|
|
479
|
+
}
|
|
480
|
+
function parseRetention(value) {
|
|
481
|
+
assertRecord(value, "team policy retention");
|
|
482
|
+
assertKnownKeys(
|
|
483
|
+
value,
|
|
484
|
+
["localRawArtifactDays", "localCacheDays", "completedSessionDays"],
|
|
485
|
+
"team policy retention"
|
|
486
|
+
);
|
|
487
|
+
return {
|
|
488
|
+
localRawArtifactDays: parseNonNegativeInteger(
|
|
489
|
+
value.localRawArtifactDays,
|
|
490
|
+
"team policy retention localRawArtifactDays"
|
|
491
|
+
),
|
|
492
|
+
localCacheDays: parseNonNegativeInteger(
|
|
493
|
+
value.localCacheDays,
|
|
494
|
+
"team policy retention localCacheDays"
|
|
495
|
+
),
|
|
496
|
+
completedSessionDays: parseNonNegativeInteger(
|
|
497
|
+
value.completedSessionDays,
|
|
498
|
+
"team policy retention completedSessionDays"
|
|
499
|
+
)
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
function assertConfigIdentityStable(previous, next) {
|
|
503
|
+
if (previous.schemaVersion !== next.schemaVersion || previous.workspaceId !== next.workspaceId) {
|
|
504
|
+
throw new Error(
|
|
505
|
+
"project config schemaVersion and workspaceId are immutable"
|
|
506
|
+
);
|
|
507
|
+
}
|
|
508
|
+
}
|
|
509
|
+
function assertRevisionIncrease(previous, next, label) {
|
|
510
|
+
if (next !== previous + 1) {
|
|
511
|
+
throw new Error(`${label} must increase exactly once per mutation`);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
function parsePositiveInteger(value, label) {
|
|
515
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
|
|
516
|
+
throw new Error(`${label} must be a positive integer`);
|
|
517
|
+
}
|
|
518
|
+
return value;
|
|
519
|
+
}
|
|
520
|
+
function parseNonNegativeInteger(value, label) {
|
|
521
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 0) {
|
|
522
|
+
throw new Error(`${label} must be a non-negative integer`);
|
|
523
|
+
}
|
|
524
|
+
return value;
|
|
525
|
+
}
|
|
526
|
+
function parseUlidOrNull(value, label) {
|
|
527
|
+
if (value === null) return null;
|
|
528
|
+
assertUlid(value, label);
|
|
529
|
+
return value;
|
|
530
|
+
}
|
|
531
|
+
function parseNonEmptyStringOrNull(value, label) {
|
|
532
|
+
if (value === null) return null;
|
|
533
|
+
if (typeof value !== "string" || !value.trim()) {
|
|
534
|
+
throw new Error(`${label} must be a non-empty string or null`);
|
|
535
|
+
}
|
|
536
|
+
return value;
|
|
537
|
+
}
|
|
538
|
+
function parseTimestamp(value, label) {
|
|
539
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
540
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
541
|
+
}
|
|
542
|
+
return value;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
// src/runtime/project-runtime.ts
|
|
546
|
+
import { execFile as execFileCallback } from "child_process";
|
|
547
|
+
import { createHash as createHash2 } from "crypto";
|
|
548
|
+
import { mkdir, readFile, realpath, rename, writeFile } from "fs/promises";
|
|
549
|
+
import path2 from "path";
|
|
550
|
+
import { promisify } from "util";
|
|
551
|
+
|
|
552
|
+
// src/runtime/workspace-binding.ts
|
|
553
|
+
var DIGEST_PATTERN = /^sha256:[a-f0-9]{64}$/;
|
|
554
|
+
function parseWorkspaceBinding(value) {
|
|
555
|
+
assertRecord(value, "workspace binding");
|
|
556
|
+
assertKnownKeys(
|
|
557
|
+
value,
|
|
558
|
+
[
|
|
559
|
+
"schemaVersion",
|
|
560
|
+
"workspaceId",
|
|
561
|
+
"repositoryBindingId",
|
|
562
|
+
"projectPathFromWorktreeRoot",
|
|
563
|
+
"configSchemaVersion",
|
|
564
|
+
"configIdentityDigest",
|
|
565
|
+
"registeredAt"
|
|
566
|
+
],
|
|
567
|
+
"workspace binding"
|
|
568
|
+
);
|
|
569
|
+
if (value.schemaVersion !== 1) {
|
|
570
|
+
throw new Error("workspace binding schemaVersion must be 1");
|
|
571
|
+
}
|
|
572
|
+
assertUlid(value.workspaceId, "workspace binding workspaceId");
|
|
573
|
+
assertUlid(
|
|
574
|
+
value.repositoryBindingId,
|
|
575
|
+
"workspace binding repositoryBindingId"
|
|
576
|
+
);
|
|
577
|
+
return {
|
|
578
|
+
schemaVersion: 1,
|
|
579
|
+
workspaceId: value.workspaceId,
|
|
580
|
+
repositoryBindingId: value.repositoryBindingId,
|
|
581
|
+
projectPathFromWorktreeRoot: parseProjectRelativePath(
|
|
582
|
+
value.projectPathFromWorktreeRoot
|
|
583
|
+
),
|
|
584
|
+
configSchemaVersion: parsePositiveInteger2(
|
|
585
|
+
value.configSchemaVersion,
|
|
586
|
+
"workspace binding configSchemaVersion"
|
|
587
|
+
),
|
|
588
|
+
configIdentityDigest: parseDigest(
|
|
589
|
+
value.configIdentityDigest,
|
|
590
|
+
"workspace binding configIdentityDigest"
|
|
591
|
+
),
|
|
592
|
+
registeredAt: parseTimestamp2(
|
|
593
|
+
value.registeredAt,
|
|
594
|
+
"workspace binding registeredAt"
|
|
595
|
+
)
|
|
596
|
+
};
|
|
597
|
+
}
|
|
598
|
+
function parseCheckoutBinding(value) {
|
|
599
|
+
assertRecord(value, "checkout binding");
|
|
600
|
+
assertKnownKeys(
|
|
601
|
+
value,
|
|
602
|
+
[
|
|
603
|
+
"schemaVersion",
|
|
604
|
+
"workspaceId",
|
|
605
|
+
"repositoryBindingId",
|
|
606
|
+
"checkoutId",
|
|
607
|
+
"worktreeGitDirHash",
|
|
608
|
+
"projectRealpathHash",
|
|
609
|
+
"registeredAt",
|
|
610
|
+
"lastSeenAt"
|
|
611
|
+
],
|
|
612
|
+
"checkout binding"
|
|
613
|
+
);
|
|
614
|
+
if (value.schemaVersion !== 1) {
|
|
615
|
+
throw new Error("checkout binding schemaVersion must be 1");
|
|
616
|
+
}
|
|
617
|
+
assertUlid(value.workspaceId, "checkout binding workspaceId");
|
|
618
|
+
assertUlid(value.repositoryBindingId, "checkout binding repositoryBindingId");
|
|
619
|
+
assertUlid(value.checkoutId, "checkout binding checkoutId");
|
|
620
|
+
return {
|
|
621
|
+
schemaVersion: 1,
|
|
622
|
+
workspaceId: value.workspaceId,
|
|
623
|
+
repositoryBindingId: value.repositoryBindingId,
|
|
624
|
+
checkoutId: value.checkoutId,
|
|
625
|
+
worktreeGitDirHash: parseDigest(
|
|
626
|
+
value.worktreeGitDirHash,
|
|
627
|
+
"checkout binding worktreeGitDirHash"
|
|
628
|
+
),
|
|
629
|
+
projectRealpathHash: parseDigest(
|
|
630
|
+
value.projectRealpathHash,
|
|
631
|
+
"checkout binding projectRealpathHash"
|
|
632
|
+
),
|
|
633
|
+
registeredAt: parseTimestamp2(
|
|
634
|
+
value.registeredAt,
|
|
635
|
+
"checkout binding registeredAt"
|
|
636
|
+
),
|
|
637
|
+
lastSeenAt: parseTimestamp2(value.lastSeenAt, "checkout binding lastSeenAt")
|
|
638
|
+
};
|
|
639
|
+
}
|
|
640
|
+
function assertWorkspaceBindingMatchesConfig(binding, config) {
|
|
641
|
+
if (binding.workspaceId !== config.workspaceId || binding.configSchemaVersion !== config.schemaVersion || binding.configIdentityDigest !== projectConfigIdentityDigest(config)) {
|
|
642
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
function assertWorkspaceBindingCompatible(existing, candidate) {
|
|
646
|
+
if (existing.workspaceId !== candidate.workspaceId || existing.repositoryBindingId !== candidate.repositoryBindingId || existing.projectPathFromWorktreeRoot !== candidate.projectPathFromWorktreeRoot || existing.configSchemaVersion !== candidate.configSchemaVersion || existing.configIdentityDigest !== candidate.configIdentityDigest) {
|
|
647
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
function assertCheckoutBindingMatchesWorkspace(checkout, workspace) {
|
|
651
|
+
if (checkout.workspaceId !== workspace.workspaceId || checkout.repositoryBindingId !== workspace.repositoryBindingId) {
|
|
652
|
+
throw new Error("MANCODE_CHECKOUT_BINDING_MISMATCH");
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
function localCoordinationDomainId(repositoryBindingId, workspaceId) {
|
|
656
|
+
assertUlid(repositoryBindingId, "local coordination repositoryBindingId");
|
|
657
|
+
assertUlid(workspaceId, "local coordination workspaceId");
|
|
658
|
+
return `local:${repositoryBindingId}:${workspaceId}`;
|
|
659
|
+
}
|
|
660
|
+
function gitRefCoordinationDomainId(remoteIdentityHash, workspaceId, transportEpoch) {
|
|
661
|
+
parseDigest(remoteIdentityHash, "git-ref coordination remoteIdentityHash");
|
|
662
|
+
assertUlid(workspaceId, "git-ref coordination workspaceId");
|
|
663
|
+
if (typeof transportEpoch === "number") {
|
|
664
|
+
if (!Number.isSafeInteger(transportEpoch) || transportEpoch < 1) {
|
|
665
|
+
throw new Error(
|
|
666
|
+
"git-ref coordination transportEpoch must be a positive integer"
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
} else {
|
|
670
|
+
assertUlid(transportEpoch, "git-ref coordination transportEpoch");
|
|
671
|
+
}
|
|
672
|
+
return `git-ref:${remoteIdentityHash}:${workspaceId}:${transportEpoch}`;
|
|
673
|
+
}
|
|
674
|
+
function parseProjectRelativePath(value) {
|
|
675
|
+
if (typeof value !== "string" || value.includes("\0") || value.includes("\\")) {
|
|
676
|
+
throw new Error("workspace binding projectPathFromWorktreeRoot is invalid");
|
|
677
|
+
}
|
|
678
|
+
if (value === ".") return value;
|
|
679
|
+
if (!value || value.startsWith("/") || value.split("/").some((segment) => segment === "" || segment === "." || segment === "..")) {
|
|
680
|
+
throw new Error("workspace binding projectPathFromWorktreeRoot is invalid");
|
|
681
|
+
}
|
|
682
|
+
return value;
|
|
683
|
+
}
|
|
684
|
+
function parsePositiveInteger2(value, label) {
|
|
685
|
+
if (typeof value !== "number" || !Number.isSafeInteger(value) || value < 1) {
|
|
686
|
+
throw new Error(`${label} must be a positive integer`);
|
|
687
|
+
}
|
|
688
|
+
return value;
|
|
689
|
+
}
|
|
690
|
+
function parseDigest(value, label) {
|
|
691
|
+
if (typeof value !== "string" || !DIGEST_PATTERN.test(value)) {
|
|
692
|
+
throw new Error(`${label} must be a sha256 digest`);
|
|
693
|
+
}
|
|
694
|
+
return value;
|
|
695
|
+
}
|
|
696
|
+
function parseTimestamp2(value, label) {
|
|
697
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
698
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
699
|
+
}
|
|
700
|
+
return value;
|
|
701
|
+
}
|
|
702
|
+
|
|
703
|
+
// src/runtime/project-runtime.ts
|
|
704
|
+
var execFile = promisify(execFileCallback);
|
|
705
|
+
var DIGEST_PATTERN2 = /^sha256:[a-f0-9]{64}$/;
|
|
706
|
+
async function ensureProjectRuntimeContext(projectRoot, now = /* @__PURE__ */ new Date()) {
|
|
707
|
+
const root = path2.resolve(projectRoot);
|
|
708
|
+
const config = await readProjectConfig(root);
|
|
709
|
+
const git = await inspectGitCheckout(root);
|
|
710
|
+
const timestamp = now.toISOString();
|
|
711
|
+
if (git === null) {
|
|
712
|
+
const checkout2 = await ensureRuntimeCheckoutRecord(root, {
|
|
713
|
+
workspaceId: config.workspaceId,
|
|
714
|
+
repositoryBindingId: null,
|
|
715
|
+
now: timestamp
|
|
716
|
+
});
|
|
717
|
+
return runtimeContext(
|
|
718
|
+
root,
|
|
719
|
+
config.workspaceId,
|
|
720
|
+
checkout2.checkoutId,
|
|
721
|
+
null,
|
|
722
|
+
null
|
|
723
|
+
);
|
|
724
|
+
}
|
|
725
|
+
const repository = await ensureRepositoryRuntimeBinding(
|
|
726
|
+
git.commonDir,
|
|
727
|
+
timestamp
|
|
728
|
+
);
|
|
729
|
+
const workspace = await ensureWorkspaceBinding(
|
|
730
|
+
root,
|
|
731
|
+
git,
|
|
732
|
+
repository.repositoryBindingId,
|
|
733
|
+
config,
|
|
734
|
+
timestamp
|
|
735
|
+
);
|
|
736
|
+
const checkout = await ensureRuntimeCheckoutRecord(root, {
|
|
737
|
+
workspaceId: config.workspaceId,
|
|
738
|
+
repositoryBindingId: repository.repositoryBindingId,
|
|
739
|
+
now: timestamp
|
|
740
|
+
});
|
|
741
|
+
await ensureCheckoutBinding(root, git, workspace, checkout, timestamp);
|
|
742
|
+
return runtimeContext(
|
|
743
|
+
root,
|
|
744
|
+
config.workspaceId,
|
|
745
|
+
checkout.checkoutId,
|
|
746
|
+
repository.repositoryBindingId,
|
|
747
|
+
git.commonDir
|
|
748
|
+
);
|
|
749
|
+
}
|
|
750
|
+
async function readProjectRuntimeContext(projectRoot) {
|
|
751
|
+
const root = path2.resolve(projectRoot);
|
|
752
|
+
const config = await readProjectConfig(root);
|
|
753
|
+
const checkout = await readRuntimeCheckoutRecord(root);
|
|
754
|
+
if (checkout === null || checkout.workspaceId !== config.workspaceId) {
|
|
755
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
756
|
+
}
|
|
757
|
+
const git = await inspectGitCheckout(root);
|
|
758
|
+
if (git === null) {
|
|
759
|
+
if (checkout.repositoryBindingId !== null) {
|
|
760
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
761
|
+
}
|
|
762
|
+
return runtimeContext(
|
|
763
|
+
root,
|
|
764
|
+
config.workspaceId,
|
|
765
|
+
checkout.checkoutId,
|
|
766
|
+
null,
|
|
767
|
+
null
|
|
768
|
+
);
|
|
769
|
+
}
|
|
770
|
+
if (checkout.repositoryBindingId === null) {
|
|
771
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
772
|
+
}
|
|
773
|
+
const repository = await readRepositoryRuntimeBinding(git.commonDir);
|
|
774
|
+
if (repository === null || repository.repositoryBindingId !== checkout.repositoryBindingId) {
|
|
775
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
776
|
+
}
|
|
777
|
+
const workspace = await readWorkspaceBinding(
|
|
778
|
+
git.commonDir,
|
|
779
|
+
config.workspaceId
|
|
780
|
+
);
|
|
781
|
+
if (workspace === null) throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
782
|
+
assertWorkspaceBindingMatchesConfig(workspace, config);
|
|
783
|
+
assertCheckoutBindingMatchesWorkspace(
|
|
784
|
+
await requireCheckoutBinding(root),
|
|
785
|
+
workspace
|
|
786
|
+
);
|
|
787
|
+
return runtimeContext(
|
|
788
|
+
root,
|
|
789
|
+
config.workspaceId,
|
|
790
|
+
checkout.checkoutId,
|
|
791
|
+
checkout.repositoryBindingId,
|
|
792
|
+
git.commonDir
|
|
793
|
+
);
|
|
794
|
+
}
|
|
795
|
+
async function readCheckoutCodeHead(projectRoot) {
|
|
796
|
+
const root = path2.resolve(projectRoot);
|
|
797
|
+
const output = await runGit(root, ["rev-parse", "HEAD"]);
|
|
798
|
+
return output === null || !output.trim() ? null : output.trim();
|
|
799
|
+
}
|
|
800
|
+
async function readCheckoutBranch(projectRoot) {
|
|
801
|
+
const root = path2.resolve(projectRoot);
|
|
802
|
+
const output = await runGit(root, ["symbolic-ref", "--short", "-q", "HEAD"]);
|
|
803
|
+
return output === null || !output.trim() ? null : output.trim();
|
|
804
|
+
}
|
|
805
|
+
function runtimeCheckoutRecordPath(projectRoot) {
|
|
806
|
+
return path2.join(
|
|
807
|
+
path2.resolve(projectRoot),
|
|
808
|
+
".mancode",
|
|
809
|
+
"local",
|
|
810
|
+
"runtime",
|
|
811
|
+
"checkout.json"
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
function runtimeCheckoutBindingPath(projectRoot) {
|
|
815
|
+
return path2.join(
|
|
816
|
+
path2.resolve(projectRoot),
|
|
817
|
+
".mancode",
|
|
818
|
+
"local",
|
|
819
|
+
"runtime",
|
|
820
|
+
"checkout-binding.json"
|
|
821
|
+
);
|
|
822
|
+
}
|
|
823
|
+
function repositoryRuntimeBindingPath(gitCommonDir) {
|
|
824
|
+
return path2.join(path2.resolve(gitCommonDir), "mancode", "repository.json");
|
|
825
|
+
}
|
|
826
|
+
function workspaceRuntimeBindingPath(gitCommonDir, workspaceId) {
|
|
827
|
+
assertUlid(workspaceId, "workspace runtime binding workspaceId");
|
|
828
|
+
return path2.join(
|
|
829
|
+
path2.resolve(gitCommonDir),
|
|
830
|
+
"mancode",
|
|
831
|
+
"workspaces",
|
|
832
|
+
workspaceId,
|
|
833
|
+
"binding.json"
|
|
834
|
+
);
|
|
835
|
+
}
|
|
836
|
+
function parseRuntimeCheckoutRecord(value) {
|
|
837
|
+
assertRecord(value, "runtime checkout record");
|
|
838
|
+
assertKnownKeys(
|
|
839
|
+
value,
|
|
840
|
+
[
|
|
841
|
+
"schemaVersion",
|
|
842
|
+
"workspaceId",
|
|
843
|
+
"checkoutId",
|
|
844
|
+
"repositoryBindingId",
|
|
845
|
+
"registeredAt",
|
|
846
|
+
"lastSeenAt"
|
|
847
|
+
],
|
|
848
|
+
"runtime checkout record"
|
|
849
|
+
);
|
|
850
|
+
if (value.schemaVersion !== 1) {
|
|
851
|
+
throw new Error("runtime checkout record schemaVersion must be 1");
|
|
852
|
+
}
|
|
853
|
+
assertUlid(value.workspaceId, "runtime checkout record workspaceId");
|
|
854
|
+
assertUlid(value.checkoutId, "runtime checkout record checkoutId");
|
|
855
|
+
if (value.repositoryBindingId !== null) {
|
|
856
|
+
assertUlid(
|
|
857
|
+
value.repositoryBindingId,
|
|
858
|
+
"runtime checkout record repositoryBindingId"
|
|
859
|
+
);
|
|
860
|
+
}
|
|
861
|
+
return {
|
|
862
|
+
schemaVersion: 1,
|
|
863
|
+
workspaceId: value.workspaceId,
|
|
864
|
+
checkoutId: value.checkoutId,
|
|
865
|
+
repositoryBindingId: value.repositoryBindingId,
|
|
866
|
+
registeredAt: parseTimestamp3(
|
|
867
|
+
value.registeredAt,
|
|
868
|
+
"runtime checkout record registeredAt"
|
|
869
|
+
),
|
|
870
|
+
lastSeenAt: parseTimestamp3(
|
|
871
|
+
value.lastSeenAt,
|
|
872
|
+
"runtime checkout record lastSeenAt"
|
|
873
|
+
)
|
|
874
|
+
};
|
|
875
|
+
}
|
|
876
|
+
function parseRepositoryRuntimeBinding(value) {
|
|
877
|
+
assertRecord(value, "repository runtime binding");
|
|
878
|
+
assertKnownKeys(
|
|
879
|
+
value,
|
|
880
|
+
["schemaVersion", "repositoryBindingId", "commonDirHash", "createdAt"],
|
|
881
|
+
"repository runtime binding"
|
|
882
|
+
);
|
|
883
|
+
if (value.schemaVersion !== 1) {
|
|
884
|
+
throw new Error("repository runtime binding schemaVersion must be 1");
|
|
885
|
+
}
|
|
886
|
+
assertUlid(value.repositoryBindingId, "repository runtime binding ID");
|
|
887
|
+
return {
|
|
888
|
+
schemaVersion: 1,
|
|
889
|
+
repositoryBindingId: value.repositoryBindingId,
|
|
890
|
+
commonDirHash: parseDigest2(
|
|
891
|
+
value.commonDirHash,
|
|
892
|
+
"repository runtime binding commonDirHash"
|
|
893
|
+
),
|
|
894
|
+
createdAt: parseTimestamp3(
|
|
895
|
+
value.createdAt,
|
|
896
|
+
"repository runtime binding createdAt"
|
|
897
|
+
)
|
|
898
|
+
};
|
|
899
|
+
}
|
|
900
|
+
async function readProjectConfig(projectRoot) {
|
|
901
|
+
try {
|
|
902
|
+
return parseProjectConfig(
|
|
903
|
+
JSON.parse(
|
|
904
|
+
await readFile(
|
|
905
|
+
path2.join(projectRoot, ".mancode", "shared", "config.json"),
|
|
906
|
+
"utf8"
|
|
907
|
+
)
|
|
908
|
+
)
|
|
909
|
+
);
|
|
910
|
+
} catch (error) {
|
|
911
|
+
if (isNotFound(error))
|
|
912
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
913
|
+
if (error instanceof SyntaxError) {
|
|
914
|
+
throw new Error("MANCODE_CONTEXT_ENTITY_CORRUPT: shared/config.json");
|
|
915
|
+
}
|
|
916
|
+
throw error;
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
async function inspectGitCheckout(projectRoot) {
|
|
920
|
+
const [commonDirRaw, gitDirRaw, worktreeRootRaw] = await Promise.all([
|
|
921
|
+
runGit(projectRoot, [
|
|
922
|
+
"rev-parse",
|
|
923
|
+
"--path-format=absolute",
|
|
924
|
+
"--git-common-dir"
|
|
925
|
+
]),
|
|
926
|
+
runGit(projectRoot, ["rev-parse", "--path-format=absolute", "--git-dir"]),
|
|
927
|
+
runGit(projectRoot, [
|
|
928
|
+
"rev-parse",
|
|
929
|
+
"--path-format=absolute",
|
|
930
|
+
"--show-toplevel"
|
|
931
|
+
])
|
|
932
|
+
]);
|
|
933
|
+
if (commonDirRaw === null || gitDirRaw === null || worktreeRootRaw === null) {
|
|
934
|
+
return null;
|
|
935
|
+
}
|
|
936
|
+
const [commonDir, gitDir, worktreeRoot] = await Promise.all([
|
|
937
|
+
realpath(resolveGitPath(projectRoot, commonDirRaw.trim())),
|
|
938
|
+
realpath(resolveGitPath(projectRoot, gitDirRaw.trim())),
|
|
939
|
+
realpath(resolveGitPath(projectRoot, worktreeRootRaw.trim()))
|
|
940
|
+
]);
|
|
941
|
+
return { commonDir, gitDir, worktreeRoot };
|
|
942
|
+
}
|
|
943
|
+
async function ensureRepositoryRuntimeBinding(gitCommonDir, now) {
|
|
944
|
+
const target = repositoryRuntimeBindingPath(gitCommonDir);
|
|
945
|
+
const existing = await readRepositoryRuntimeBinding(gitCommonDir);
|
|
946
|
+
const commonDirHash = digestPath(gitCommonDir);
|
|
947
|
+
if (existing !== null) {
|
|
948
|
+
if (existing.commonDirHash !== commonDirHash) {
|
|
949
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
950
|
+
}
|
|
951
|
+
return existing;
|
|
952
|
+
}
|
|
953
|
+
const candidate = parseRepositoryRuntimeBinding({
|
|
954
|
+
schemaVersion: 1,
|
|
955
|
+
repositoryBindingId: createUlid(),
|
|
956
|
+
commonDirHash,
|
|
957
|
+
createdAt: now
|
|
958
|
+
});
|
|
959
|
+
return writeExclusiveOrRead(
|
|
960
|
+
target,
|
|
961
|
+
candidate,
|
|
962
|
+
parseRepositoryRuntimeBinding,
|
|
963
|
+
(stored, intended) => stored.commonDirHash === intended.commonDirHash,
|
|
964
|
+
"MANCODE_WORKSPACE_BINDING_MISMATCH"
|
|
965
|
+
);
|
|
966
|
+
}
|
|
967
|
+
async function ensureWorkspaceBinding(projectRoot, git, repositoryBindingId, config, now) {
|
|
968
|
+
const projectPathFromWorktreeRoot = relativeProjectPath(
|
|
969
|
+
await realpath(git.worktreeRoot),
|
|
970
|
+
await realpath(projectRoot)
|
|
971
|
+
);
|
|
972
|
+
const candidate = parseWorkspaceBinding({
|
|
973
|
+
schemaVersion: 1,
|
|
974
|
+
workspaceId: config.workspaceId,
|
|
975
|
+
repositoryBindingId,
|
|
976
|
+
projectPathFromWorktreeRoot,
|
|
977
|
+
configSchemaVersion: config.schemaVersion,
|
|
978
|
+
configIdentityDigest: projectConfigIdentityDigest(config),
|
|
979
|
+
registeredAt: now
|
|
980
|
+
});
|
|
981
|
+
const target = workspaceRuntimeBindingPath(git.commonDir, config.workspaceId);
|
|
982
|
+
const existing = await readWorkspaceBinding(
|
|
983
|
+
git.commonDir,
|
|
984
|
+
config.workspaceId
|
|
985
|
+
);
|
|
986
|
+
if (existing !== null) {
|
|
987
|
+
assertWorkspaceBindingCompatible(existing, candidate);
|
|
988
|
+
assertWorkspaceBindingMatchesConfig(existing, config);
|
|
989
|
+
return existing;
|
|
990
|
+
}
|
|
991
|
+
return writeExclusiveOrRead(
|
|
992
|
+
target,
|
|
993
|
+
candidate,
|
|
994
|
+
parseWorkspaceBinding,
|
|
995
|
+
(stored, intended) => {
|
|
996
|
+
try {
|
|
997
|
+
assertWorkspaceBindingCompatible(stored, intended);
|
|
998
|
+
return true;
|
|
999
|
+
} catch {
|
|
1000
|
+
return false;
|
|
1001
|
+
}
|
|
1002
|
+
},
|
|
1003
|
+
"MANCODE_WORKSPACE_BINDING_MISMATCH"
|
|
1004
|
+
);
|
|
1005
|
+
}
|
|
1006
|
+
async function ensureRuntimeCheckoutRecord(projectRoot, input) {
|
|
1007
|
+
const target = runtimeCheckoutRecordPath(projectRoot);
|
|
1008
|
+
const existing = await readRuntimeCheckoutRecord(projectRoot);
|
|
1009
|
+
if (existing !== null) {
|
|
1010
|
+
if (existing.workspaceId !== input.workspaceId || existing.repositoryBindingId !== input.repositoryBindingId) {
|
|
1011
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1012
|
+
}
|
|
1013
|
+
const updated = { ...existing, lastSeenAt: input.now };
|
|
1014
|
+
await writeAtomic(target, updated);
|
|
1015
|
+
return updated;
|
|
1016
|
+
}
|
|
1017
|
+
const candidate = parseRuntimeCheckoutRecord({
|
|
1018
|
+
schemaVersion: 1,
|
|
1019
|
+
workspaceId: input.workspaceId,
|
|
1020
|
+
checkoutId: createUlid(),
|
|
1021
|
+
repositoryBindingId: input.repositoryBindingId,
|
|
1022
|
+
registeredAt: input.now,
|
|
1023
|
+
lastSeenAt: input.now
|
|
1024
|
+
});
|
|
1025
|
+
return writeExclusiveOrRead(
|
|
1026
|
+
target,
|
|
1027
|
+
candidate,
|
|
1028
|
+
parseRuntimeCheckoutRecord,
|
|
1029
|
+
(stored, intended) => stored.workspaceId === intended.workspaceId && stored.repositoryBindingId === intended.repositoryBindingId,
|
|
1030
|
+
"MANCODE_WORKSPACE_BINDING_MISMATCH"
|
|
1031
|
+
);
|
|
1032
|
+
}
|
|
1033
|
+
async function ensureCheckoutBinding(projectRoot, git, workspace, checkout, now) {
|
|
1034
|
+
if (checkout.repositoryBindingId === null) {
|
|
1035
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1036
|
+
}
|
|
1037
|
+
const candidate = parseCheckoutBinding({
|
|
1038
|
+
schemaVersion: 1,
|
|
1039
|
+
workspaceId: workspace.workspaceId,
|
|
1040
|
+
repositoryBindingId: workspace.repositoryBindingId,
|
|
1041
|
+
checkoutId: checkout.checkoutId,
|
|
1042
|
+
worktreeGitDirHash: digestPath(git.gitDir),
|
|
1043
|
+
projectRealpathHash: digestPath(await realpath(projectRoot)),
|
|
1044
|
+
registeredAt: checkout.registeredAt,
|
|
1045
|
+
lastSeenAt: now
|
|
1046
|
+
});
|
|
1047
|
+
const target = runtimeCheckoutBindingPath(projectRoot);
|
|
1048
|
+
const existing = await readCheckoutBinding(projectRoot);
|
|
1049
|
+
if (existing !== null) {
|
|
1050
|
+
assertCheckoutBindingMatchesWorkspace(existing, workspace);
|
|
1051
|
+
if (existing.checkoutId !== candidate.checkoutId || existing.worktreeGitDirHash !== candidate.worktreeGitDirHash || existing.projectRealpathHash !== candidate.projectRealpathHash) {
|
|
1052
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1053
|
+
}
|
|
1054
|
+
const updated = { ...existing, lastSeenAt: now };
|
|
1055
|
+
await writeAtomic(target, updated);
|
|
1056
|
+
return updated;
|
|
1057
|
+
}
|
|
1058
|
+
return writeExclusiveOrRead(
|
|
1059
|
+
target,
|
|
1060
|
+
candidate,
|
|
1061
|
+
parseCheckoutBinding,
|
|
1062
|
+
(stored, intended) => stored.checkoutId === intended.checkoutId && stored.worktreeGitDirHash === intended.worktreeGitDirHash && stored.projectRealpathHash === intended.projectRealpathHash,
|
|
1063
|
+
"MANCODE_WORKSPACE_BINDING_MISMATCH"
|
|
1064
|
+
);
|
|
1065
|
+
}
|
|
1066
|
+
async function readRuntimeCheckoutRecord(projectRoot) {
|
|
1067
|
+
return readJsonOrNull(
|
|
1068
|
+
runtimeCheckoutRecordPath(projectRoot),
|
|
1069
|
+
parseRuntimeCheckoutRecord
|
|
1070
|
+
);
|
|
1071
|
+
}
|
|
1072
|
+
async function requireCheckoutBinding(projectRoot) {
|
|
1073
|
+
const binding = await readCheckoutBinding(projectRoot);
|
|
1074
|
+
if (binding === null) throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1075
|
+
return binding;
|
|
1076
|
+
}
|
|
1077
|
+
async function readCheckoutBinding(projectRoot) {
|
|
1078
|
+
return readJsonOrNull(
|
|
1079
|
+
runtimeCheckoutBindingPath(projectRoot),
|
|
1080
|
+
parseCheckoutBinding
|
|
1081
|
+
);
|
|
1082
|
+
}
|
|
1083
|
+
async function readRepositoryRuntimeBinding(gitCommonDir) {
|
|
1084
|
+
return readJsonOrNull(
|
|
1085
|
+
repositoryRuntimeBindingPath(gitCommonDir),
|
|
1086
|
+
parseRepositoryRuntimeBinding
|
|
1087
|
+
);
|
|
1088
|
+
}
|
|
1089
|
+
async function readWorkspaceBinding(gitCommonDir, workspaceId) {
|
|
1090
|
+
return readJsonOrNull(
|
|
1091
|
+
workspaceRuntimeBindingPath(gitCommonDir, workspaceId),
|
|
1092
|
+
parseWorkspaceBinding
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
function runtimeContext(projectRoot, workspaceId, checkoutId, repositoryBindingId, gitCommonDir) {
|
|
1096
|
+
const entityHomeStoreContext = {
|
|
1097
|
+
projectRoot,
|
|
1098
|
+
workspaceId,
|
|
1099
|
+
checkoutId,
|
|
1100
|
+
repositoryBindingId,
|
|
1101
|
+
gitCommonDir
|
|
1102
|
+
};
|
|
1103
|
+
resolveCoordinationEntityHomeStore(entityHomeStoreContext);
|
|
1104
|
+
return {
|
|
1105
|
+
projectRoot,
|
|
1106
|
+
workspaceId,
|
|
1107
|
+
checkoutId,
|
|
1108
|
+
repositoryBindingId,
|
|
1109
|
+
gitCommonDir,
|
|
1110
|
+
entityHomeStoreContext
|
|
1111
|
+
};
|
|
1112
|
+
}
|
|
1113
|
+
async function runGit(cwd, args) {
|
|
1114
|
+
try {
|
|
1115
|
+
const { stdout } = await execFile("git", args, {
|
|
1116
|
+
cwd,
|
|
1117
|
+
encoding: "utf8",
|
|
1118
|
+
timeout: 5e3,
|
|
1119
|
+
maxBuffer: 64 * 1024
|
|
1120
|
+
});
|
|
1121
|
+
return stdout;
|
|
1122
|
+
} catch {
|
|
1123
|
+
return null;
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
function resolveGitPath(projectRoot, value) {
|
|
1127
|
+
if (!value || value.includes("\0")) {
|
|
1128
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1129
|
+
}
|
|
1130
|
+
return path2.isAbsolute(value) ? path2.resolve(value) : path2.resolve(projectRoot, value);
|
|
1131
|
+
}
|
|
1132
|
+
function relativeProjectPath(worktreeRoot, projectRoot) {
|
|
1133
|
+
const relative = path2.relative(worktreeRoot, projectRoot);
|
|
1134
|
+
if (relative === ".." || relative.startsWith(`..${path2.sep}`) || path2.isAbsolute(relative)) {
|
|
1135
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_MISMATCH");
|
|
1136
|
+
}
|
|
1137
|
+
return relative === "." || relative === "" ? "." : relative.split(path2.sep).join("/");
|
|
1138
|
+
}
|
|
1139
|
+
function digestPath(value) {
|
|
1140
|
+
return `sha256:${createHash2("sha256").update(value, "utf8").digest("hex")}`;
|
|
1141
|
+
}
|
|
1142
|
+
async function writeExclusiveOrRead(target, value, parser, compatible, conflictCode) {
|
|
1143
|
+
await mkdir(path2.dirname(target), { recursive: true });
|
|
1144
|
+
try {
|
|
1145
|
+
await writeFile(target, serialize(value), { encoding: "utf8", flag: "wx" });
|
|
1146
|
+
return value;
|
|
1147
|
+
} catch (error) {
|
|
1148
|
+
if (!isAlreadyExists(error)) throw error;
|
|
1149
|
+
const existing = await readJsonOrNull(target, parser);
|
|
1150
|
+
if (existing !== null && compatible(existing, value)) return existing;
|
|
1151
|
+
throw new Error(conflictCode);
|
|
1152
|
+
}
|
|
1153
|
+
}
|
|
1154
|
+
async function writeAtomic(target, value) {
|
|
1155
|
+
await mkdir(path2.dirname(target), { recursive: true });
|
|
1156
|
+
const temporary = path2.join(
|
|
1157
|
+
path2.dirname(target),
|
|
1158
|
+
`.${path2.basename(target)}.${process.pid}.${createUlid()}.tmp`
|
|
1159
|
+
);
|
|
1160
|
+
await writeFile(temporary, serialize(value), {
|
|
1161
|
+
encoding: "utf8",
|
|
1162
|
+
flag: "wx"
|
|
1163
|
+
});
|
|
1164
|
+
await rename(temporary, target);
|
|
1165
|
+
}
|
|
1166
|
+
async function readJsonOrNull(target, parser) {
|
|
1167
|
+
try {
|
|
1168
|
+
return parser(JSON.parse(await readFile(target, "utf8")));
|
|
1169
|
+
} catch (error) {
|
|
1170
|
+
if (isNotFound(error)) return null;
|
|
1171
|
+
if (error instanceof SyntaxError) {
|
|
1172
|
+
throw new Error("MANCODE_WORKSPACE_BINDING_CORRUPT");
|
|
1173
|
+
}
|
|
1174
|
+
throw error;
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
function parseDigest2(value, label) {
|
|
1178
|
+
if (typeof value !== "string" || !DIGEST_PATTERN2.test(value)) {
|
|
1179
|
+
throw new Error(`${label} must be a sha256 digest`);
|
|
1180
|
+
}
|
|
1181
|
+
return value;
|
|
1182
|
+
}
|
|
1183
|
+
function parseTimestamp3(value, label) {
|
|
1184
|
+
if (typeof value !== "string" || Number.isNaN(Date.parse(value))) {
|
|
1185
|
+
throw new Error(`${label} must be an ISO timestamp`);
|
|
1186
|
+
}
|
|
1187
|
+
return value;
|
|
1188
|
+
}
|
|
1189
|
+
function serialize(value) {
|
|
1190
|
+
return `${JSON.stringify(value, null, 2)}
|
|
1191
|
+
`;
|
|
1192
|
+
}
|
|
1193
|
+
function isNotFound(error) {
|
|
1194
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
|
|
1195
|
+
}
|
|
1196
|
+
function isAlreadyExists(error) {
|
|
1197
|
+
return typeof error === "object" && error !== null && "code" in error && error.code === "EEXIST";
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1200
|
+
export {
|
|
1201
|
+
isUlid,
|
|
1202
|
+
assertUlid,
|
|
1203
|
+
createUlid,
|
|
1204
|
+
assertRecord,
|
|
1205
|
+
assertKnownKeys,
|
|
1206
|
+
formatTaskRef,
|
|
1207
|
+
parseTaskRef,
|
|
1208
|
+
parseTaskRefValue,
|
|
1209
|
+
sameTaskRef,
|
|
1210
|
+
resolveTaskEntityHomeStore,
|
|
1211
|
+
resolveLocalEntityHomeStore,
|
|
1212
|
+
resolveCoordinationEntityHomeStore,
|
|
1213
|
+
operationDirectory,
|
|
1214
|
+
reservationDirectory,
|
|
1215
|
+
lockDirectory,
|
|
1216
|
+
claimDirectory,
|
|
1217
|
+
handoffDirectory,
|
|
1218
|
+
taskHeadDirectory,
|
|
1219
|
+
canonicalizeJson,
|
|
1220
|
+
digestCanonicalJson,
|
|
1221
|
+
sortUtf8StringSet,
|
|
1222
|
+
parseProjectConfig,
|
|
1223
|
+
parseTeamPolicy,
|
|
1224
|
+
projectConfigDigest,
|
|
1225
|
+
assertConfigPolicyConsistency,
|
|
1226
|
+
assertProjectConfigTransition,
|
|
1227
|
+
assertTeamPolicyTransition,
|
|
1228
|
+
localCoordinationDomainId,
|
|
1229
|
+
gitRefCoordinationDomainId,
|
|
1230
|
+
ensureProjectRuntimeContext,
|
|
1231
|
+
readProjectRuntimeContext,
|
|
1232
|
+
readCheckoutCodeHead,
|
|
1233
|
+
readCheckoutBranch
|
|
1234
|
+
};
|
|
1235
|
+
//# sourceMappingURL=chunk-WRBNOPFA.js.map
|