dotmem 0.0.8 → 0.0.9
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/AGENTS.md +12 -0
- package/JOY.md +55 -0
- package/README.md +74 -291
- package/cli.cjs +180 -55
- package/cli.js +180 -55
- package/global.cjs +176 -55
- package/global.js +176 -55
- package/index.cjs +1867 -63
- package/index.js +1839 -56
- package/llms.txt +92 -8
- package/mcp.cjs +177 -56
- package/mcp.js +177 -56
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2410,6 +2410,7 @@ var init_policy_store = __esm({
|
|
|
2410
2410
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2411
2411
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2412
2412
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2413
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2413
2414
|
return built;
|
|
2414
2415
|
}, "validatePolicyAuditEntry");
|
|
2415
2416
|
validatePolicyDocument = /* @__PURE__ */ __name((value) => {
|
|
@@ -2487,6 +2488,7 @@ var init_policy_store = __esm({
|
|
|
2487
2488
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2488
2489
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2489
2490
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2491
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2490
2492
|
return built;
|
|
2491
2493
|
}, "buildAuditEntry");
|
|
2492
2494
|
boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
|
|
@@ -2499,9 +2501,10 @@ var init_policy_store = __esm({
|
|
|
2499
2501
|
for (let index = entries2.length - 1; index >= 0; index--) {
|
|
2500
2502
|
const entry = entries2[index];
|
|
2501
2503
|
const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
|
|
2502
|
-
|
|
2504
|
+
const mandatory = entry.mandatory === true;
|
|
2505
|
+
if (activeLinked || mandatory || budget > 0) {
|
|
2503
2506
|
kept.unshift(entry);
|
|
2504
|
-
if (!activeLinked) budget--;
|
|
2507
|
+
if (!activeLinked && !mandatory) budget--;
|
|
2505
2508
|
}
|
|
2506
2509
|
}
|
|
2507
2510
|
return kept;
|
|
@@ -2542,6 +2545,12 @@ var init_policy_store = __esm({
|
|
|
2542
2545
|
try {
|
|
2543
2546
|
await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
|
|
2544
2547
|
`, "utf8");
|
|
2548
|
+
const handle = await fs.open(temporary, "r+");
|
|
2549
|
+
try {
|
|
2550
|
+
await handle.sync();
|
|
2551
|
+
} finally {
|
|
2552
|
+
await handle.close();
|
|
2553
|
+
}
|
|
2545
2554
|
await fs.rename(temporary, filePath);
|
|
2546
2555
|
} catch (error) {
|
|
2547
2556
|
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
@@ -5036,6 +5045,109 @@ ${contentId}
|
|
|
5036
5045
|
}
|
|
5037
5046
|
});
|
|
5038
5047
|
|
|
5048
|
+
// core/storage-coordinator/index.ts
|
|
5049
|
+
var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
|
|
5050
|
+
var init_storage_coordinator = __esm({
|
|
5051
|
+
"core/storage-coordinator/index.ts"() {
|
|
5052
|
+
nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
5053
|
+
withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
|
|
5054
|
+
const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
|
|
5055
|
+
const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
|
|
5056
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
5057
|
+
const staleMs = opts.staleMs ?? 6e4;
|
|
5058
|
+
const lockName = opts.lockName ?? ".storage.lock";
|
|
5059
|
+
const lockPath = `${directoryPath}/${lockName}`;
|
|
5060
|
+
const fs = await nodeFs4();
|
|
5061
|
+
const started = Date.now();
|
|
5062
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
5063
|
+
while (true) {
|
|
5064
|
+
try {
|
|
5065
|
+
await fs.mkdir(lockPath);
|
|
5066
|
+
const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
5067
|
+
await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
|
|
5068
|
+
break;
|
|
5069
|
+
} catch (error) {
|
|
5070
|
+
const code = error.code;
|
|
5071
|
+
if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
|
|
5072
|
+
let stale = false;
|
|
5073
|
+
let processDead = false;
|
|
5074
|
+
try {
|
|
5075
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5076
|
+
const parsed = JSON.parse(raw);
|
|
5077
|
+
const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
|
|
5078
|
+
if (typeof parsed.pid === "number") {
|
|
5079
|
+
try {
|
|
5080
|
+
process.kill(parsed.pid, 0);
|
|
5081
|
+
} catch {
|
|
5082
|
+
processDead = true;
|
|
5083
|
+
}
|
|
5084
|
+
} else {
|
|
5085
|
+
processDead = true;
|
|
5086
|
+
}
|
|
5087
|
+
if (ageStale || processDead) stale = true;
|
|
5088
|
+
} catch {
|
|
5089
|
+
stale = true;
|
|
5090
|
+
}
|
|
5091
|
+
if (stale) {
|
|
5092
|
+
if (processDead) {
|
|
5093
|
+
try {
|
|
5094
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
5095
|
+
continue;
|
|
5096
|
+
} catch {
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
try {
|
|
5100
|
+
await fs.rmdir(lockPath);
|
|
5101
|
+
continue;
|
|
5102
|
+
} catch {
|
|
5103
|
+
}
|
|
5104
|
+
}
|
|
5105
|
+
if (Date.now() - started > timeoutMs) {
|
|
5106
|
+
throw new Error(`Storage lock timeout for ${directoryPath}`);
|
|
5107
|
+
}
|
|
5108
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
5109
|
+
}
|
|
5110
|
+
}
|
|
5111
|
+
let token = "";
|
|
5112
|
+
try {
|
|
5113
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5114
|
+
token = JSON.parse(raw).token ?? "";
|
|
5115
|
+
} catch {
|
|
5116
|
+
token = "";
|
|
5117
|
+
}
|
|
5118
|
+
try {
|
|
5119
|
+
return await op();
|
|
5120
|
+
} finally {
|
|
5121
|
+
try {
|
|
5122
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5123
|
+
const parsed = JSON.parse(raw);
|
|
5124
|
+
if (parsed.token === token && token) {
|
|
5125
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
5126
|
+
}
|
|
5127
|
+
} catch {
|
|
5128
|
+
}
|
|
5129
|
+
}
|
|
5130
|
+
}, "withStorageLock");
|
|
5131
|
+
readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
|
|
5132
|
+
const fs = await nodeFs4();
|
|
5133
|
+
try {
|
|
5134
|
+
const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
|
|
5135
|
+
const value = Number(raw.trim());
|
|
5136
|
+
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
5137
|
+
} catch {
|
|
5138
|
+
return 0;
|
|
5139
|
+
}
|
|
5140
|
+
}, "readStorageGeneration");
|
|
5141
|
+
writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
|
|
5142
|
+
const fs = await nodeFs4();
|
|
5143
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
5144
|
+
const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
5145
|
+
await fs.writeFile(temp, String(value));
|
|
5146
|
+
await fs.rename(temp, `${directoryPath}/.storage.generation`);
|
|
5147
|
+
}, "writeStorageGeneration");
|
|
5148
|
+
}
|
|
5149
|
+
});
|
|
5150
|
+
|
|
5039
5151
|
// core/knowledge/mem-repository.ts
|
|
5040
5152
|
function resolveDuplicate(existing, existingSource, current, currentSource) {
|
|
5041
5153
|
const existingRetracts = existing.revision?.kind === "retracts";
|
|
@@ -5076,19 +5188,20 @@ async function parseMemFile(value, sourceId) {
|
|
|
5076
5188
|
throw new Error(`${message} in ${sourceId}`);
|
|
5077
5189
|
}
|
|
5078
5190
|
}
|
|
5079
|
-
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE,
|
|
5191
|
+
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
|
|
5080
5192
|
var init_mem_repository = __esm({
|
|
5081
5193
|
"core/knowledge/mem-repository.ts"() {
|
|
5082
5194
|
init_mem_codec();
|
|
5083
5195
|
init_mem_package();
|
|
5084
5196
|
init_state();
|
|
5085
5197
|
init_acquisition_limits();
|
|
5198
|
+
init_storage_coordinator();
|
|
5086
5199
|
DOTMEM_DIRECTORY = ".dotmem";
|
|
5087
5200
|
DEFAULT_MEM_FILE = "project.mem";
|
|
5088
|
-
|
|
5089
|
-
nodeFs4 = /* @__PURE__ */ __name(async () => {
|
|
5201
|
+
nodeFs5 = /* @__PURE__ */ __name(async () => {
|
|
5090
5202
|
return import('fs/promises');
|
|
5091
5203
|
}, "nodeFs");
|
|
5204
|
+
repositoryLoadCaches = /* @__PURE__ */ new Map();
|
|
5092
5205
|
separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
|
|
5093
5206
|
normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
|
|
5094
5207
|
isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
|
|
@@ -5100,25 +5213,8 @@ var init_mem_repository = __esm({
|
|
|
5100
5213
|
return index <= 0 ? "." : normalized.slice(0, index);
|
|
5101
5214
|
}, "parentPath");
|
|
5102
5215
|
uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
|
|
5103
|
-
withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
|
|
5104
|
-
const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
|
|
5105
|
-
let release = /* @__PURE__ */ __name(() => void 0, "release");
|
|
5106
|
-
const gate = new Promise((resolveGate) => {
|
|
5107
|
-
release = resolveGate;
|
|
5108
|
-
});
|
|
5109
|
-
const queued = previous.then(() => gate);
|
|
5110
|
-
directoryLocks.set(directoryPath, queued);
|
|
5111
|
-
await previous;
|
|
5112
|
-
try {
|
|
5113
|
-
return await operation();
|
|
5114
|
-
} finally {
|
|
5115
|
-
release();
|
|
5116
|
-
if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
|
|
5117
|
-
}
|
|
5118
|
-
}, "withDirectoryLock");
|
|
5119
5216
|
isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
|
|
5120
5217
|
sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
|
|
5121
|
-
if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
|
|
5122
5218
|
if (scope === "project" || sourceId === "project.mem") return 4;
|
|
5123
5219
|
if (scope === "user" || sourceId === "user.mem") return 3;
|
|
5124
5220
|
if (sourceId === "agent.mem" || sourceId === "agent") return 2;
|
|
@@ -5146,7 +5242,7 @@ var init_mem_repository = __esm({
|
|
|
5146
5242
|
this.loadWritableOnly = loadWritableOnly;
|
|
5147
5243
|
}
|
|
5148
5244
|
async discover() {
|
|
5149
|
-
const { readdir } = await
|
|
5245
|
+
const { readdir } = await nodeFs5();
|
|
5150
5246
|
try {
|
|
5151
5247
|
const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
|
|
5152
5248
|
return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
@@ -5161,7 +5257,7 @@ var init_mem_repository = __esm({
|
|
|
5161
5257
|
* receive a clear failure instead of discovering corruption on first use.
|
|
5162
5258
|
*/
|
|
5163
5259
|
async initialize() {
|
|
5164
|
-
const { mkdir } = await
|
|
5260
|
+
const { mkdir } = await nodeFs5();
|
|
5165
5261
|
await mkdir(this.directoryPath, { recursive: true });
|
|
5166
5262
|
await this.load();
|
|
5167
5263
|
}
|
|
@@ -5176,7 +5272,7 @@ var init_mem_repository = __esm({
|
|
|
5176
5272
|
* that does not delete is not a delete.
|
|
5177
5273
|
*/
|
|
5178
5274
|
async clearWritable() {
|
|
5179
|
-
const { rm, readdir } = await
|
|
5275
|
+
const { rm, readdir } = await nodeFs5();
|
|
5180
5276
|
const filePath = memFilePath(this.directoryPath, this.writableSourceId);
|
|
5181
5277
|
await rm(filePath, { force: true });
|
|
5182
5278
|
await rm(`${filePath}.bak`, { force: true });
|
|
@@ -5193,15 +5289,28 @@ var init_mem_repository = __esm({
|
|
|
5193
5289
|
}
|
|
5194
5290
|
}
|
|
5195
5291
|
async load() {
|
|
5196
|
-
const
|
|
5197
|
-
|
|
5292
|
+
const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
|
|
5293
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
5294
|
+
const generationBefore = await readStorageGeneration(this.directoryPath);
|
|
5295
|
+
const cached = repositoryLoadCaches.get(cacheKey);
|
|
5296
|
+
if (cached && cached.signature === String(generationBefore)) {
|
|
5297
|
+
return structuredClone(cached.state);
|
|
5298
|
+
}
|
|
5299
|
+
const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
|
|
5300
|
+
const state3 = this.combine(loaded);
|
|
5301
|
+
const generationAfter = await readStorageGeneration(this.directoryPath);
|
|
5302
|
+
if (generationBefore !== generationAfter && attempt < 4) continue;
|
|
5303
|
+
repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state3) });
|
|
5304
|
+
return state3;
|
|
5305
|
+
}
|
|
5306
|
+
throw new Error("Unable to load stable repository state");
|
|
5198
5307
|
}
|
|
5199
|
-
async save(state3, expectedVersion) {
|
|
5308
|
+
async save(state3, expectedVersion, locked = false) {
|
|
5200
5309
|
const validated = validateKnowledgeState(state3);
|
|
5201
5310
|
if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
|
|
5202
5311
|
throw new Error("Invalid expected persisted version");
|
|
5203
5312
|
}
|
|
5204
|
-
|
|
5313
|
+
const doWrite = /* @__PURE__ */ __name(async () => {
|
|
5205
5314
|
const loaded = await this.loadSources();
|
|
5206
5315
|
const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
|
|
5207
5316
|
id: this.writableSourceId,
|
|
@@ -5223,10 +5332,16 @@ var init_mem_repository = __esm({
|
|
|
5223
5332
|
claims: [...writable.state.claims, ...newClaims]
|
|
5224
5333
|
};
|
|
5225
5334
|
await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
|
|
5226
|
-
|
|
5335
|
+
await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
|
|
5336
|
+
}, "doWrite");
|
|
5337
|
+
if (locked) {
|
|
5338
|
+
await doWrite();
|
|
5339
|
+
} else {
|
|
5340
|
+
await withStorageLock(this.directoryPath, doWrite);
|
|
5341
|
+
}
|
|
5227
5342
|
}
|
|
5228
5343
|
async loadSources(sourceIds) {
|
|
5229
|
-
const { readFile: readFile5 } = await
|
|
5344
|
+
const { readFile: readFile5 } = await nodeFs5();
|
|
5230
5345
|
const ids = sourceIds ?? await this.discover();
|
|
5231
5346
|
const sources = [];
|
|
5232
5347
|
for (const sourceId of ids) {
|
|
@@ -5316,7 +5431,7 @@ var init_mem_repository = __esm({
|
|
|
5316
5431
|
* valid package, so the backup is always a version this library can read.
|
|
5317
5432
|
*/
|
|
5318
5433
|
async keepLastValidVersion(filePath) {
|
|
5319
|
-
const { readFile: readFile5, copyFile, rename, rm } = await
|
|
5434
|
+
const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
|
|
5320
5435
|
let current;
|
|
5321
5436
|
try {
|
|
5322
5437
|
current = await readFile5(filePath);
|
|
@@ -5340,7 +5455,7 @@ var init_mem_repository = __esm({
|
|
|
5340
5455
|
}
|
|
5341
5456
|
/** Best-effort directory sync: the rename is durable once the directory is. */
|
|
5342
5457
|
async syncDirectory(directoryPath) {
|
|
5343
|
-
const { open } = await
|
|
5458
|
+
const { open } = await nodeFs5();
|
|
5344
5459
|
try {
|
|
5345
5460
|
const handle = await open(directoryPath, "r");
|
|
5346
5461
|
try {
|
|
@@ -5352,7 +5467,7 @@ var init_mem_repository = __esm({
|
|
|
5352
5467
|
}
|
|
5353
5468
|
}
|
|
5354
5469
|
async writeSource(filePath, state3, preserved, preservedPackage) {
|
|
5355
|
-
const { mkdir, rename, rm, open, readFile: readFile5 } = await
|
|
5470
|
+
const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
|
|
5356
5471
|
if (!Number.isInteger(state3.version) || state3.version < 0) throw new Error("Invalid writable .mem knowledge version");
|
|
5357
5472
|
for (const claim of state3.claims) validateClaim(claim);
|
|
5358
5473
|
await mkdir(parentPath(filePath), { recursive: true });
|
|
@@ -5880,7 +5995,7 @@ var init_project_identity = __esm({
|
|
|
5880
5995
|
});
|
|
5881
5996
|
|
|
5882
5997
|
// core/knowledge/staging.ts
|
|
5883
|
-
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor,
|
|
5998
|
+
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
|
|
5884
5999
|
var init_staging = __esm({
|
|
5885
6000
|
"core/knowledge/staging.ts"() {
|
|
5886
6001
|
init_claim_record();
|
|
@@ -5915,7 +6030,7 @@ var init_staging = __esm({
|
|
|
5915
6030
|
return `${base}/dotmem`;
|
|
5916
6031
|
}, "resolveStagingDirectory");
|
|
5917
6032
|
stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
|
|
5918
|
-
|
|
6033
|
+
nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
5919
6034
|
normalizeClaimIds = /* @__PURE__ */ __name((value) => {
|
|
5920
6035
|
if (!value) return [];
|
|
5921
6036
|
return Array.isArray(value) ? value : [value];
|
|
@@ -5940,7 +6055,7 @@ var init_staging = __esm({
|
|
|
5940
6055
|
};
|
|
5941
6056
|
const requestId = generateRequestId();
|
|
5942
6057
|
const stagePath = stagePathFor(requestId);
|
|
5943
|
-
const fs = await
|
|
6058
|
+
const fs = await nodeFs6();
|
|
5944
6059
|
await fs.mkdir(resolveStagingDirectory(), { recursive: true });
|
|
5945
6060
|
const stageFile = {
|
|
5946
6061
|
format: DOTMEM_STAGE_FORMAT,
|
|
@@ -5960,7 +6075,7 @@ var init_staging = __esm({
|
|
|
5960
6075
|
}, "writeStageFile");
|
|
5961
6076
|
readStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
5962
6077
|
const stagePath = stagePathFor(requestId);
|
|
5963
|
-
const fs = await
|
|
6078
|
+
const fs = await nodeFs6();
|
|
5964
6079
|
try {
|
|
5965
6080
|
const content = await fs.readFile(stagePath, "utf8");
|
|
5966
6081
|
return JSON.parse(content);
|
|
@@ -6071,7 +6186,7 @@ var init_staging = __esm({
|
|
|
6071
6186
|
}, "commitStageFile");
|
|
6072
6187
|
cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
6073
6188
|
const stagePath = stagePathFor(requestId);
|
|
6074
|
-
const fs = await
|
|
6189
|
+
const fs = await nodeFs6();
|
|
6075
6190
|
try {
|
|
6076
6191
|
await fs.unlink(stagePath);
|
|
6077
6192
|
} catch (error) {
|
|
@@ -8552,10 +8667,12 @@ init_project_identity();
|
|
|
8552
8667
|
init_project_anchor();
|
|
8553
8668
|
init_acquisition_boundary();
|
|
8554
8669
|
init_claim_record();
|
|
8670
|
+
init_state();
|
|
8555
8671
|
init_router();
|
|
8556
8672
|
init_store_queue();
|
|
8557
8673
|
init_staging();
|
|
8558
8674
|
init_user_paths();
|
|
8675
|
+
init_storage_coordinator();
|
|
8559
8676
|
init_mem_repository();
|
|
8560
8677
|
function normalizeClaimIds3(value) {
|
|
8561
8678
|
if (!value) return [];
|
|
@@ -8633,23 +8750,27 @@ var acquire = {
|
|
|
8633
8750
|
);
|
|
8634
8751
|
}
|
|
8635
8752
|
const revision = revisionFromAcquire2(opts);
|
|
8753
|
+
const repository = target.repository;
|
|
8636
8754
|
await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
|
|
8637
|
-
|
|
8638
|
-
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8755
|
+
await withStorageLock(repository.directoryPath, async () => {
|
|
8756
|
+
const state3 = await repository.load();
|
|
8757
|
+
const next = updateKnowledge(state3, {
|
|
8758
|
+
basedOnVersion: state3.version,
|
|
8759
|
+
claims: [buildClaimRecord({
|
|
8760
|
+
id: opts.id,
|
|
8761
|
+
knowledge: boundary.knowledge,
|
|
8762
|
+
applicability: opts.applicability,
|
|
8763
|
+
evidence: opts.evidence,
|
|
8764
|
+
epistemicType: opts.epistemicType,
|
|
8765
|
+
provenance: opts.provenance,
|
|
8766
|
+
acquiredAt: opts.acquiredAt,
|
|
8767
|
+
revision,
|
|
8768
|
+
scope,
|
|
8769
|
+
sourceProject,
|
|
8770
|
+
kind
|
|
8771
|
+
})]
|
|
8772
|
+
});
|
|
8773
|
+
await repository.save(next, state3.version, true);
|
|
8653
8774
|
});
|
|
8654
8775
|
});
|
|
8655
8776
|
const verification = await target.repository.load();
|
|
@@ -10190,6 +10311,442 @@ var saveContinuityDocument = /* @__PURE__ */ __name(async (path, document) => {
|
|
|
10190
10311
|
}
|
|
10191
10312
|
}, "saveContinuityDocument");
|
|
10192
10313
|
|
|
10314
|
+
// core/continuity-ledger/index.ts
|
|
10315
|
+
var EVENT_ID_PREFIX = "evt.";
|
|
10316
|
+
var LEDGER_APPLICABILITY = { area: "continuity", baseline: false };
|
|
10317
|
+
var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
10318
|
+
"decision",
|
|
10319
|
+
"discovery",
|
|
10320
|
+
"correction",
|
|
10321
|
+
"conversation",
|
|
10322
|
+
"commitment",
|
|
10323
|
+
"open-loop",
|
|
10324
|
+
"project-change",
|
|
10325
|
+
"activity"
|
|
10326
|
+
]);
|
|
10327
|
+
var MEMORY_DOMAINS = /* @__PURE__ */ new Set(["environmental", "professional", "conversational", "activity"]);
|
|
10328
|
+
var EPITEMIC_STATUSES = /* @__PURE__ */ new Set([
|
|
10329
|
+
"verified-fact",
|
|
10330
|
+
"user-statement",
|
|
10331
|
+
"ai-inference",
|
|
10332
|
+
"hypothesis",
|
|
10333
|
+
"proposal",
|
|
10334
|
+
"decision",
|
|
10335
|
+
"verified-result"
|
|
10336
|
+
]);
|
|
10337
|
+
var CONFIDENCE_VALUES = /* @__PURE__ */ new Set(["high", "medium", "low", "unknown"]);
|
|
10338
|
+
var AUTHORS = /* @__PURE__ */ new Set(["human", "ai", "system"]);
|
|
10339
|
+
var CHANNELS = /* @__PURE__ */ new Set(["conversation", "acquisition", "tool", "migration"]);
|
|
10340
|
+
var SOURCE_KINDS = /* @__PURE__ */ new Set(["claim", "file", "commit", "tool-output", "conversation"]);
|
|
10341
|
+
var RELATIONSHIP_KEYS = [
|
|
10342
|
+
"refines",
|
|
10343
|
+
"supersedes",
|
|
10344
|
+
"retracts",
|
|
10345
|
+
"dependsOn",
|
|
10346
|
+
"resolves",
|
|
10347
|
+
"relatedTo"
|
|
10348
|
+
];
|
|
10349
|
+
var isRecord = /* @__PURE__ */ __name((value) => !!value && typeof value === "object" && !Array.isArray(value), "isRecord");
|
|
10350
|
+
var normalizeRelationships = /* @__PURE__ */ __name((value) => {
|
|
10351
|
+
const result = {
|
|
10352
|
+
refines: [],
|
|
10353
|
+
supersedes: [],
|
|
10354
|
+
retracts: [],
|
|
10355
|
+
dependsOn: [],
|
|
10356
|
+
resolves: [],
|
|
10357
|
+
relatedTo: []
|
|
10358
|
+
};
|
|
10359
|
+
if (!isRecord(value)) return result;
|
|
10360
|
+
for (const key of RELATIONSHIP_KEYS) {
|
|
10361
|
+
const raw = value[key];
|
|
10362
|
+
if (Array.isArray(raw)) {
|
|
10363
|
+
result[key] = raw.filter((item) => typeof item === "string");
|
|
10364
|
+
}
|
|
10365
|
+
}
|
|
10366
|
+
return result;
|
|
10367
|
+
}, "normalizeRelationships");
|
|
10368
|
+
var validateEvent = /* @__PURE__ */ __name((event) => {
|
|
10369
|
+
if (!isRecord(event)) throw new Error("Continuity event must be an object");
|
|
10370
|
+
if (event.schemaVersion !== 1) throw new Error("Unsupported continuity event schemaVersion");
|
|
10371
|
+
if (typeof event.eventId !== "string" || !event.eventId.startsWith(EVENT_ID_PREFIX)) {
|
|
10372
|
+
throw new Error("Continuity event requires a valid eventId");
|
|
10373
|
+
}
|
|
10374
|
+
if (typeof event.eventType !== "string" || !EVENT_TYPES.has(event.eventType)) {
|
|
10375
|
+
throw new Error("Continuity event requires a valid eventType");
|
|
10376
|
+
}
|
|
10377
|
+
if (typeof event.memoryDomain !== "string" || !MEMORY_DOMAINS.has(event.memoryDomain)) {
|
|
10378
|
+
throw new Error("Continuity event requires a valid memoryDomain");
|
|
10379
|
+
}
|
|
10380
|
+
if (typeof event.epistemicStatus !== "string" || !EPITEMIC_STATUSES.has(event.epistemicStatus)) {
|
|
10381
|
+
throw new Error("Continuity event requires a valid epistemicStatus");
|
|
10382
|
+
}
|
|
10383
|
+
if (typeof event.confidence !== "string" || !CONFIDENCE_VALUES.has(event.confidence)) {
|
|
10384
|
+
throw new Error("Continuity event requires a valid confidence");
|
|
10385
|
+
}
|
|
10386
|
+
if (typeof event.createdAt !== "string") throw new Error("Continuity event requires createdAt");
|
|
10387
|
+
if (typeof event.sessionId !== "string") throw new Error("Continuity event requires sessionId");
|
|
10388
|
+
if (event.projectId !== null && typeof event.projectId !== "string") {
|
|
10389
|
+
throw new Error("Continuity event requires projectId to be string or null");
|
|
10390
|
+
}
|
|
10391
|
+
if (typeof event.subject !== "string") throw new Error("Continuity event requires subject");
|
|
10392
|
+
if (!isRecord(event.content)) throw new Error("Continuity event requires content object");
|
|
10393
|
+
if (!Array.isArray(event.sourceRefs)) throw new Error("Continuity event requires sourceRefs array");
|
|
10394
|
+
for (const ref of event.sourceRefs) {
|
|
10395
|
+
if (!isRecord(ref) || typeof ref.kind !== "string" || !SOURCE_KINDS.has(ref.kind) || typeof ref.id !== "string") {
|
|
10396
|
+
throw new Error("Continuity event contains an invalid sourceRef");
|
|
10397
|
+
}
|
|
10398
|
+
}
|
|
10399
|
+
if (!isRecord(event.relationships)) throw new Error("Continuity event requires relationships object");
|
|
10400
|
+
if (!isRecord(event.provenance)) throw new Error("Continuity event requires provenance object");
|
|
10401
|
+
const provenance = event.provenance;
|
|
10402
|
+
if (typeof provenance.author !== "string" || !AUTHORS.has(provenance.author)) {
|
|
10403
|
+
throw new Error("Continuity event requires a valid provenance.author");
|
|
10404
|
+
}
|
|
10405
|
+
if (typeof provenance.origin !== "string") throw new Error("Continuity event requires provenance.origin");
|
|
10406
|
+
if (typeof provenance.channel !== "string" || !CHANNELS.has(provenance.channel)) {
|
|
10407
|
+
throw new Error("Continuity event requires a valid provenance.channel");
|
|
10408
|
+
}
|
|
10409
|
+
if (typeof provenance.recordedAt !== "string") throw new Error("Continuity event requires provenance.recordedAt");
|
|
10410
|
+
return {
|
|
10411
|
+
schemaVersion: 1,
|
|
10412
|
+
eventId: event.eventId,
|
|
10413
|
+
eventType: event.eventType,
|
|
10414
|
+
memoryDomain: event.memoryDomain,
|
|
10415
|
+
epistemicStatus: event.epistemicStatus,
|
|
10416
|
+
confidence: event.confidence,
|
|
10417
|
+
createdAt: event.createdAt,
|
|
10418
|
+
sessionId: event.sessionId,
|
|
10419
|
+
projectId: event.projectId,
|
|
10420
|
+
subject: event.subject,
|
|
10421
|
+
content: event.content,
|
|
10422
|
+
sourceRefs: event.sourceRefs,
|
|
10423
|
+
relationships: normalizeRelationships(event.relationships),
|
|
10424
|
+
provenance
|
|
10425
|
+
};
|
|
10426
|
+
}, "validateEvent");
|
|
10427
|
+
var isLedgerEvent = /* @__PURE__ */ __name((claim) => {
|
|
10428
|
+
return isRecord(claim.knowledge) && claim.knowledge.schemaVersion === 1 && typeof claim.knowledge.eventId === "string";
|
|
10429
|
+
}, "isLedgerEvent");
|
|
10430
|
+
var loadEvents = /* @__PURE__ */ __name(async () => {
|
|
10431
|
+
const state3 = await loadCombinedKnowledgeState();
|
|
10432
|
+
const events = [];
|
|
10433
|
+
for (const claim of state3.claims) {
|
|
10434
|
+
if (isLedgerEvent(claim)) {
|
|
10435
|
+
try {
|
|
10436
|
+
events.push(validateEvent(claim.knowledge));
|
|
10437
|
+
} catch {
|
|
10438
|
+
}
|
|
10439
|
+
}
|
|
10440
|
+
}
|
|
10441
|
+
return events.sort((left, right) => left.eventId.localeCompare(right.eventId));
|
|
10442
|
+
}, "loadEvents");
|
|
10443
|
+
var findEvent = /* @__PURE__ */ __name((events, eventId) => events.find((event) => event.eventId === eventId), "findEvent");
|
|
10444
|
+
var epistemicTypeFor = /* @__PURE__ */ __name((status) => {
|
|
10445
|
+
if (status === "verified-fact" || status === "verified-result" || status === "decision") return "verified";
|
|
10446
|
+
if (status === "user-statement") return "human-stated";
|
|
10447
|
+
return "inferred";
|
|
10448
|
+
}, "epistemicTypeFor");
|
|
10449
|
+
var EVENT_ID_PATTERN = /^evt\.[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
10450
|
+
var validateEventId = /* @__PURE__ */ __name((eventId) => {
|
|
10451
|
+
if (!EVENT_ID_PATTERN.test(eventId)) throw new Error(`Invalid continuity event ID: ${eventId}`);
|
|
10452
|
+
}, "validateEventId");
|
|
10453
|
+
var hasRelationshipPath = /* @__PURE__ */ __name((events, startId, targetId, relationKey) => {
|
|
10454
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10455
|
+
const stack = [startId];
|
|
10456
|
+
while (stack.length > 0) {
|
|
10457
|
+
const current = stack.pop();
|
|
10458
|
+
if (current === targetId) return true;
|
|
10459
|
+
if (visited.has(current)) continue;
|
|
10460
|
+
visited.add(current);
|
|
10461
|
+
const event = findEvent(events, current);
|
|
10462
|
+
if (!event) continue;
|
|
10463
|
+
for (const next of event.relationships[relationKey]) {
|
|
10464
|
+
if (!visited.has(next)) stack.push(next);
|
|
10465
|
+
}
|
|
10466
|
+
}
|
|
10467
|
+
return false;
|
|
10468
|
+
}, "hasRelationshipPath");
|
|
10469
|
+
var CYCLIC_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
10470
|
+
"refines",
|
|
10471
|
+
"supersedes",
|
|
10472
|
+
"retracts",
|
|
10473
|
+
"dependsOn",
|
|
10474
|
+
"resolves",
|
|
10475
|
+
"relatedTo"
|
|
10476
|
+
]);
|
|
10477
|
+
var validateRelationshipTargets = /* @__PURE__ */ __name(async (event, existing) => {
|
|
10478
|
+
validateEventId(event.eventId);
|
|
10479
|
+
for (const key of RELATIONSHIP_KEYS) {
|
|
10480
|
+
for (const targetId of event.relationships[key]) {
|
|
10481
|
+
validateEventId(targetId);
|
|
10482
|
+
if (targetId === event.eventId) throw new Error(`Continuity event cannot reference itself: ${targetId}`);
|
|
10483
|
+
const target = findEvent(existing, targetId);
|
|
10484
|
+
if (!target) throw new Error(`Continuity relationship references a nonexistent event: ${targetId}`);
|
|
10485
|
+
if (target.projectId !== event.projectId) {
|
|
10486
|
+
throw new Error(`Cross-scope continuity relationship is not allowed: ${event.eventId} -> ${targetId}`);
|
|
10487
|
+
}
|
|
10488
|
+
if (CYCLIC_RELATIONSHIP_KEYS.has(key) && hasRelationshipPath(existing, targetId, event.eventId, key)) {
|
|
10489
|
+
throw new Error(`Continuity relationship would create a cycle: ${event.eventId} -> ${targetId}`);
|
|
10490
|
+
}
|
|
10491
|
+
}
|
|
10492
|
+
}
|
|
10493
|
+
}, "validateRelationshipTargets");
|
|
10494
|
+
var validateResolutionRequest = /* @__PURE__ */ __name(async (resolution, existing) => {
|
|
10495
|
+
validateEventId(resolution.resolutionEventId);
|
|
10496
|
+
if (!Array.isArray(resolution.conflictingEventIds) || resolution.conflictingEventIds.length === 0) {
|
|
10497
|
+
throw new Error("Conflict resolution requires at least one conflicting event");
|
|
10498
|
+
}
|
|
10499
|
+
if (typeof resolution.selectedEventId !== "string" || !resolution.selectedEventId) {
|
|
10500
|
+
throw new Error("Conflict resolution requires a selected event");
|
|
10501
|
+
}
|
|
10502
|
+
if (typeof resolution.reason !== "string" || !resolution.reason.trim()) {
|
|
10503
|
+
throw new Error("Conflict resolution requires a reason");
|
|
10504
|
+
}
|
|
10505
|
+
const conflicting = resolution.conflictingEventIds.map((id) => {
|
|
10506
|
+
validateEventId(id);
|
|
10507
|
+
const event = findEvent(existing, id);
|
|
10508
|
+
if (!event) throw new Error(`Conflict resolution references a nonexistent event: ${id}`);
|
|
10509
|
+
if (event.relationships.supersedes.length !== 1) {
|
|
10510
|
+
throw new Error(`Conflict resolution references a non-supersession event: ${id}`);
|
|
10511
|
+
}
|
|
10512
|
+
return event;
|
|
10513
|
+
});
|
|
10514
|
+
const targets = new Set(conflicting.map((event) => event.relationships.supersedes[0]));
|
|
10515
|
+
if (targets.size !== 1) {
|
|
10516
|
+
throw new Error("Conflict resolution references supersession events targeting different decisions");
|
|
10517
|
+
}
|
|
10518
|
+
const targetId = [...targets][0];
|
|
10519
|
+
const knownCompeting = supersedingEvents(existing, targetId).map((event) => event.eventId).sort();
|
|
10520
|
+
const requested = [...resolution.conflictingEventIds].sort();
|
|
10521
|
+
if (requested.length !== knownCompeting.length || requested.some((id, index) => id !== knownCompeting[index])) {
|
|
10522
|
+
throw new Error("Conflict resolution must include every competing supersession event");
|
|
10523
|
+
}
|
|
10524
|
+
if (!resolution.conflictingEventIds.includes(resolution.selectedEventId)) {
|
|
10525
|
+
throw new Error("Conflict resolution selectedEventId must be one of the conflicting events");
|
|
10526
|
+
}
|
|
10527
|
+
const existingResolutions = existing.filter(
|
|
10528
|
+
(event) => event.relationships.resolves.includes(targetId) || resolution.conflictingEventIds.some((id) => event.relationships.resolves.includes(id))
|
|
10529
|
+
);
|
|
10530
|
+
if (existingResolutions.length > 0) {
|
|
10531
|
+
const selectedOutcomes = new Set(existingResolutions.map((event) => {
|
|
10532
|
+
const content = isRecord(event.content) ? event.content : {};
|
|
10533
|
+
return typeof content.selectedEventId === "string" ? content.selectedEventId : null;
|
|
10534
|
+
}).filter((value) => value !== null));
|
|
10535
|
+
if (existingResolutions.some((event) => event.eventId === resolution.resolutionEventId)) {
|
|
10536
|
+
throw new Error(`Duplicate continuity resolution event: ${resolution.resolutionEventId}`);
|
|
10537
|
+
}
|
|
10538
|
+
if (selectedOutcomes.size > 0 && !selectedOutcomes.has(resolution.selectedEventId)) {
|
|
10539
|
+
throw new Error("Incompatible continuity conflict resolution");
|
|
10540
|
+
}
|
|
10541
|
+
throw new Error("Continuity conflict is already resolved");
|
|
10542
|
+
}
|
|
10543
|
+
const resolutionEventId = resolution.resolutionEventId;
|
|
10544
|
+
for (const conflictingId of resolution.conflictingEventIds) {
|
|
10545
|
+
const event = findEvent(existing, conflictingId);
|
|
10546
|
+
if (event && hasRelationshipPath(existing, conflictingId, resolutionEventId, "resolves")) {
|
|
10547
|
+
throw new Error(`Continuity resolution would create a cycle: ${resolutionEventId}`);
|
|
10548
|
+
}
|
|
10549
|
+
}
|
|
10550
|
+
}, "validateResolutionRequest");
|
|
10551
|
+
var recordEventOnce = /* @__PURE__ */ __name(async (event) => {
|
|
10552
|
+
const validated = validateEvent(event);
|
|
10553
|
+
const existing = await loadEvents();
|
|
10554
|
+
await validateRelationshipTargets(validated, existing);
|
|
10555
|
+
if (existing.some((item) => item.eventId === validated.eventId)) {
|
|
10556
|
+
throw new Error(`Continuity event already exists: ${validated.eventId}`);
|
|
10557
|
+
}
|
|
10558
|
+
const scope = validated.projectId ? "project" : "user";
|
|
10559
|
+
const evidence = validated.sourceRefs.length > 0 ? validated.sourceRefs.map((ref) => ({
|
|
10560
|
+
id: ref.id,
|
|
10561
|
+
source: ref.kind,
|
|
10562
|
+
observedAt: validated.createdAt,
|
|
10563
|
+
supports: validated.eventId
|
|
10564
|
+
})) : [{ id: validated.eventId, source: "continuity-ledger", observedAt: validated.createdAt, supports: validated.eventId }];
|
|
10565
|
+
await memory.acquire({
|
|
10566
|
+
id: validated.eventId,
|
|
10567
|
+
knowledge: validated,
|
|
10568
|
+
applicability: { ...LEDGER_APPLICABILITY, topic: validated.eventType },
|
|
10569
|
+
evidence,
|
|
10570
|
+
epistemicType: epistemicTypeFor(validated.epistemicStatus),
|
|
10571
|
+
provenance: {
|
|
10572
|
+
source: validated.provenance.origin,
|
|
10573
|
+
detail: validated.provenance.channel
|
|
10574
|
+
},
|
|
10575
|
+
scope,
|
|
10576
|
+
sourceProject: validated.projectId ?? void 0,
|
|
10577
|
+
kind: "experience"
|
|
10578
|
+
});
|
|
10579
|
+
}, "recordEventOnce");
|
|
10580
|
+
var recordEvent = /* @__PURE__ */ __name(async (event) => {
|
|
10581
|
+
let lastError;
|
|
10582
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
10583
|
+
try {
|
|
10584
|
+
await recordEventOnce(event);
|
|
10585
|
+
return;
|
|
10586
|
+
} catch (error) {
|
|
10587
|
+
lastError = error;
|
|
10588
|
+
if (error instanceof Error && /Stale update/i.test(error.message) && attempt < 9) {
|
|
10589
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
10590
|
+
continue;
|
|
10591
|
+
}
|
|
10592
|
+
throw error;
|
|
10593
|
+
}
|
|
10594
|
+
}
|
|
10595
|
+
throw lastError instanceof Error ? lastError : new Error("Continuity event recording failed");
|
|
10596
|
+
}, "recordEvent");
|
|
10597
|
+
var getEvent = /* @__PURE__ */ __name(async (eventId) => {
|
|
10598
|
+
const events = await loadEvents();
|
|
10599
|
+
return findEvent(events, eventId) ?? null;
|
|
10600
|
+
}, "getEvent");
|
|
10601
|
+
var listEvents = /* @__PURE__ */ __name(async (filter = {}) => {
|
|
10602
|
+
const events = await loadEvents();
|
|
10603
|
+
return events.filter((event) => {
|
|
10604
|
+
if (filter.eventId && event.eventId !== filter.eventId) return false;
|
|
10605
|
+
if (filter.eventType && event.eventType !== filter.eventType) return false;
|
|
10606
|
+
if (filter.memoryDomain && event.memoryDomain !== filter.memoryDomain) return false;
|
|
10607
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) return false;
|
|
10608
|
+
if (filter.projectId !== void 0 && event.projectId !== filter.projectId) return false;
|
|
10609
|
+
if (filter.epistemicStatus && event.epistemicStatus !== filter.epistemicStatus) return false;
|
|
10610
|
+
return true;
|
|
10611
|
+
});
|
|
10612
|
+
}, "listEvents");
|
|
10613
|
+
var supersedingEvents = /* @__PURE__ */ __name((events, eventId) => events.filter((event) => event.relationships.supersedes.includes(eventId)), "supersedingEvents");
|
|
10614
|
+
var retractingEvents = /* @__PURE__ */ __name((events, eventId) => events.filter((event) => event.relationships.retracts.includes(eventId)), "retractingEvents");
|
|
10615
|
+
var resolutionEventsFor = /* @__PURE__ */ __name((events, eventId, conflictingIds) => events.filter(
|
|
10616
|
+
(event) => event.relationships.resolves.includes(eventId) || conflictingIds.some((id) => event.relationships.resolves.includes(id))
|
|
10617
|
+
), "resolutionEventsFor");
|
|
10618
|
+
var deriveValidity = /* @__PURE__ */ __name(async (eventId) => {
|
|
10619
|
+
let events = await loadEvents();
|
|
10620
|
+
for (let stabilityAttempt = 0; stabilityAttempt < 5; stabilityAttempt += 1) {
|
|
10621
|
+
const nextEvents = await loadEvents();
|
|
10622
|
+
if (nextEvents.length === events.length) break;
|
|
10623
|
+
events = nextEvents;
|
|
10624
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
10625
|
+
}
|
|
10626
|
+
const supersededBy = supersedingEvents(events, eventId).map((event) => event.eventId);
|
|
10627
|
+
const retractedBy = retractingEvents(events, eventId).map((event) => event.eventId);
|
|
10628
|
+
const contestedBy = supersededBy.length > 1 ? supersededBy : [];
|
|
10629
|
+
const resolvedBy = resolutionEventsFor(events, eventId, contestedBy).map((event) => event.eventId);
|
|
10630
|
+
let state3 = "current";
|
|
10631
|
+
if (retractedBy.length > 0) state3 = "retracted";
|
|
10632
|
+
else if (supersededBy.length > 0) state3 = supersededBy.length > 1 && resolvedBy.length === 0 ? "contested" : "superseded";
|
|
10633
|
+
else if (events.some((event) => event.eventId === eventId && event.eventType === "decision") === false) state3 = "current";
|
|
10634
|
+
if (supersededBy.length > 1 && resolvedBy.length === 0) state3 = "contested";
|
|
10635
|
+
return {
|
|
10636
|
+
eventId,
|
|
10637
|
+
state: state3,
|
|
10638
|
+
supersededBy,
|
|
10639
|
+
retractedBy,
|
|
10640
|
+
contestedBy,
|
|
10641
|
+
resolvedBy
|
|
10642
|
+
};
|
|
10643
|
+
}, "deriveValidity");
|
|
10644
|
+
var listContested = /* @__PURE__ */ __name(async () => {
|
|
10645
|
+
const events = await loadEvents();
|
|
10646
|
+
const targets = /* @__PURE__ */ new Set();
|
|
10647
|
+
for (const event of events) {
|
|
10648
|
+
for (const target of event.relationships.supersedes) targets.add(target);
|
|
10649
|
+
}
|
|
10650
|
+
const contested = [];
|
|
10651
|
+
for (const target of targets) {
|
|
10652
|
+
const supersededBy = supersedingEvents(events, target);
|
|
10653
|
+
if (supersededBy.length <= 1) continue;
|
|
10654
|
+
const conflictingIds = supersededBy.map((event) => event.eventId);
|
|
10655
|
+
const resolvedBy = resolutionEventsFor(events, target, conflictingIds);
|
|
10656
|
+
if (resolvedBy.length === 0) contested.push(target);
|
|
10657
|
+
}
|
|
10658
|
+
return contested.sort();
|
|
10659
|
+
}, "listContested");
|
|
10660
|
+
var resolveConflictOnce = /* @__PURE__ */ __name(async (resolution) => {
|
|
10661
|
+
const existing = await loadEvents();
|
|
10662
|
+
await validateResolutionRequest(resolution, existing);
|
|
10663
|
+
const resolutionEvent = {
|
|
10664
|
+
schemaVersion: 1,
|
|
10665
|
+
eventId: resolution.resolutionEventId,
|
|
10666
|
+
eventType: "correction",
|
|
10667
|
+
memoryDomain: "professional",
|
|
10668
|
+
epistemicStatus: "decision",
|
|
10669
|
+
confidence: "high",
|
|
10670
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10671
|
+
sessionId: "resolution",
|
|
10672
|
+
projectId: null,
|
|
10673
|
+
subject: "Continuity conflict resolution",
|
|
10674
|
+
content: {
|
|
10675
|
+
selectedEventId: resolution.selectedEventId,
|
|
10676
|
+
reason: resolution.reason
|
|
10677
|
+
},
|
|
10678
|
+
sourceRefs: resolution.conflictingEventIds.map((id) => ({ kind: "claim", id })),
|
|
10679
|
+
relationships: {
|
|
10680
|
+
refines: [],
|
|
10681
|
+
supersedes: [],
|
|
10682
|
+
retracts: [],
|
|
10683
|
+
dependsOn: [],
|
|
10684
|
+
resolves: [...resolution.conflictingEventIds],
|
|
10685
|
+
relatedTo: [resolution.selectedEventId]
|
|
10686
|
+
},
|
|
10687
|
+
provenance: {
|
|
10688
|
+
author: "system",
|
|
10689
|
+
origin: "continuity-ledger",
|
|
10690
|
+
channel: "acquisition",
|
|
10691
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10692
|
+
}
|
|
10693
|
+
};
|
|
10694
|
+
await recordEvent(resolutionEvent);
|
|
10695
|
+
}, "resolveConflictOnce");
|
|
10696
|
+
var reconstructValidity = /* @__PURE__ */ __name(async () => {
|
|
10697
|
+
const events = await loadEvents();
|
|
10698
|
+
const result = [];
|
|
10699
|
+
const targets = new Set(events.map((event) => event.eventId));
|
|
10700
|
+
for (const event of events) {
|
|
10701
|
+
for (const target of event.relationships.supersedes) targets.add(target);
|
|
10702
|
+
for (const target of event.relationships.retracts) targets.add(target);
|
|
10703
|
+
}
|
|
10704
|
+
for (const target of targets) {
|
|
10705
|
+
result.push(await deriveValidity(target));
|
|
10706
|
+
}
|
|
10707
|
+
return result.sort((left, right) => left.eventId.localeCompare(right.eventId));
|
|
10708
|
+
}, "reconstructValidity");
|
|
10709
|
+
var resolveConflict = /* @__PURE__ */ __name(async (resolution) => {
|
|
10710
|
+
let lastError;
|
|
10711
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
10712
|
+
try {
|
|
10713
|
+
await resolveConflictOnce(resolution);
|
|
10714
|
+
return;
|
|
10715
|
+
} catch (error) {
|
|
10716
|
+
lastError = error;
|
|
10717
|
+
if (error instanceof Error && /Stale update/i.test(error.message) && attempt < 9) {
|
|
10718
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
10719
|
+
continue;
|
|
10720
|
+
}
|
|
10721
|
+
throw error;
|
|
10722
|
+
}
|
|
10723
|
+
}
|
|
10724
|
+
throw lastError instanceof Error ? lastError : new Error("Continuity conflict resolution failed");
|
|
10725
|
+
}, "resolveConflict");
|
|
10726
|
+
var validateRelationships = validateRelationshipTargets;
|
|
10727
|
+
var continuityLedger = {
|
|
10728
|
+
recordEvent,
|
|
10729
|
+
record: recordEvent,
|
|
10730
|
+
append: recordEvent,
|
|
10731
|
+
add: recordEvent,
|
|
10732
|
+
getEvent,
|
|
10733
|
+
get: getEvent,
|
|
10734
|
+
find: getEvent,
|
|
10735
|
+
listEvents,
|
|
10736
|
+
list: listEvents,
|
|
10737
|
+
query: listEvents,
|
|
10738
|
+
deriveValidity,
|
|
10739
|
+
validity: deriveValidity,
|
|
10740
|
+
listContested,
|
|
10741
|
+
contested: listContested,
|
|
10742
|
+
resolveConflict,
|
|
10743
|
+
resolve: resolveConflict,
|
|
10744
|
+
reconstructValidity,
|
|
10745
|
+
validateRelationships,
|
|
10746
|
+
validateRelationshipTargets,
|
|
10747
|
+
validateResolution: validateResolutionRequest
|
|
10748
|
+
};
|
|
10749
|
+
|
|
10193
10750
|
// core/continuity/index.ts
|
|
10194
10751
|
var continuity = {
|
|
10195
10752
|
async context(options) {
|
|
@@ -12018,6 +12575,1232 @@ var createCompactionEngine = /* @__PURE__ */ __name((options) => new CompactionE
|
|
|
12018
12575
|
// index.ts
|
|
12019
12576
|
init_user_identity();
|
|
12020
12577
|
init_user_paths();
|
|
12578
|
+
|
|
12579
|
+
// core/governance/index.ts
|
|
12580
|
+
init_user_paths();
|
|
12581
|
+
init_policy();
|
|
12582
|
+
init_policy_namespace_lock();
|
|
12583
|
+
init_detection_events();
|
|
12584
|
+
init_policy();
|
|
12585
|
+
var GOVERNANCE_FILE_NAME = "governance.policy.json";
|
|
12586
|
+
var GOVERNANCE_FORMAT = "dotmem.governance-policy";
|
|
12587
|
+
var GOVERNANCE_VERSION = 1;
|
|
12588
|
+
var GOVERNANCE_AUDIT_RETENTION = 200;
|
|
12589
|
+
var GovernancePolicyError = class extends Error {
|
|
12590
|
+
static {
|
|
12591
|
+
__name(this, "GovernancePolicyError");
|
|
12592
|
+
}
|
|
12593
|
+
constructor(message) {
|
|
12594
|
+
super(message);
|
|
12595
|
+
this.name = "GovernancePolicyError";
|
|
12596
|
+
}
|
|
12597
|
+
};
|
|
12598
|
+
var requiredText3 = /* @__PURE__ */ __name((v) => typeof v === "string" && v.trim().length > 0, "requiredText");
|
|
12599
|
+
var nowIso2 = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString(), "nowIso");
|
|
12600
|
+
var joinPath7 = /* @__PURE__ */ __name((base, child) => {
|
|
12601
|
+
const sep = process.platform === "win32" ? "\\" : "/";
|
|
12602
|
+
return `${base.replace(/[\\/]+/g, sep)}${sep}${child}`;
|
|
12603
|
+
}, "joinPath");
|
|
12604
|
+
var resolveGovernanceDir = /* @__PURE__ */ __name((directory) => directory ?? joinPath7(resolveUserDotMemPath(), "policy"), "resolveGovernanceDir");
|
|
12605
|
+
var resolveGovernanceFilePath = /* @__PURE__ */ __name((directory) => joinPath7(resolveGovernanceDir(directory), GOVERNANCE_FILE_NAME), "resolveGovernanceFilePath");
|
|
12606
|
+
var ROLE_CAPABILITIES = {
|
|
12607
|
+
"human-owner": /* @__PURE__ */ new Set(["policy.propose", "policy.review", "policy.approve", "policy.reject", "policy.activate", "policy.suspend", "policy.retire", "role.bind", "governance.read", "governance.audit.read", "agent.execute", "guardrail.stop.activate", "guardrail.stop.release"]),
|
|
12608
|
+
"human-approver": /* @__PURE__ */ new Set(["policy.review", "policy.approve", "policy.reject", "governance.read", "governance.audit.read", "guardrail.stop.activate", "guardrail.stop.release"]),
|
|
12609
|
+
"coordinator-jo": /* @__PURE__ */ new Set(["policy.propose", "policy.review", "policy.activate", "governance.read", "governance.audit.read", "agent.execute"]),
|
|
12610
|
+
"specialist-agent": /* @__PURE__ */ new Set(["policy.propose", "governance.read"]),
|
|
12611
|
+
"policy-authority": /* @__PURE__ */ new Set(["policy.propose", "policy.review", "policy.activate", "governance.read", "governance.audit.read", "agent.execute"]),
|
|
12612
|
+
"auditor-reviewer": /* @__PURE__ */ new Set(["governance.read", "governance.audit.read"])
|
|
12613
|
+
};
|
|
12614
|
+
var defaultRoleBindings = /* @__PURE__ */ __name(() => [
|
|
12615
|
+
{ principalId: "hi", role: "human-owner", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
12616
|
+
{ principalId: "human-owner", role: "human-owner", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
12617
|
+
{ principalId: "jo", role: "coordinator-jo", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() }
|
|
12618
|
+
], "defaultRoleBindings");
|
|
12619
|
+
var emptyDocument2 = /* @__PURE__ */ __name(() => ({
|
|
12620
|
+
format: GOVERNANCE_FORMAT,
|
|
12621
|
+
version: GOVERNANCE_VERSION,
|
|
12622
|
+
revision: 0,
|
|
12623
|
+
policies: [],
|
|
12624
|
+
roleBindings: defaultRoleBindings(),
|
|
12625
|
+
audit: []
|
|
12626
|
+
}), "emptyDocument");
|
|
12627
|
+
var validateAuthority = /* @__PURE__ */ __name((value) => {
|
|
12628
|
+
try {
|
|
12629
|
+
return validatePolicyAuthority(value);
|
|
12630
|
+
} catch (err2) {
|
|
12631
|
+
void detectAuthorityEscalationAttempt({ claimed: "invalid governance authority", channel: "governance" }).catch(() => void 0);
|
|
12632
|
+
throw err2 instanceof GovernancePolicyError ? err2 : new GovernancePolicyError(err2.message);
|
|
12633
|
+
}
|
|
12634
|
+
}, "validateAuthority");
|
|
12635
|
+
var buildAudit = /* @__PURE__ */ __name((entry) => ({
|
|
12636
|
+
id: entry.id ?? `gov-audit.${entry.action}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`,
|
|
12637
|
+
action: entry.action,
|
|
12638
|
+
at: entry.at,
|
|
12639
|
+
...entry.actorPrincipal !== void 0 ? { actorPrincipal: entry.actorPrincipal } : {},
|
|
12640
|
+
...entry.actorRole !== void 0 ? { actorRole: entry.actorRole } : {},
|
|
12641
|
+
...entry.targetPolicyId !== void 0 ? { targetPolicyId: entry.targetPolicyId } : {},
|
|
12642
|
+
...entry.targetVersion !== void 0 ? { targetVersion: entry.targetVersion } : {},
|
|
12643
|
+
...entry.scope !== void 0 ? { scope: entry.scope } : {},
|
|
12644
|
+
outcome: entry.outcome,
|
|
12645
|
+
...entry.reason !== void 0 ? { reason: entry.reason } : {},
|
|
12646
|
+
...entry.detail !== void 0 ? { detail: entry.detail } : {},
|
|
12647
|
+
...entry.correlationId !== void 0 ? { correlationId: entry.correlationId } : {},
|
|
12648
|
+
...entry.mandatory !== void 0 ? { mandatory: entry.mandatory } : {}
|
|
12649
|
+
}), "buildAudit");
|
|
12650
|
+
var boundedAudit2 = /* @__PURE__ */ __name((entries2) => {
|
|
12651
|
+
if (entries2.length <= GOVERNANCE_AUDIT_RETENTION) return [...entries2];
|
|
12652
|
+
const kept = [];
|
|
12653
|
+
let budget = GOVERNANCE_AUDIT_RETENTION;
|
|
12654
|
+
for (let i2 = entries2.length - 1; i2 >= 0; i2--) {
|
|
12655
|
+
const e = entries2[i2];
|
|
12656
|
+
const mandatory = e.mandatory === true;
|
|
12657
|
+
if (mandatory || budget > 0) {
|
|
12658
|
+
kept.unshift(e);
|
|
12659
|
+
if (!mandatory) budget--;
|
|
12660
|
+
}
|
|
12661
|
+
}
|
|
12662
|
+
return kept;
|
|
12663
|
+
}, "boundedAudit");
|
|
12664
|
+
var validatePolicy = /* @__PURE__ */ __name((p) => {
|
|
12665
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) throw new GovernancePolicyError("Governance policy must be an object");
|
|
12666
|
+
const r = p;
|
|
12667
|
+
if (!requiredText3(r.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
12668
|
+
if (typeof r.version !== "number" || !Number.isInteger(r.version) || r.version < 1) throw new GovernancePolicyError("version must be a positive integer");
|
|
12669
|
+
if (!requiredText3(r.scope)) throw new GovernancePolicyError("scope is required");
|
|
12670
|
+
if (!requiredText3(r.rationale)) throw new GovernancePolicyError("rationale is required");
|
|
12671
|
+
if (!requiredText3(r.proposedBy)) throw new GovernancePolicyError("proposedBy is required");
|
|
12672
|
+
if (!requiredText3(r.proposedAt)) throw new GovernancePolicyError("proposedAt is required");
|
|
12673
|
+
if (typeof r.status !== "string" || !["proposed", "under_review", "approved", "active", "rejected", "suspended", "retired"].includes(r.status)) {
|
|
12674
|
+
throw new GovernancePolicyError("invalid status");
|
|
12675
|
+
}
|
|
12676
|
+
if (typeof r.revision !== "number" || !Number.isInteger(r.revision) || r.revision < 0) throw new GovernancePolicyError("revision must be a non-negative integer");
|
|
12677
|
+
if (!Array.isArray(r.applicablePrincipals)) throw new GovernancePolicyError("applicablePrincipals must be an array");
|
|
12678
|
+
if (!Array.isArray(r.applicableResources)) throw new GovernancePolicyError("applicableResources must be an array");
|
|
12679
|
+
const authority = validateAuthority(r.ownerAuthority);
|
|
12680
|
+
return {
|
|
12681
|
+
policyId: r.policyId,
|
|
12682
|
+
version: r.version,
|
|
12683
|
+
...requiredText3(r.title) ? { title: r.title } : {},
|
|
12684
|
+
...requiredText3(r.description) ? { description: r.description } : {},
|
|
12685
|
+
scope: r.scope,
|
|
12686
|
+
applicablePrincipals: r.applicablePrincipals,
|
|
12687
|
+
applicableResources: r.applicableResources,
|
|
12688
|
+
...requiredText3(r.effectiveAt) ? { effectiveAt: r.effectiveAt } : {},
|
|
12689
|
+
...requiredText3(r.expiresAt) ? { expiresAt: r.expiresAt } : {},
|
|
12690
|
+
rationale: r.rationale,
|
|
12691
|
+
status: r.status,
|
|
12692
|
+
ownerAuthority: authority,
|
|
12693
|
+
proposedBy: r.proposedBy,
|
|
12694
|
+
proposedAt: r.proposedAt,
|
|
12695
|
+
...requiredText3(r.reviewedBy) ? { reviewedBy: r.reviewedBy } : {},
|
|
12696
|
+
...requiredText3(r.reviewedAt) ? { reviewedAt: r.reviewedAt } : {},
|
|
12697
|
+
...r.approval && typeof r.approval === "object" ? { approval: r.approval } : {},
|
|
12698
|
+
...requiredText3(r.activatedBy) ? { activatedBy: r.activatedBy } : {},
|
|
12699
|
+
...requiredText3(r.activatedAt) ? { activatedAt: r.activatedAt } : {},
|
|
12700
|
+
...requiredText3(r.suspendedBy) ? { suspendedBy: r.suspendedBy } : {},
|
|
12701
|
+
...requiredText3(r.suspendedAt) ? { suspendedAt: r.suspendedAt } : {},
|
|
12702
|
+
...requiredText3(r.retiredBy) ? { retiredBy: r.retiredBy } : {},
|
|
12703
|
+
...requiredText3(r.retiredAt) ? { retiredAt: r.retiredAt } : {},
|
|
12704
|
+
...requiredText3(r.supersedes) ? { supersedes: r.supersedes } : {},
|
|
12705
|
+
...requiredText3(r.supersededBy) ? { supersededBy: r.supersededBy } : {},
|
|
12706
|
+
...requiredText3(r.correlationId) ? { correlationId: r.correlationId } : {},
|
|
12707
|
+
revision: r.revision,
|
|
12708
|
+
...requiredText3(r.fingerprint) ? { fingerprint: r.fingerprint } : {}
|
|
12709
|
+
};
|
|
12710
|
+
}, "validatePolicy");
|
|
12711
|
+
var validateRoleBinding = /* @__PURE__ */ __name((rb) => {
|
|
12712
|
+
if (!rb || typeof rb !== "object" || Array.isArray(rb)) throw new GovernancePolicyError("role binding must be an object");
|
|
12713
|
+
const r = rb;
|
|
12714
|
+
if (!requiredText3(r.principalId)) throw new GovernancePolicyError("principalId is required");
|
|
12715
|
+
if (typeof r.role !== "string" || !["human-owner", "human-approver", "coordinator-jo", "specialist-agent", "policy-authority", "auditor-reviewer"].includes(r.role)) {
|
|
12716
|
+
throw new GovernancePolicyError("invalid role");
|
|
12717
|
+
}
|
|
12718
|
+
if (!requiredText3(r.scope)) throw new GovernancePolicyError("scope is required");
|
|
12719
|
+
return {
|
|
12720
|
+
principalId: r.principalId,
|
|
12721
|
+
role: r.role,
|
|
12722
|
+
scope: r.scope,
|
|
12723
|
+
...requiredText3(r.grantedBy) ? { grantedBy: r.grantedBy } : {},
|
|
12724
|
+
...requiredText3(r.grantedAt) ? { grantedAt: r.grantedAt } : {}
|
|
12725
|
+
};
|
|
12726
|
+
}, "validateRoleBinding");
|
|
12727
|
+
var validateDocument2 = /* @__PURE__ */ __name((value) => {
|
|
12728
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new GovernancePolicyError("document must be an object");
|
|
12729
|
+
const d = value;
|
|
12730
|
+
if (d.format !== GOVERNANCE_FORMAT) throw new GovernancePolicyError("invalid format");
|
|
12731
|
+
if (d.version !== GOVERNANCE_VERSION) throw new GovernancePolicyError("invalid version");
|
|
12732
|
+
if (typeof d.revision !== "number" || !Number.isInteger(d.revision) || d.revision < 0) throw new GovernancePolicyError("invalid revision");
|
|
12733
|
+
if (!Array.isArray(d.policies)) throw new GovernancePolicyError("policies must be an array");
|
|
12734
|
+
if (!Array.isArray(d.roleBindings)) throw new GovernancePolicyError("roleBindings must be an array");
|
|
12735
|
+
if (!Array.isArray(d.audit)) throw new GovernancePolicyError("audit must be an array");
|
|
12736
|
+
const policies = d.policies.map(validatePolicy);
|
|
12737
|
+
const roleBindings = d.roleBindings.map(validateRoleBinding);
|
|
12738
|
+
const audit = d.audit.map((e) => {
|
|
12739
|
+
if (!e || typeof e !== "object" || Array.isArray(e)) throw new GovernancePolicyError("audit entry must be an object");
|
|
12740
|
+
const r = e;
|
|
12741
|
+
if (!requiredText3(r.id)) throw new GovernancePolicyError("audit id required");
|
|
12742
|
+
if (!requiredText3(r.action)) throw new GovernancePolicyError("audit action required");
|
|
12743
|
+
if (!requiredText3(r.at)) throw new GovernancePolicyError("audit at required");
|
|
12744
|
+
if (r.outcome !== "applied" && r.outcome !== "rejected") throw new GovernancePolicyError("audit outcome invalid");
|
|
12745
|
+
return r;
|
|
12746
|
+
});
|
|
12747
|
+
return { format: GOVERNANCE_FORMAT, version: GOVERNANCE_VERSION, revision: d.revision, policies, roleBindings, audit };
|
|
12748
|
+
}, "validateDocument");
|
|
12749
|
+
var scopeCovers = /* @__PURE__ */ __name((bindingScope, targetScope) => {
|
|
12750
|
+
if (bindingScope === "*") return true;
|
|
12751
|
+
if (bindingScope === targetScope) return true;
|
|
12752
|
+
return targetScope.startsWith(bindingScope.endsWith("/") ? bindingScope : bindingScope + "/");
|
|
12753
|
+
}, "scopeCovers");
|
|
12754
|
+
var GovernancePolicyStore = class {
|
|
12755
|
+
constructor(directory) {
|
|
12756
|
+
this.directory = directory;
|
|
12757
|
+
}
|
|
12758
|
+
directory;
|
|
12759
|
+
static {
|
|
12760
|
+
__name(this, "GovernancePolicyStore");
|
|
12761
|
+
}
|
|
12762
|
+
dir() {
|
|
12763
|
+
return resolveGovernanceDir(this.directory);
|
|
12764
|
+
}
|
|
12765
|
+
filePath() {
|
|
12766
|
+
return resolveGovernanceFilePath(this.directory);
|
|
12767
|
+
}
|
|
12768
|
+
async load() {
|
|
12769
|
+
const fs = await import('fs/promises');
|
|
12770
|
+
let raw;
|
|
12771
|
+
try {
|
|
12772
|
+
raw = await fs.readFile(this.filePath(), "utf8");
|
|
12773
|
+
} catch (err2) {
|
|
12774
|
+
if (err2.code === "ENOENT") return emptyDocument2();
|
|
12775
|
+
throw err2;
|
|
12776
|
+
}
|
|
12777
|
+
return validateDocument2(JSON.parse(raw));
|
|
12778
|
+
}
|
|
12779
|
+
async readDoc(fs) {
|
|
12780
|
+
try {
|
|
12781
|
+
const raw = await fs.readFile(this.filePath(), "utf8");
|
|
12782
|
+
return validateDocument2(JSON.parse(raw));
|
|
12783
|
+
} catch (err2) {
|
|
12784
|
+
if (err2.code === "ENOENT") return emptyDocument2();
|
|
12785
|
+
throw err2;
|
|
12786
|
+
}
|
|
12787
|
+
}
|
|
12788
|
+
async writeDoc(fs, doc) {
|
|
12789
|
+
await fs.mkdir(this.dir(), { recursive: true });
|
|
12790
|
+
const fp = this.filePath();
|
|
12791
|
+
const tmp = `${fp}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
12792
|
+
try {
|
|
12793
|
+
await fs.writeFile(tmp, `${JSON.stringify(doc, null, 2)}
|
|
12794
|
+
`, "utf8");
|
|
12795
|
+
const handle = await fs.open(tmp, "r+");
|
|
12796
|
+
try {
|
|
12797
|
+
await handle.sync();
|
|
12798
|
+
} finally {
|
|
12799
|
+
await handle.close();
|
|
12800
|
+
}
|
|
12801
|
+
await fs.rename(tmp, fp);
|
|
12802
|
+
} catch (err2) {
|
|
12803
|
+
await fs.rm(tmp, { force: true }).catch(() => void 0);
|
|
12804
|
+
throw err2;
|
|
12805
|
+
}
|
|
12806
|
+
}
|
|
12807
|
+
async mutate(operation, options = {}) {
|
|
12808
|
+
const directory = this.dir();
|
|
12809
|
+
return withPolicyNamespaceLock(async (session3) => {
|
|
12810
|
+
const fs = await import('fs/promises');
|
|
12811
|
+
const doc = await this.readDoc(fs);
|
|
12812
|
+
if (options.basedOnRevision !== void 0 && options.basedOnRevision !== doc.revision) {
|
|
12813
|
+
throw new GovernancePolicyError(`Stale governance revision: expected ${options.basedOnRevision}, current ${doc.revision}`);
|
|
12814
|
+
}
|
|
12815
|
+
const outcome = operation(doc);
|
|
12816
|
+
let next = outcome.document;
|
|
12817
|
+
validateDocument2(next);
|
|
12818
|
+
await session3.assertOwnership();
|
|
12819
|
+
await this.writeDoc(fs, next);
|
|
12820
|
+
return outcome.result;
|
|
12821
|
+
}, { directory });
|
|
12822
|
+
}
|
|
12823
|
+
// --- role helpers ---
|
|
12824
|
+
findRoleBinding(doc, principalId, scope) {
|
|
12825
|
+
const candidates = doc.roleBindings.filter((rb) => rb.principalId === principalId && scopeCovers(rb.scope, scope));
|
|
12826
|
+
if (candidates.length === 0) return null;
|
|
12827
|
+
return candidates.sort((a, b) => b.scope.length - a.scope.length)[0];
|
|
12828
|
+
}
|
|
12829
|
+
resolveRole(principalId, scope = "*") {
|
|
12830
|
+
return { principalId, role: null, scope, reason: "use resolveRoleAsync" };
|
|
12831
|
+
}
|
|
12832
|
+
async resolveRoleAsync(principalId, scope = "*") {
|
|
12833
|
+
const doc = await this.load();
|
|
12834
|
+
const rb = this.findRoleBinding(doc, principalId, scope);
|
|
12835
|
+
if (!rb) return { principalId, role: null, scope, reason: "no role binding for principal in scope" };
|
|
12836
|
+
return { principalId, role: rb.role, scope, reason: `bound role ${rb.role} (scope ${rb.scope})` };
|
|
12837
|
+
}
|
|
12838
|
+
async hasPermission(principalId, action, scope) {
|
|
12839
|
+
const res = await this.resolveRoleAsync(principalId, scope);
|
|
12840
|
+
if (!res.role) return false;
|
|
12841
|
+
return ROLE_CAPABILITIES[res.role].has(action);
|
|
12842
|
+
}
|
|
12843
|
+
// --- role binding governed ---
|
|
12844
|
+
async bindRole(input, basedOnRevision) {
|
|
12845
|
+
const authority = validateAuthority(input.authority);
|
|
12846
|
+
if (!requiredText3(input.principalId)) throw new GovernancePolicyError("principalId is required");
|
|
12847
|
+
if (!requiredText3(input.grantedBy)) throw new GovernancePolicyError("grantedBy is required");
|
|
12848
|
+
if (input.principalId === input.grantedBy && (input.role === "human-owner" || input.role === "human-approver" || input.role === "policy-authority")) {
|
|
12849
|
+
void detectSelfPromoted({ claim: `self-grant ${input.role}`, channel: "governance.role.bind" }).catch(() => void 0);
|
|
12850
|
+
throw new GovernancePolicyError("self-grant of elevated role denied");
|
|
12851
|
+
}
|
|
12852
|
+
const doc0 = await this.load();
|
|
12853
|
+
const grantedByRole = this.findRoleBinding(doc0, input.grantedBy, input.scope ?? "*");
|
|
12854
|
+
if (!grantedByRole || !ROLE_CAPABILITIES[grantedByRole.role].has("role.bind")) {
|
|
12855
|
+
throw new GovernancePolicyError("grantedBy lacks role.bind authority");
|
|
12856
|
+
}
|
|
12857
|
+
if (authority.kind !== "HI" && authority.kind !== "operator") {
|
|
12858
|
+
throw new GovernancePolicyError("invalid authority for role binding");
|
|
12859
|
+
}
|
|
12860
|
+
const binding = {
|
|
12861
|
+
principalId: input.principalId,
|
|
12862
|
+
role: input.role,
|
|
12863
|
+
scope: input.scope || "*",
|
|
12864
|
+
grantedBy: input.grantedBy,
|
|
12865
|
+
grantedAt: nowIso2()
|
|
12866
|
+
};
|
|
12867
|
+
return this.mutate((doc) => {
|
|
12868
|
+
const nextBindings = [...doc.roleBindings.filter((rb) => !(rb.principalId === binding.principalId && rb.scope === binding.scope)), binding];
|
|
12869
|
+
const revision = doc.revision + 1;
|
|
12870
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
12871
|
+
action: "role.bind",
|
|
12872
|
+
at: binding.grantedAt,
|
|
12873
|
+
actorPrincipal: input.grantedBy,
|
|
12874
|
+
actorRole: grantedByRole.role,
|
|
12875
|
+
scope: binding.scope,
|
|
12876
|
+
outcome: "applied",
|
|
12877
|
+
detail: `bound ${binding.principalId} to ${binding.role} in scope ${binding.scope}`,
|
|
12878
|
+
correlationId: void 0
|
|
12879
|
+
})]);
|
|
12880
|
+
return { document: { ...doc, revision, roleBindings: nextBindings, audit }, result: binding };
|
|
12881
|
+
}, { basedOnRevision });
|
|
12882
|
+
}
|
|
12883
|
+
// --- propose ---
|
|
12884
|
+
async propose(input, basedOnRevision) {
|
|
12885
|
+
if (!input || typeof input !== "object") throw new GovernancePolicyError("proposal input required");
|
|
12886
|
+
if (!requiredText3(input.scope)) throw new GovernancePolicyError("scope is required");
|
|
12887
|
+
if (!requiredText3(input.rationale)) throw new GovernancePolicyError("rationale is required");
|
|
12888
|
+
if (!requiredText3(input.proposedBy)) throw new GovernancePolicyError("proposedBy is required");
|
|
12889
|
+
const authority = validateAuthority(input.authority);
|
|
12890
|
+
const doc0 = await this.load();
|
|
12891
|
+
if (!await this.hasPermission(input.proposedBy, "policy.propose", input.scope)) {
|
|
12892
|
+
void detectAuthorityEscalationAttempt({ claimed: `${input.proposedBy} propose without permission`, channel: "governance.propose" }).catch(() => void 0);
|
|
12893
|
+
await this.recordDenial(doc0, "policy.propose", input.proposedBy, input.scope, "permission denied");
|
|
12894
|
+
throw new GovernancePolicyError("proposer lacks policy.propose permission");
|
|
12895
|
+
}
|
|
12896
|
+
const policyId = requiredText3(input.policyId) ? input.policyId : `gov-policy.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
12897
|
+
const existing = doc0.policies.filter((p) => p.policyId === policyId);
|
|
12898
|
+
const version = existing.length > 0 ? Math.max(...existing.map((p) => p.version)) + 1 : 1;
|
|
12899
|
+
const supersedes = input.supersedes;
|
|
12900
|
+
const policy2 = {
|
|
12901
|
+
policyId,
|
|
12902
|
+
version,
|
|
12903
|
+
...requiredText3(input.title) ? { title: input.title } : {},
|
|
12904
|
+
...requiredText3(input.description) ? { description: input.description } : {},
|
|
12905
|
+
scope: input.scope,
|
|
12906
|
+
applicablePrincipals: input.applicablePrincipals ?? [],
|
|
12907
|
+
applicableResources: input.applicableResources ?? [],
|
|
12908
|
+
...requiredText3(input.effectiveAt) ? { effectiveAt: input.effectiveAt } : {},
|
|
12909
|
+
...requiredText3(input.expiresAt) ? { expiresAt: input.expiresAt } : {},
|
|
12910
|
+
rationale: input.rationale,
|
|
12911
|
+
status: "proposed",
|
|
12912
|
+
ownerAuthority: authority,
|
|
12913
|
+
proposedBy: input.proposedBy,
|
|
12914
|
+
proposedAt: nowIso2(),
|
|
12915
|
+
...requiredText3(supersedes) ? { supersedes } : {},
|
|
12916
|
+
...requiredText3(input.correlationId) ? { correlationId: input.correlationId } : {},
|
|
12917
|
+
revision: 0
|
|
12918
|
+
};
|
|
12919
|
+
return this.mutate((doc) => {
|
|
12920
|
+
const revision = doc.revision + 1;
|
|
12921
|
+
const fingerprint = `${policy2.policyId}@v${policy2.version}`;
|
|
12922
|
+
const stored = { ...policy2, revision, fingerprint };
|
|
12923
|
+
const policies = [...doc.policies, stored];
|
|
12924
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
12925
|
+
action: "policy.proposed",
|
|
12926
|
+
at: stored.proposedAt,
|
|
12927
|
+
actorPrincipal: input.proposedBy,
|
|
12928
|
+
targetPolicyId: stored.policyId,
|
|
12929
|
+
targetVersion: stored.version,
|
|
12930
|
+
scope: stored.scope,
|
|
12931
|
+
outcome: "applied",
|
|
12932
|
+
detail: `policy ${stored.policyId} v${stored.version} proposed`,
|
|
12933
|
+
correlationId: input.correlationId
|
|
12934
|
+
})]);
|
|
12935
|
+
return { document: { ...doc, revision, policies, audit }, result: stored };
|
|
12936
|
+
}, { basedOnRevision });
|
|
12937
|
+
}
|
|
12938
|
+
async recordDenial(doc, action, actor, scope, reason) {
|
|
12939
|
+
try {
|
|
12940
|
+
await this.mutate((d) => {
|
|
12941
|
+
const revision = d.revision + 1;
|
|
12942
|
+
const audit = boundedAudit2([...d.audit, buildAudit({
|
|
12943
|
+
action: `${action}.denied`,
|
|
12944
|
+
at: nowIso2(),
|
|
12945
|
+
actorPrincipal: actor,
|
|
12946
|
+
scope,
|
|
12947
|
+
outcome: "rejected",
|
|
12948
|
+
reason
|
|
12949
|
+
})]);
|
|
12950
|
+
return { document: { ...d, revision, audit }, result: void 0 };
|
|
12951
|
+
});
|
|
12952
|
+
} catch {
|
|
12953
|
+
}
|
|
12954
|
+
}
|
|
12955
|
+
// --- review ---
|
|
12956
|
+
async review(input) {
|
|
12957
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
12958
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
12959
|
+
if (!requiredText3(input.reviewerPrincipal)) throw new GovernancePolicyError("reviewerPrincipal is required");
|
|
12960
|
+
const doc0 = await this.load();
|
|
12961
|
+
if (!await this.hasPermission(input.reviewerPrincipal, "policy.review", "*")) {
|
|
12962
|
+
const pol = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
12963
|
+
const scope = pol?.scope ?? "*";
|
|
12964
|
+
if (!await this.hasPermission(input.reviewerPrincipal, "policy.review", scope)) {
|
|
12965
|
+
await this.recordDenial(doc0, "policy.review", input.reviewerPrincipal, scope, "permission denied");
|
|
12966
|
+
throw new GovernancePolicyError("reviewer lacks policy.review permission");
|
|
12967
|
+
}
|
|
12968
|
+
}
|
|
12969
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
12970
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
12971
|
+
if (policy2.status !== "proposed") throw new GovernancePolicyError(`cannot review policy in status ${policy2.status}`);
|
|
12972
|
+
return this.mutate((doc) => {
|
|
12973
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
12974
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
12975
|
+
const updated = {
|
|
12976
|
+
...doc.policies[idx],
|
|
12977
|
+
status: "under_review",
|
|
12978
|
+
reviewedBy: input.reviewerPrincipal,
|
|
12979
|
+
reviewedAt: nowIso2()
|
|
12980
|
+
};
|
|
12981
|
+
const policies = [...doc.policies];
|
|
12982
|
+
policies[idx] = updated;
|
|
12983
|
+
const revision = doc.revision + 1;
|
|
12984
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
12985
|
+
action: "policy.reviewed",
|
|
12986
|
+
at: updated.reviewedAt,
|
|
12987
|
+
actorPrincipal: input.reviewerPrincipal,
|
|
12988
|
+
targetPolicyId: updated.policyId,
|
|
12989
|
+
targetVersion: updated.version,
|
|
12990
|
+
scope: updated.scope,
|
|
12991
|
+
outcome: "applied",
|
|
12992
|
+
correlationId: input.correlationId
|
|
12993
|
+
})]);
|
|
12994
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
12995
|
+
});
|
|
12996
|
+
}
|
|
12997
|
+
// --- decide (approve/reject) ---
|
|
12998
|
+
async decide(input) {
|
|
12999
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13000
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13001
|
+
if (!requiredText3(input.approverPrincipal)) throw new GovernancePolicyError("approverPrincipal is required");
|
|
13002
|
+
if (input.decision !== "approved" && input.decision !== "rejected") throw new GovernancePolicyError("decision must be approved or rejected");
|
|
13003
|
+
const doc0 = await this.load();
|
|
13004
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13005
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13006
|
+
const scope = policy2.scope;
|
|
13007
|
+
const action = input.decision === "approved" ? "policy.approve" : "policy.reject";
|
|
13008
|
+
if (!await this.hasPermission(input.approverPrincipal, "policy.approve", scope) && !await this.hasPermission(input.approverPrincipal, "policy.reject", scope)) {
|
|
13009
|
+
if (!await this.hasPermission(input.approverPrincipal, "policy.approve", scope)) {
|
|
13010
|
+
await this.recordDenial(doc0, action, input.approverPrincipal, scope, "permission denied");
|
|
13011
|
+
throw new GovernancePolicyError("approver lacks approval permission");
|
|
13012
|
+
}
|
|
13013
|
+
}
|
|
13014
|
+
if (input.approverPrincipal === policy2.proposedBy) {
|
|
13015
|
+
void detectSelfPromoted({ claim: `self-approval ${policy2.policyId}`, channel: "governance.approve" }).catch(() => void 0);
|
|
13016
|
+
await this.recordDenial(doc0, action, input.approverPrincipal, scope, "self-approval denied");
|
|
13017
|
+
throw new GovernancePolicyError("self-approval denied: proposer cannot be sole approver");
|
|
13018
|
+
}
|
|
13019
|
+
if (policy2.status !== "under_review" && policy2.status !== "proposed") {
|
|
13020
|
+
throw new GovernancePolicyError(`cannot decide policy in status ${policy2.status}`);
|
|
13021
|
+
}
|
|
13022
|
+
return this.mutate((doc) => {
|
|
13023
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13024
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13025
|
+
const current = doc.policies[idx];
|
|
13026
|
+
const newStatus = input.decision === "approved" ? "approved" : "rejected";
|
|
13027
|
+
const updated = {
|
|
13028
|
+
...current,
|
|
13029
|
+
status: newStatus,
|
|
13030
|
+
approval: {
|
|
13031
|
+
approverPrincipal: input.approverPrincipal,
|
|
13032
|
+
decision: input.decision,
|
|
13033
|
+
...requiredText3(input.reason) ? { reason: input.reason } : {},
|
|
13034
|
+
at: nowIso2()
|
|
13035
|
+
}
|
|
13036
|
+
};
|
|
13037
|
+
const policies = [...doc.policies];
|
|
13038
|
+
policies[idx] = updated;
|
|
13039
|
+
const revision = doc.revision + 1;
|
|
13040
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13041
|
+
action: input.decision === "approved" ? "policy.approved" : "policy.rejected",
|
|
13042
|
+
at: updated.approval.at,
|
|
13043
|
+
actorPrincipal: input.approverPrincipal,
|
|
13044
|
+
targetPolicyId: updated.policyId,
|
|
13045
|
+
targetVersion: updated.version,
|
|
13046
|
+
scope: updated.scope,
|
|
13047
|
+
outcome: "applied",
|
|
13048
|
+
reason: input.reason,
|
|
13049
|
+
correlationId: input.correlationId
|
|
13050
|
+
})]);
|
|
13051
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13052
|
+
});
|
|
13053
|
+
}
|
|
13054
|
+
// --- activate ---
|
|
13055
|
+
async activate(input) {
|
|
13056
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13057
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13058
|
+
if (!requiredText3(input.activatorPrincipal)) throw new GovernancePolicyError("activatorPrincipal is required");
|
|
13059
|
+
const doc0 = await this.load();
|
|
13060
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13061
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13062
|
+
if (policy2.status !== "approved") throw new GovernancePolicyError(`cannot activate policy in status ${policy2.status}`);
|
|
13063
|
+
const scope = policy2.scope;
|
|
13064
|
+
if (!await this.hasPermission(input.activatorPrincipal, "policy.activate", scope)) {
|
|
13065
|
+
await this.recordDenial(doc0, "policy.activate", input.activatorPrincipal, scope, "permission denied");
|
|
13066
|
+
throw new GovernancePolicyError("activator lacks policy.activate permission");
|
|
13067
|
+
}
|
|
13068
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
13069
|
+
if (policy2.effectiveAt && Date.parse(policy2.effectiveAt) > now3.getTime()) {
|
|
13070
|
+
throw new GovernancePolicyError("policy not yet effective");
|
|
13071
|
+
}
|
|
13072
|
+
if (policy2.expiresAt && Date.parse(policy2.expiresAt) <= now3.getTime()) {
|
|
13073
|
+
throw new GovernancePolicyError("policy expired");
|
|
13074
|
+
}
|
|
13075
|
+
return this.mutate((doc) => {
|
|
13076
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13077
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13078
|
+
const current = doc.policies[idx];
|
|
13079
|
+
if (current.status !== "approved") throw new GovernancePolicyError(`cannot activate policy in status ${current.status}`);
|
|
13080
|
+
const policies = doc.policies.map((p) => {
|
|
13081
|
+
if (p.policyId === current.policyId && p.version !== current.version && p.status === "active") {
|
|
13082
|
+
return { ...p, status: "retired", retiredBy: input.activatorPrincipal, retiredAt: nowIso2(), supersededBy: `${current.policyId}@v${current.version}` };
|
|
13083
|
+
}
|
|
13084
|
+
return p;
|
|
13085
|
+
});
|
|
13086
|
+
const nowStr = nowIso2();
|
|
13087
|
+
policies[idx] = {
|
|
13088
|
+
...current,
|
|
13089
|
+
status: "active",
|
|
13090
|
+
activatedBy: input.activatorPrincipal,
|
|
13091
|
+
activatedAt: nowStr
|
|
13092
|
+
};
|
|
13093
|
+
const updated = policies[idx];
|
|
13094
|
+
const revision = doc.revision + 1;
|
|
13095
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13096
|
+
action: "policy.activated",
|
|
13097
|
+
at: nowStr,
|
|
13098
|
+
actorPrincipal: input.activatorPrincipal,
|
|
13099
|
+
targetPolicyId: updated.policyId,
|
|
13100
|
+
targetVersion: updated.version,
|
|
13101
|
+
scope: updated.scope,
|
|
13102
|
+
outcome: "applied",
|
|
13103
|
+
correlationId: input.correlationId
|
|
13104
|
+
})]);
|
|
13105
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13106
|
+
});
|
|
13107
|
+
}
|
|
13108
|
+
async suspend(input) {
|
|
13109
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13110
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13111
|
+
if (!requiredText3(input.actorPrincipal)) throw new GovernancePolicyError("actorPrincipal is required");
|
|
13112
|
+
const doc0 = await this.load();
|
|
13113
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13114
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13115
|
+
const scope = policy2.scope;
|
|
13116
|
+
if (!await this.hasPermission(input.actorPrincipal, "policy.suspend", scope)) {
|
|
13117
|
+
const role = await this.resolveRoleAsync(input.actorPrincipal, scope);
|
|
13118
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13119
|
+
await this.recordDenial(doc0, "policy.suspend", input.actorPrincipal, scope, "permission denied");
|
|
13120
|
+
throw new GovernancePolicyError("actor lacks suspend permission");
|
|
13121
|
+
}
|
|
13122
|
+
}
|
|
13123
|
+
return this.mutate((doc) => {
|
|
13124
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13125
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13126
|
+
const current = doc.policies[idx];
|
|
13127
|
+
if (current.status !== "active" && current.status !== "approved") throw new GovernancePolicyError(`cannot suspend policy in status ${current.status}`);
|
|
13128
|
+
const updated = {
|
|
13129
|
+
...current,
|
|
13130
|
+
status: "suspended",
|
|
13131
|
+
suspendedBy: input.actorPrincipal,
|
|
13132
|
+
suspendedAt: nowIso2()
|
|
13133
|
+
};
|
|
13134
|
+
const policies = [...doc.policies];
|
|
13135
|
+
policies[idx] = updated;
|
|
13136
|
+
const revision = doc.revision + 1;
|
|
13137
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13138
|
+
action: "policy.suspended",
|
|
13139
|
+
at: updated.suspendedAt,
|
|
13140
|
+
actorPrincipal: input.actorPrincipal,
|
|
13141
|
+
targetPolicyId: updated.policyId,
|
|
13142
|
+
targetVersion: updated.version,
|
|
13143
|
+
scope: updated.scope,
|
|
13144
|
+
outcome: "applied",
|
|
13145
|
+
reason: input.reason,
|
|
13146
|
+
correlationId: input.correlationId
|
|
13147
|
+
})]);
|
|
13148
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13149
|
+
});
|
|
13150
|
+
}
|
|
13151
|
+
async retire(input) {
|
|
13152
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13153
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13154
|
+
if (!requiredText3(input.actorPrincipal)) throw new GovernancePolicyError("actorPrincipal is required");
|
|
13155
|
+
const doc0 = await this.load();
|
|
13156
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13157
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13158
|
+
const scope = policy2.scope;
|
|
13159
|
+
const role = await this.resolveRoleAsync(input.actorPrincipal, scope);
|
|
13160
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13161
|
+
await this.recordDenial(doc0, "policy.retire", input.actorPrincipal, scope, "permission denied");
|
|
13162
|
+
throw new GovernancePolicyError("actor lacks retire permission");
|
|
13163
|
+
}
|
|
13164
|
+
return this.mutate((doc) => {
|
|
13165
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13166
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13167
|
+
const current = doc.policies[idx];
|
|
13168
|
+
const updated = {
|
|
13169
|
+
...current,
|
|
13170
|
+
status: "retired",
|
|
13171
|
+
retiredBy: input.actorPrincipal,
|
|
13172
|
+
retiredAt: nowIso2()
|
|
13173
|
+
};
|
|
13174
|
+
const policies = [...doc.policies];
|
|
13175
|
+
policies[idx] = updated;
|
|
13176
|
+
const revision = doc.revision + 1;
|
|
13177
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13178
|
+
action: "policy.retired",
|
|
13179
|
+
at: updated.retiredAt,
|
|
13180
|
+
actorPrincipal: input.actorPrincipal,
|
|
13181
|
+
targetPolicyId: updated.policyId,
|
|
13182
|
+
targetVersion: updated.version,
|
|
13183
|
+
scope: updated.scope,
|
|
13184
|
+
outcome: "applied",
|
|
13185
|
+
reason: input.reason,
|
|
13186
|
+
correlationId: input.correlationId
|
|
13187
|
+
})]);
|
|
13188
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13189
|
+
});
|
|
13190
|
+
}
|
|
13191
|
+
// --- resolution (fail-closed) ---
|
|
13192
|
+
async resolvePolicy(policyId, version, requestedScope, now3 = /* @__PURE__ */ new Date()) {
|
|
13193
|
+
const doc = await this.load();
|
|
13194
|
+
const policy2 = doc.policies.find((p) => p.policyId === policyId && p.version === version);
|
|
13195
|
+
if (!policy2) {
|
|
13196
|
+
return { outcome: "none", policy: null, reason: "no applicable policy" };
|
|
13197
|
+
}
|
|
13198
|
+
if (!scopeCovers(policy2.scope, requestedScope)) {
|
|
13199
|
+
return { outcome: "insufficient-scope", policy: null, reason: "policy scope does not cover requested scope" };
|
|
13200
|
+
}
|
|
13201
|
+
if (!policy2.ownerAuthority || policy2.ownerAuthority.kind !== "HI" && policy2.ownerAuthority.kind !== "operator") {
|
|
13202
|
+
return { outcome: "unresolved-authority", policy: null, reason: "unresolved or invalid authority" };
|
|
13203
|
+
}
|
|
13204
|
+
if (policy2.effectiveAt && Date.parse(policy2.effectiveAt) > now3.getTime()) {
|
|
13205
|
+
return { outcome: "not-yet-effective", policy: null, reason: "policy not yet effective" };
|
|
13206
|
+
}
|
|
13207
|
+
if (policy2.expiresAt && Date.parse(policy2.expiresAt) <= now3.getTime()) {
|
|
13208
|
+
return { outcome: "expired", policy: null, reason: "policy expired" };
|
|
13209
|
+
}
|
|
13210
|
+
if (policy2.status !== "active") {
|
|
13211
|
+
return { outcome: "none", policy: null, reason: `policy status ${policy2.status} is not active` };
|
|
13212
|
+
}
|
|
13213
|
+
if (policy2.fingerprint && policy2.fingerprint !== `${policy2.policyId}@v${policy2.version}`) {
|
|
13214
|
+
return { outcome: "invalid-provenance", policy: null, reason: "fingerprint mismatch" };
|
|
13215
|
+
}
|
|
13216
|
+
return { outcome: "applicable", policy: policy2, reason: "applicable and valid" };
|
|
13217
|
+
}
|
|
13218
|
+
async resolveActivePolicies(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13219
|
+
const doc = await this.load();
|
|
13220
|
+
const active = doc.policies.filter((p) => p.status === "active" && scopeCovers(p.scope, scope));
|
|
13221
|
+
return active.filter((p) => {
|
|
13222
|
+
if (p.effectiveAt && Date.parse(p.effectiveAt) > now3.getTime()) return false;
|
|
13223
|
+
if (p.expiresAt && Date.parse(p.expiresAt) <= now3.getTime()) return false;
|
|
13224
|
+
return true;
|
|
13225
|
+
});
|
|
13226
|
+
}
|
|
13227
|
+
async checkConflicts(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13228
|
+
const actives = await this.resolveActivePolicies(scope, now3);
|
|
13229
|
+
const conflicting = actives.length > 1;
|
|
13230
|
+
return { conflicting, policies: actives };
|
|
13231
|
+
}
|
|
13232
|
+
async failClosedResolve(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13233
|
+
const actives = await this.resolveActivePolicies(scope, now3);
|
|
13234
|
+
if (actives.length === 0) {
|
|
13235
|
+
return { outcome: "none", policy: null, reason: "no applicable policy" };
|
|
13236
|
+
}
|
|
13237
|
+
if (actives.length > 1) {
|
|
13238
|
+
return { outcome: "conflicting", policy: null, reason: "conflicting active policies" };
|
|
13239
|
+
}
|
|
13240
|
+
const p = actives[0];
|
|
13241
|
+
if (!p.ownerAuthority || p.ownerAuthority.kind !== "HI" && p.ownerAuthority.kind !== "operator") {
|
|
13242
|
+
return { outcome: "unresolved-authority", policy: null, reason: "unresolved authority" };
|
|
13243
|
+
}
|
|
13244
|
+
return { outcome: "applicable", policy: p, reason: "applicable and valid" };
|
|
13245
|
+
}
|
|
13246
|
+
// --- authorization granted/denied audit ---
|
|
13247
|
+
async recordAuthorization(decision) {
|
|
13248
|
+
await this.mutate((doc) => {
|
|
13249
|
+
const revision = doc.revision + 1;
|
|
13250
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13251
|
+
action: decision.allowed ? "authorization.granted" : "authorization.denied",
|
|
13252
|
+
at: nowIso2(),
|
|
13253
|
+
actorPrincipal: decision.principalId,
|
|
13254
|
+
scope: decision.scope ?? decision.resource,
|
|
13255
|
+
outcome: decision.allowed ? "applied" : "rejected",
|
|
13256
|
+
reason: decision.reason,
|
|
13257
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13258
|
+
correlationId: decision.correlationId
|
|
13259
|
+
})]);
|
|
13260
|
+
return { document: { ...doc, revision, audit }, result: void 0 };
|
|
13261
|
+
});
|
|
13262
|
+
}
|
|
13263
|
+
// --- list ---
|
|
13264
|
+
async listPolicies() {
|
|
13265
|
+
const doc = await this.load();
|
|
13266
|
+
return [...doc.policies].sort((a, b) => a.policyId.localeCompare(b.policyId) || a.version - b.version);
|
|
13267
|
+
}
|
|
13268
|
+
async listRoleBindings() {
|
|
13269
|
+
const doc = await this.load();
|
|
13270
|
+
return [...doc.roleBindings];
|
|
13271
|
+
}
|
|
13272
|
+
async auditTrail() {
|
|
13273
|
+
const doc = await this.load();
|
|
13274
|
+
return [...doc.audit];
|
|
13275
|
+
}
|
|
13276
|
+
async recordAuditEvent(input) {
|
|
13277
|
+
await this.mutate((doc) => {
|
|
13278
|
+
const revision = doc.revision + 1;
|
|
13279
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13280
|
+
action: input.action,
|
|
13281
|
+
at: input.at ?? nowIso2(),
|
|
13282
|
+
...input.actorPrincipal !== void 0 ? { actorPrincipal: input.actorPrincipal } : {},
|
|
13283
|
+
...input.actorRole !== void 0 ? { actorRole: input.actorRole } : {},
|
|
13284
|
+
...input.targetPolicyId !== void 0 ? { targetPolicyId: input.targetPolicyId } : {},
|
|
13285
|
+
...input.targetVersion !== void 0 ? { targetVersion: input.targetVersion } : {},
|
|
13286
|
+
...input.scope !== void 0 ? { scope: input.scope } : {},
|
|
13287
|
+
outcome: input.outcome,
|
|
13288
|
+
...input.reason !== void 0 ? { reason: input.reason } : {},
|
|
13289
|
+
...input.detail !== void 0 ? { detail: input.detail } : {},
|
|
13290
|
+
...input.correlationId !== void 0 ? { correlationId: input.correlationId } : {},
|
|
13291
|
+
...input.mandatory !== void 0 ? { mandatory: input.mandatory } : {}
|
|
13292
|
+
})]);
|
|
13293
|
+
return { document: { ...doc, revision, audit }, result: void 0 };
|
|
13294
|
+
});
|
|
13295
|
+
}
|
|
13296
|
+
};
|
|
13297
|
+
var roleHasPermission = /* @__PURE__ */ __name((role, action) => ROLE_CAPABILITIES[role].has(action), "roleHasPermission");
|
|
13298
|
+
var GOVERNANCE_ROLES = [
|
|
13299
|
+
"human-owner",
|
|
13300
|
+
"human-approver",
|
|
13301
|
+
"coordinator-jo",
|
|
13302
|
+
"specialist-agent",
|
|
13303
|
+
"policy-authority",
|
|
13304
|
+
"auditor-reviewer"
|
|
13305
|
+
];
|
|
13306
|
+
var GOVERNANCE_POLICY_STATUSES = [
|
|
13307
|
+
"proposed",
|
|
13308
|
+
"under_review",
|
|
13309
|
+
"approved",
|
|
13310
|
+
"active",
|
|
13311
|
+
"rejected",
|
|
13312
|
+
"suspended",
|
|
13313
|
+
"retired"
|
|
13314
|
+
];
|
|
13315
|
+
|
|
13316
|
+
// core/guardrails/index.ts
|
|
13317
|
+
init_user_paths();
|
|
13318
|
+
init_policy_namespace_lock();
|
|
13319
|
+
var STOP_FILE_NAME = "guardrails.stop.json";
|
|
13320
|
+
var STOP_FORMAT = "dotmem.guardrails-stop";
|
|
13321
|
+
var STOP_VERSION = 1;
|
|
13322
|
+
var GuardrailError = class extends Error {
|
|
13323
|
+
static {
|
|
13324
|
+
__name(this, "GuardrailError");
|
|
13325
|
+
}
|
|
13326
|
+
constructor(message) {
|
|
13327
|
+
super(message);
|
|
13328
|
+
this.name = "GuardrailError";
|
|
13329
|
+
}
|
|
13330
|
+
};
|
|
13331
|
+
var requiredText4 = /* @__PURE__ */ __name((v) => typeof v === "string" && v.trim().length > 0, "requiredText");
|
|
13332
|
+
var nowIso3 = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString(), "nowIso");
|
|
13333
|
+
var joinPath8 = /* @__PURE__ */ __name((base, child) => {
|
|
13334
|
+
const sep = process.platform === "win32" ? "\\" : "/";
|
|
13335
|
+
return `${base.replace(/[\\/]+/g, sep)}${sep}${child}`;
|
|
13336
|
+
}, "joinPath");
|
|
13337
|
+
var resolveGuardrailDir = /* @__PURE__ */ __name((directory) => directory ?? joinPath8(resolveUserDotMemPath(), "policy"), "resolveGuardrailDir");
|
|
13338
|
+
var resolveStopFilePath = /* @__PURE__ */ __name((directory) => joinPath8(resolveGuardrailDir(directory), STOP_FILE_NAME), "resolveStopFilePath");
|
|
13339
|
+
var ACTIONS_REQUIRING_TRUSTED_IDENTITY = /* @__PURE__ */ new Set([
|
|
13340
|
+
"agent.execute",
|
|
13341
|
+
"guardrail.stop.activate",
|
|
13342
|
+
"guardrail.stop.release"
|
|
13343
|
+
]);
|
|
13344
|
+
var GuardrailService = class {
|
|
13345
|
+
static {
|
|
13346
|
+
__name(this, "GuardrailService");
|
|
13347
|
+
}
|
|
13348
|
+
directory;
|
|
13349
|
+
governance;
|
|
13350
|
+
constructor(directory, governance) {
|
|
13351
|
+
this.directory = directory;
|
|
13352
|
+
this.governance = governance ?? new GovernancePolicyStore(directory);
|
|
13353
|
+
}
|
|
13354
|
+
dir() {
|
|
13355
|
+
return resolveGuardrailDir(this.directory);
|
|
13356
|
+
}
|
|
13357
|
+
// --- stop controller persistence ---
|
|
13358
|
+
async readStopState() {
|
|
13359
|
+
const fs = await import('fs/promises');
|
|
13360
|
+
let raw;
|
|
13361
|
+
try {
|
|
13362
|
+
raw = await fs.readFile(resolveStopFilePath(this.directory), "utf8");
|
|
13363
|
+
} catch (err2) {
|
|
13364
|
+
if (err2.code === "ENOENT") {
|
|
13365
|
+
return { stopped: false, revision: 0 };
|
|
13366
|
+
}
|
|
13367
|
+
throw err2;
|
|
13368
|
+
}
|
|
13369
|
+
const parsed = JSON.parse(raw);
|
|
13370
|
+
if (parsed.format !== STOP_FORMAT || parsed.version !== STOP_VERSION) {
|
|
13371
|
+
throw new GuardrailError("invalid stop state document");
|
|
13372
|
+
}
|
|
13373
|
+
return {
|
|
13374
|
+
stopped: parsed.stopped === true,
|
|
13375
|
+
...requiredText4(parsed.activatedBy) ? { activatedBy: parsed.activatedBy } : {},
|
|
13376
|
+
...requiredText4(parsed.activatedAt) ? { activatedAt: parsed.activatedAt } : {},
|
|
13377
|
+
...requiredText4(parsed.reason) ? { reason: parsed.reason } : {},
|
|
13378
|
+
...requiredText4(parsed.releasedBy) ? { releasedBy: parsed.releasedBy } : {},
|
|
13379
|
+
...requiredText4(parsed.releasedAt) ? { releasedAt: parsed.releasedAt } : {},
|
|
13380
|
+
revision: typeof parsed.revision === "number" ? parsed.revision : 0
|
|
13381
|
+
};
|
|
13382
|
+
}
|
|
13383
|
+
async writeStopState(state3) {
|
|
13384
|
+
const fs = await import('fs/promises');
|
|
13385
|
+
const dir = this.dir();
|
|
13386
|
+
await fs.mkdir(dir, { recursive: true });
|
|
13387
|
+
const fp = resolveStopFilePath(this.directory);
|
|
13388
|
+
const tmp = `${fp}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
13389
|
+
const doc = { format: STOP_FORMAT, version: STOP_VERSION, ...state3 };
|
|
13390
|
+
try {
|
|
13391
|
+
await fs.writeFile(tmp, `${JSON.stringify(doc, null, 2)}
|
|
13392
|
+
`, "utf8");
|
|
13393
|
+
const handle = await fs.open(tmp, "r+");
|
|
13394
|
+
try {
|
|
13395
|
+
await handle.sync();
|
|
13396
|
+
} finally {
|
|
13397
|
+
await handle.close();
|
|
13398
|
+
}
|
|
13399
|
+
await fs.rename(tmp, fp);
|
|
13400
|
+
} catch (err2) {
|
|
13401
|
+
await fs.rm(tmp, { force: true }).catch(() => void 0);
|
|
13402
|
+
throw err2;
|
|
13403
|
+
}
|
|
13404
|
+
}
|
|
13405
|
+
async getStopState() {
|
|
13406
|
+
return this.readStopState();
|
|
13407
|
+
}
|
|
13408
|
+
async isStopped() {
|
|
13409
|
+
const s = await this.readStopState();
|
|
13410
|
+
return s.stopped;
|
|
13411
|
+
}
|
|
13412
|
+
async activateStop(input) {
|
|
13413
|
+
if (!requiredText4(input.actorPrincipal)) throw new GuardrailError("actorPrincipal is required");
|
|
13414
|
+
const role = await this.governance.resolveRoleAsync(input.actorPrincipal, "*");
|
|
13415
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13416
|
+
await this.governance.recordAuditEvent({
|
|
13417
|
+
action: "guardrail.stop.activate.denied",
|
|
13418
|
+
actorPrincipal: input.actorPrincipal,
|
|
13419
|
+
actorRole: role.role ?? void 0,
|
|
13420
|
+
outcome: "rejected",
|
|
13421
|
+
reason: "insufficient authority to activate stop",
|
|
13422
|
+
correlationId: input.correlationId
|
|
13423
|
+
}).catch(() => void 0);
|
|
13424
|
+
throw new GuardrailError("unauthorized stop activation");
|
|
13425
|
+
}
|
|
13426
|
+
const current = await this.readStopState();
|
|
13427
|
+
if (current.stopped) {
|
|
13428
|
+
await this.governance.recordAuditEvent({
|
|
13429
|
+
action: "guardrail.stop.activate",
|
|
13430
|
+
actorPrincipal: input.actorPrincipal,
|
|
13431
|
+
actorRole: role.role ?? void 0,
|
|
13432
|
+
outcome: "applied",
|
|
13433
|
+
reason: "already stopped (idempotent)",
|
|
13434
|
+
correlationId: input.correlationId
|
|
13435
|
+
}).catch(() => void 0);
|
|
13436
|
+
return current;
|
|
13437
|
+
}
|
|
13438
|
+
const now3 = nowIso3();
|
|
13439
|
+
const next = {
|
|
13440
|
+
stopped: true,
|
|
13441
|
+
activatedBy: input.actorPrincipal,
|
|
13442
|
+
activatedAt: now3,
|
|
13443
|
+
...requiredText4(input.reason) ? { reason: input.reason } : {},
|
|
13444
|
+
revision: current.revision + 1
|
|
13445
|
+
};
|
|
13446
|
+
await this.governance.recordAuditEvent({
|
|
13447
|
+
action: "guardrail.stop.activated",
|
|
13448
|
+
actorPrincipal: input.actorPrincipal,
|
|
13449
|
+
actorRole: role.role ?? void 0,
|
|
13450
|
+
outcome: "applied",
|
|
13451
|
+
reason: input.reason,
|
|
13452
|
+
correlationId: input.correlationId
|
|
13453
|
+
});
|
|
13454
|
+
await withPolicyNamespaceLock(async (session3) => {
|
|
13455
|
+
await this.writeStopState(next);
|
|
13456
|
+
await session3.assertOwnership();
|
|
13457
|
+
}, { directory: this.dir() });
|
|
13458
|
+
return next;
|
|
13459
|
+
}
|
|
13460
|
+
async releaseStop(input) {
|
|
13461
|
+
if (!requiredText4(input.actorPrincipal)) throw new GuardrailError("actorPrincipal is required");
|
|
13462
|
+
const role = await this.governance.resolveRoleAsync(input.actorPrincipal, "*");
|
|
13463
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13464
|
+
await this.governance.recordAuditEvent({
|
|
13465
|
+
action: "guardrail.stop.release.denied",
|
|
13466
|
+
actorPrincipal: input.actorPrincipal,
|
|
13467
|
+
actorRole: role.role ?? void 0,
|
|
13468
|
+
outcome: "rejected",
|
|
13469
|
+
reason: "insufficient authority to release stop",
|
|
13470
|
+
correlationId: input.correlationId
|
|
13471
|
+
}).catch(() => void 0);
|
|
13472
|
+
throw new GuardrailError("unauthorized stop release");
|
|
13473
|
+
}
|
|
13474
|
+
const current = await this.readStopState();
|
|
13475
|
+
if (!current.stopped) {
|
|
13476
|
+
await this.governance.recordAuditEvent({
|
|
13477
|
+
action: "guardrail.stop.release",
|
|
13478
|
+
actorPrincipal: input.actorPrincipal,
|
|
13479
|
+
actorRole: role.role ?? void 0,
|
|
13480
|
+
outcome: "applied",
|
|
13481
|
+
reason: "already running (idempotent)",
|
|
13482
|
+
correlationId: input.correlationId
|
|
13483
|
+
}).catch(() => void 0);
|
|
13484
|
+
return current;
|
|
13485
|
+
}
|
|
13486
|
+
const now3 = nowIso3();
|
|
13487
|
+
const next = {
|
|
13488
|
+
stopped: false,
|
|
13489
|
+
activatedBy: current.activatedBy,
|
|
13490
|
+
activatedAt: current.activatedAt,
|
|
13491
|
+
reason: current.reason,
|
|
13492
|
+
releasedBy: input.actorPrincipal,
|
|
13493
|
+
releasedAt: now3,
|
|
13494
|
+
revision: current.revision + 1
|
|
13495
|
+
};
|
|
13496
|
+
await this.governance.recordAuditEvent({
|
|
13497
|
+
action: "guardrail.stop.released",
|
|
13498
|
+
actorPrincipal: input.actorPrincipal,
|
|
13499
|
+
actorRole: role.role ?? void 0,
|
|
13500
|
+
outcome: "applied",
|
|
13501
|
+
reason: input.reason,
|
|
13502
|
+
correlationId: input.correlationId
|
|
13503
|
+
});
|
|
13504
|
+
await withPolicyNamespaceLock(async (session3) => {
|
|
13505
|
+
await this.writeStopState(next);
|
|
13506
|
+
await session3.assertOwnership();
|
|
13507
|
+
}, { directory: this.dir() });
|
|
13508
|
+
return next;
|
|
13509
|
+
}
|
|
13510
|
+
auditModes = {
|
|
13511
|
+
"agent.execute": "mandatory",
|
|
13512
|
+
"guardrail.stop.activate": "mandatory",
|
|
13513
|
+
"guardrail.stop.release": "mandatory"
|
|
13514
|
+
};
|
|
13515
|
+
auditModeFor(action) {
|
|
13516
|
+
return this.auditModes[action] ?? "best_effort";
|
|
13517
|
+
}
|
|
13518
|
+
async recordAudit(decision, auditMode) {
|
|
13519
|
+
const persisted = await this.auditDecision(decision);
|
|
13520
|
+
decision.auditPersisted = persisted;
|
|
13521
|
+
decision.auditMode = auditMode;
|
|
13522
|
+
if (!persisted && auditMode === "mandatory" && decision.allowed) {
|
|
13523
|
+
decision.outcome = "audit_failure";
|
|
13524
|
+
decision.allowed = false;
|
|
13525
|
+
decision.reason = "mandatory audit persistence failed";
|
|
13526
|
+
}
|
|
13527
|
+
return persisted;
|
|
13528
|
+
}
|
|
13529
|
+
// --- evaluation ---
|
|
13530
|
+
async evaluate(input) {
|
|
13531
|
+
const evaluatedAt = nowIso3();
|
|
13532
|
+
const auditMode = this.auditModeFor(input.action);
|
|
13533
|
+
const principalId = input.principalContext.trusted ? input.principalContext.trustedPrincipalId : input.principalContext.assertedPrincipalId;
|
|
13534
|
+
const stopState = await this.readStopState().catch(() => ({ stopped: false, revision: 0 }));
|
|
13535
|
+
const base = {
|
|
13536
|
+
outcome: "deny",
|
|
13537
|
+
allowed: false,
|
|
13538
|
+
reason: "",
|
|
13539
|
+
evaluatedAt,
|
|
13540
|
+
principalId,
|
|
13541
|
+
action: input.action,
|
|
13542
|
+
resource: input.resource,
|
|
13543
|
+
scope: input.scope,
|
|
13544
|
+
phase: input.phase,
|
|
13545
|
+
stopState: stopState.stopped ? "stopped" : "running",
|
|
13546
|
+
correlationId: input.correlationId,
|
|
13547
|
+
auditMode,
|
|
13548
|
+
auditPersisted: false
|
|
13549
|
+
};
|
|
13550
|
+
if (stopState.stopped && input.action !== "guardrail.stop.release") {
|
|
13551
|
+
const decision2 = { ...base, outcome: "stop_active", reason: "global stop active" };
|
|
13552
|
+
await this.recordAudit(decision2, auditMode);
|
|
13553
|
+
return decision2;
|
|
13554
|
+
}
|
|
13555
|
+
const requiresTrusted = ACTIONS_REQUIRING_TRUSTED_IDENTITY.has(input.action);
|
|
13556
|
+
if (requiresTrusted && !input.principalContext.trusted) {
|
|
13557
|
+
const decision2 = { ...base, outcome: "identity_unverified", reason: "trusted identity required but not verified" };
|
|
13558
|
+
await this.recordAudit(decision2, auditMode);
|
|
13559
|
+
return decision2;
|
|
13560
|
+
}
|
|
13561
|
+
if (!requiredText4(input.action) || !requiredText4(input.resource) || !requiredText4(input.scope)) {
|
|
13562
|
+
const decision2 = { ...base, outcome: "invalid_context", reason: "missing action/resource/scope" };
|
|
13563
|
+
await this.recordAudit(decision2, auditMode);
|
|
13564
|
+
return decision2;
|
|
13565
|
+
}
|
|
13566
|
+
let policyId;
|
|
13567
|
+
let policyVersion;
|
|
13568
|
+
try {
|
|
13569
|
+
const resolution = await this.governance.failClosedResolve(input.scope, input.now ?? /* @__PURE__ */ new Date());
|
|
13570
|
+
if (resolution.outcome !== "applicable" || !resolution.policy) {
|
|
13571
|
+
const decision2 = {
|
|
13572
|
+
...base,
|
|
13573
|
+
outcome: resolution.outcome === "conflicting" ? "deny" : "policy_unavailable",
|
|
13574
|
+
reason: `policy resolution: ${resolution.outcome} - ${resolution.reason}`
|
|
13575
|
+
};
|
|
13576
|
+
await this.recordAudit(decision2, auditMode);
|
|
13577
|
+
return decision2;
|
|
13578
|
+
}
|
|
13579
|
+
policyId = resolution.policy.policyId;
|
|
13580
|
+
policyVersion = resolution.policy.version;
|
|
13581
|
+
const actorForPermission = principalId ?? "";
|
|
13582
|
+
const allowed = await this.governance.hasPermission(actorForPermission, input.action, input.scope);
|
|
13583
|
+
if (!allowed) {
|
|
13584
|
+
const decision2 = {
|
|
13585
|
+
...base,
|
|
13586
|
+
outcome: "deny",
|
|
13587
|
+
reason: "permission denied by governance role binding",
|
|
13588
|
+
policyId,
|
|
13589
|
+
policyVersion
|
|
13590
|
+
};
|
|
13591
|
+
await this.recordAudit(decision2, auditMode);
|
|
13592
|
+
return decision2;
|
|
13593
|
+
}
|
|
13594
|
+
} catch (err2) {
|
|
13595
|
+
const decision2 = {
|
|
13596
|
+
...base,
|
|
13597
|
+
outcome: "policy_unavailable",
|
|
13598
|
+
reason: `governance unavailable: ${err2.message}`
|
|
13599
|
+
};
|
|
13600
|
+
await this.recordAudit(decision2, auditMode);
|
|
13601
|
+
return decision2;
|
|
13602
|
+
}
|
|
13603
|
+
const decision = {
|
|
13604
|
+
...base,
|
|
13605
|
+
outcome: "allow",
|
|
13606
|
+
allowed: true,
|
|
13607
|
+
reason: "allowed by governance policy",
|
|
13608
|
+
policyId,
|
|
13609
|
+
policyVersion
|
|
13610
|
+
};
|
|
13611
|
+
await this.recordAudit(decision, auditMode);
|
|
13612
|
+
return decision;
|
|
13613
|
+
}
|
|
13614
|
+
async auditDecision(decision) {
|
|
13615
|
+
try {
|
|
13616
|
+
await this.governance.recordAuditEvent({
|
|
13617
|
+
action: decision.allowed ? "guardrail.evaluation.allowed" : "guardrail.evaluation.denied",
|
|
13618
|
+
actorPrincipal: decision.principalId,
|
|
13619
|
+
scope: decision.scope,
|
|
13620
|
+
outcome: decision.allowed ? "applied" : "rejected",
|
|
13621
|
+
reason: decision.reason,
|
|
13622
|
+
detail: `${decision.action} on ${decision.resource} (${decision.outcome})`,
|
|
13623
|
+
correlationId: decision.correlationId,
|
|
13624
|
+
mandatory: decision.auditMode === "mandatory"
|
|
13625
|
+
});
|
|
13626
|
+
return true;
|
|
13627
|
+
} catch {
|
|
13628
|
+
return false;
|
|
13629
|
+
}
|
|
13630
|
+
}
|
|
13631
|
+
async execute(input, operation) {
|
|
13632
|
+
const decision = await this.evaluate(input);
|
|
13633
|
+
if (!decision.allowed) {
|
|
13634
|
+
return { decision, executed: false, error: decision.reason };
|
|
13635
|
+
}
|
|
13636
|
+
await this.governance.recordAuditEvent({
|
|
13637
|
+
action: "guardrail.operation.started",
|
|
13638
|
+
actorPrincipal: decision.principalId,
|
|
13639
|
+
scope: decision.scope,
|
|
13640
|
+
outcome: "applied",
|
|
13641
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13642
|
+
correlationId: decision.correlationId
|
|
13643
|
+
}).catch(() => void 0);
|
|
13644
|
+
let result;
|
|
13645
|
+
try {
|
|
13646
|
+
result = await operation();
|
|
13647
|
+
} catch (err2) {
|
|
13648
|
+
await this.governance.recordAuditEvent({
|
|
13649
|
+
action: "guardrail.operation.failed",
|
|
13650
|
+
actorPrincipal: decision.principalId,
|
|
13651
|
+
scope: decision.scope,
|
|
13652
|
+
outcome: "rejected",
|
|
13653
|
+
reason: err2.message,
|
|
13654
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13655
|
+
correlationId: decision.correlationId
|
|
13656
|
+
}).catch(() => void 0);
|
|
13657
|
+
return { decision, executed: true, error: err2.message };
|
|
13658
|
+
}
|
|
13659
|
+
await this.governance.recordAuditEvent({
|
|
13660
|
+
action: "guardrail.operation.completed",
|
|
13661
|
+
actorPrincipal: decision.principalId,
|
|
13662
|
+
scope: decision.scope,
|
|
13663
|
+
outcome: "applied",
|
|
13664
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13665
|
+
correlationId: decision.correlationId
|
|
13666
|
+
}).catch(() => void 0);
|
|
13667
|
+
return { decision, executed: true, result };
|
|
13668
|
+
}
|
|
13669
|
+
};
|
|
13670
|
+
function createGuardrailService(directory, governance) {
|
|
13671
|
+
return new GuardrailService(directory, governance);
|
|
13672
|
+
}
|
|
13673
|
+
__name(createGuardrailService, "createGuardrailService");
|
|
13674
|
+
|
|
13675
|
+
// core/memory-anthology/index.ts
|
|
13676
|
+
var VALID_TRANSITIONS = {
|
|
13677
|
+
unassessed: ["asserted", "corroborated", "reviewed", "disputed", "refuted", "superseded"],
|
|
13678
|
+
asserted: ["corroborated", "reviewed", "disputed", "refuted", "superseded"],
|
|
13679
|
+
corroborated: ["reviewed", "disputed", "refuted", "superseded"],
|
|
13680
|
+
reviewed: ["disputed", "refuted", "superseded"],
|
|
13681
|
+
disputed: ["corroborated", "reviewed", "refuted", "superseded"],
|
|
13682
|
+
refuted: ["superseded"],
|
|
13683
|
+
superseded: []
|
|
13684
|
+
};
|
|
13685
|
+
function canTransitionEpistemic(from, to) {
|
|
13686
|
+
if (from === to) return true;
|
|
13687
|
+
return (VALID_TRANSITIONS[from] ?? []).includes(to);
|
|
13688
|
+
}
|
|
13689
|
+
__name(canTransitionEpistemic, "canTransitionEpistemic");
|
|
13690
|
+
function validateEpistemicTransition(from, to, reason) {
|
|
13691
|
+
if (from === to) return { allowed: true, reason: "no change" };
|
|
13692
|
+
if (!canTransitionEpistemic(from, to)) {
|
|
13693
|
+
return { allowed: false, reason: `invalid transition ${from} -> ${to}` };
|
|
13694
|
+
}
|
|
13695
|
+
if (to === "corroborated" && !reason) {
|
|
13696
|
+
return { allowed: false, reason: "corroborated requires explanation of independent sources" };
|
|
13697
|
+
}
|
|
13698
|
+
if (to === "reviewed" && !reason) {
|
|
13699
|
+
return { allowed: false, reason: "reviewed requires reviewer identity and scope" };
|
|
13700
|
+
}
|
|
13701
|
+
return { allowed: true, reason: reason ?? "ok" };
|
|
13702
|
+
}
|
|
13703
|
+
__name(validateEpistemicTransition, "validateEpistemicTransition");
|
|
13704
|
+
function validateProvenance(p) {
|
|
13705
|
+
const errors = [];
|
|
13706
|
+
if (!p || typeof p !== "object") {
|
|
13707
|
+
errors.push("provenance must be an object");
|
|
13708
|
+
return { valid: false, errors };
|
|
13709
|
+
}
|
|
13710
|
+
const kinds = ["user-statement", "system-observation", "imported-document", "external-source", "agent-inference", "model-summary", "human-correction", "system-event", "unknown"];
|
|
13711
|
+
if (!kinds.includes(p.kind)) errors.push("invalid provenance kind");
|
|
13712
|
+
if (p.observedAt !== void 0 && (typeof p.observedAt !== "string" || isNaN(Date.parse(p.observedAt)))) {
|
|
13713
|
+
errors.push("observedAt must be valid ISO timestamp if provided");
|
|
13714
|
+
}
|
|
13715
|
+
return { valid: errors.length === 0, errors };
|
|
13716
|
+
}
|
|
13717
|
+
__name(validateProvenance, "validateProvenance");
|
|
13718
|
+
function validateMemoryItem(item) {
|
|
13719
|
+
const errors = [];
|
|
13720
|
+
if (!item || typeof item !== "object") {
|
|
13721
|
+
errors.push("item must be an object");
|
|
13722
|
+
return { valid: false, errors };
|
|
13723
|
+
}
|
|
13724
|
+
if (typeof item.itemId !== "string" || !item.itemId.trim()) errors.push("itemId is required");
|
|
13725
|
+
if (typeof item.schemaVersion !== "number" || !Number.isInteger(item.schemaVersion) || item.schemaVersion < 1) errors.push("schemaVersion must be positive integer");
|
|
13726
|
+
if (typeof item.scope !== "string" || !item.scope.trim()) errors.push("scope is required");
|
|
13727
|
+
if (!item.temporal || typeof item.temporal !== "object") errors.push("temporal is required");
|
|
13728
|
+
else if (typeof item.temporal.recordedAt !== "string" || isNaN(Date.parse(item.temporal.recordedAt))) errors.push("recordedAt must be valid ISO timestamp");
|
|
13729
|
+
if (!item.provenance) errors.push("provenance is required");
|
|
13730
|
+
else {
|
|
13731
|
+
const pv = validateProvenance(item.provenance);
|
|
13732
|
+
if (!pv.valid) errors.push(...pv.errors);
|
|
13733
|
+
}
|
|
13734
|
+
if (!item.revision || typeof item.revision !== "object") errors.push("revision is required");
|
|
13735
|
+
else if (typeof item.revision.revision !== "number" || item.revision.revision < 0) errors.push("revision.revision must be non-negative integer");
|
|
13736
|
+
if (item.category === "derived-summary") {
|
|
13737
|
+
if (!item.derivedLineage) errors.push("derived summary requires lineage");
|
|
13738
|
+
}
|
|
13739
|
+
return { valid: errors.length === 0, errors };
|
|
13740
|
+
}
|
|
13741
|
+
__name(validateMemoryItem, "validateMemoryItem");
|
|
13742
|
+
function validateDerivedLineage(lineage, _sourceStatuses) {
|
|
13743
|
+
const errors = [];
|
|
13744
|
+
if (!lineage || typeof lineage !== "object") {
|
|
13745
|
+
errors.push("lineage required");
|
|
13746
|
+
return { valid: false, errors };
|
|
13747
|
+
}
|
|
13748
|
+
if (!Array.isArray(lineage.sourceItemIds) || lineage.sourceItemIds.length === 0) errors.push("sourceItemIds required");
|
|
13749
|
+
if (!Array.isArray(lineage.sourceRevisions) || lineage.sourceRevisions.length !== lineage.sourceItemIds.length) errors.push("sourceRevisions must match sourceItemIds length");
|
|
13750
|
+
if (typeof lineage.derivedAt !== "string" || isNaN(Date.parse(lineage.derivedAt))) errors.push("derivedAt invalid");
|
|
13751
|
+
if (typeof lineage.producingComponent !== "string" || !lineage.producingComponent.trim()) errors.push("producingComponent required");
|
|
13752
|
+
return { valid: errors.length === 0, errors };
|
|
13753
|
+
}
|
|
13754
|
+
__name(validateDerivedLineage, "validateDerivedLineage");
|
|
13755
|
+
function markDerivedStale(lineage) {
|
|
13756
|
+
return { ...lineage, stale: true };
|
|
13757
|
+
}
|
|
13758
|
+
__name(markDerivedStale, "markDerivedStale");
|
|
13759
|
+
function isLegacyRecord(item) {
|
|
13760
|
+
return !item || typeof item !== "object" || !item.provenance || !item.temporal || !item.revision;
|
|
13761
|
+
}
|
|
13762
|
+
__name(isLegacyRecord, "isLegacyRecord");
|
|
13763
|
+
function normalizeLegacyRecord(item) {
|
|
13764
|
+
return {
|
|
13765
|
+
itemId: item?.id ?? item?.itemId ?? `legacy-${Math.random().toString(36).slice(2, 10)}`,
|
|
13766
|
+
schemaVersion: 0,
|
|
13767
|
+
category: "legacy",
|
|
13768
|
+
payload: item,
|
|
13769
|
+
scope: item?.scope ?? "unknown",
|
|
13770
|
+
provenance: { kind: "unknown" },
|
|
13771
|
+
epistemicStatus: "unassessed",
|
|
13772
|
+
sensitivity: "unknown",
|
|
13773
|
+
temporal: { recordedAt: item?.createdAt ? new Date(item.createdAt).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
13774
|
+
revision: { revision: 0 }
|
|
13775
|
+
};
|
|
13776
|
+
}
|
|
13777
|
+
__name(normalizeLegacyRecord, "normalizeLegacyRecord");
|
|
13778
|
+
var MEMORY_CATEGORIES = [
|
|
13779
|
+
"observation",
|
|
13780
|
+
"claim",
|
|
13781
|
+
"fact-candidate",
|
|
13782
|
+
"hypothesis",
|
|
13783
|
+
"decision",
|
|
13784
|
+
"preference",
|
|
13785
|
+
"instruction",
|
|
13786
|
+
"experience",
|
|
13787
|
+
"goal",
|
|
13788
|
+
"relationship",
|
|
13789
|
+
"derived-summary",
|
|
13790
|
+
"unknown-disputed",
|
|
13791
|
+
"legacy"
|
|
13792
|
+
];
|
|
13793
|
+
var EPITEMIC_STATUSES2 = [
|
|
13794
|
+
"unassessed",
|
|
13795
|
+
"asserted",
|
|
13796
|
+
"corroborated",
|
|
13797
|
+
"reviewed",
|
|
13798
|
+
"disputed",
|
|
13799
|
+
"refuted",
|
|
13800
|
+
"superseded"
|
|
13801
|
+
];
|
|
13802
|
+
|
|
13803
|
+
// index.ts
|
|
12021
13804
|
var dotmem = {
|
|
12022
13805
|
version: "0.0.8",
|
|
12023
13806
|
isBrowser,
|
|
@@ -12029,4 +13812,4 @@ var dotmem = {
|
|
|
12029
13812
|
};
|
|
12030
13813
|
var index_default = dotmem;
|
|
12031
13814
|
|
|
12032
|
-
export { CompactionEngine, ContextCompiler, ContextLifecycleOrchestrator, RecoveryService, addDecision, addOpenLoopRef, addRejectedApproach, addUnresolvedQuestion, appendReasoningStep, clearUserIdentity, closeConversationalState, compileContext, continuity, createCompactionEngine, createConversationalState, createNoOpHost, createRecoveryService, index_default as default, deleteConversationalState, dotmem, encryption, getCapabilities, getPlatform, idb, inspectMem, isBrowser, isBun, isDeno, isEdge, isNode, listUnfinishedConversations, loadAllConversationalStates, loadConversationalStateById, memory, recoverConversation, resolveUserAgentMemoryPath, resolveUserBrainPath, resolveUserContractPath, resolveUserDotMemPath, resolveUserExperiencePath, resolveUserIdentity, resolveUserProjectMemoryPath, resolveUserReadmePath, saveConversationalState, security, setUserIdentity, signMemPackage, updateConversationalState };
|
|
13815
|
+
export { CompactionEngine, ContextCompiler, ContextLifecycleOrchestrator, EPITEMIC_STATUSES2 as EPITEMIC_STATUSES, GOVERNANCE_POLICY_STATUSES, GOVERNANCE_ROLES, GovernancePolicyError, GovernancePolicyStore, GuardrailError, GuardrailService, MEMORY_CATEGORIES, RecoveryService, addDecision, addOpenLoopRef, addRejectedApproach, addUnresolvedQuestion, appendReasoningStep, canTransitionEpistemic, clearUserIdentity, closeConversationalState, compileContext, continuity, continuityLedger, createCompactionEngine, createConversationalState, createGuardrailService, createNoOpHost, createRecoveryService, index_default as default, deleteConversationalState, dotmem, encryption, getCapabilities, getPlatform, idb, inspectMem, isBrowser, isBun, isDeno, isEdge, isLegacyRecord, isNode, listUnfinishedConversations, loadAllConversationalStates, loadConversationalStateById, markDerivedStale, memory, normalizeLegacyRecord, recoverConversation, resolveUserAgentMemoryPath, resolveUserBrainPath, resolveUserContractPath, resolveUserDotMemPath, resolveUserExperiencePath, resolveUserIdentity, resolveUserProjectMemoryPath, resolveUserReadmePath, roleHasPermission, saveConversationalState, security, setUserIdentity, signMemPackage, updateConversationalState, validateDerivedLineage, validateEpistemicTransition, validatePolicyAuthority as validateGovernanceAuthority, validateMemoryItem, validateProvenance, validateRelationshipTargets, validateResolutionRequest };
|