dotmem 0.0.8 → 0.1.0
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 +13 -1
- package/DOTMEM.md +538 -539
- package/JOY.md +55 -0
- package/MEMORY.md +4 -4
- package/PACKAGE.md +1 -1
- package/README.md +251 -417
- package/agent.cjs +13 -0
- package/agent.js +13 -0
- package/bin/cli.cjs +5 -0
- package/bin/cli.js +5 -0
- package/bin/mcp.cjs +5 -0
- package/bin/mcp.js +5 -0
- package/cli.cjs +207 -61
- package/cli.js +207 -61
- package/global.cjs +206 -60
- package/global.js +206 -60
- package/index.cjs +1897 -68
- package/index.js +1869 -61
- package/llms.txt +94 -10
- package/mcp.cjs +207 -61
- package/mcp.js +207 -61
- package/package.json +1 -1
package/index.cjs
CHANGED
|
@@ -22,6 +22,18 @@ var __export = (target, all) => {
|
|
|
22
22
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
23
23
|
};
|
|
24
24
|
|
|
25
|
+
// suppress-idb-warning.ts
|
|
26
|
+
var orig;
|
|
27
|
+
var init_suppress_idb_warning = __esm({
|
|
28
|
+
"suppress-idb-warning.ts"() {
|
|
29
|
+
orig = console.warn.bind(console);
|
|
30
|
+
console.warn = (...args) => {
|
|
31
|
+
if (typeof args[0] === "string" && args[0].includes("IndexedDB is not available")) return;
|
|
32
|
+
orig(...args);
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
});
|
|
36
|
+
|
|
25
37
|
// core/env.ts
|
|
26
38
|
var DEV;
|
|
27
39
|
var init_env = __esm({
|
|
@@ -674,7 +686,7 @@ var init_detection_events = __esm({
|
|
|
674
686
|
});
|
|
675
687
|
|
|
676
688
|
// core/knowledge/policy.ts
|
|
677
|
-
var ENFORCEMENT_TIERS, POLICY_DIR_NAME, PolicyAuthorityError
|
|
689
|
+
var ENFORCEMENT_TIERS, POLICY_DIR_NAME, PolicyAuthorityError; exports.validateGovernanceAuthority = void 0; var TIER_ENFORCEMENT, TIER_ACTION, CONVERSATIONAL_OVERRIDE_KEYS, ConversationalOverrideRejectedError, PolicyPreconditionViolationError, assertNoConversationalOverride, findPolicyBypassKey, canonicalEvent, findFieldValue, evaluateCondition, validatePolicyAuditAuthority, authorityRelaxationPermitted, authorityRevocationPermitted, canonicalPolicyFingerprint, resolveOverrideState, resolveEffectivePolicies, evaluatePolicies, applyOverrideOverlay, stripNonAuthoritativeTierFields;
|
|
678
690
|
var init_policy = __esm({
|
|
679
691
|
"core/knowledge/policy.ts"() {
|
|
680
692
|
init_canonical_hash();
|
|
@@ -690,7 +702,7 @@ var init_policy = __esm({
|
|
|
690
702
|
this.name = "PolicyAuthorityError";
|
|
691
703
|
}
|
|
692
704
|
};
|
|
693
|
-
|
|
705
|
+
exports.validateGovernanceAuthority = /* @__PURE__ */ __name((value) => {
|
|
694
706
|
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
695
707
|
throw new PolicyAuthorityError("Policy authority requires an authority object");
|
|
696
708
|
}
|
|
@@ -825,17 +837,17 @@ var init_policy = __esm({
|
|
|
825
837
|
if (candidate.kind === "system") {
|
|
826
838
|
return { kind: "system", source: candidate.source.trim() };
|
|
827
839
|
}
|
|
828
|
-
return
|
|
840
|
+
return exports.validateGovernanceAuthority(candidate);
|
|
829
841
|
}, "validatePolicyAuditAuthority");
|
|
830
842
|
authorityRelaxationPermitted = /* @__PURE__ */ __name((targetAuthority, actorAuthority) => {
|
|
831
|
-
|
|
832
|
-
|
|
843
|
+
exports.validateGovernanceAuthority(targetAuthority);
|
|
844
|
+
exports.validateGovernanceAuthority(actorAuthority);
|
|
833
845
|
if (targetAuthority.kind === "HI") return actorAuthority.kind === "HI";
|
|
834
846
|
return actorAuthority.kind === "HI" || actorAuthority.kind === "operator";
|
|
835
847
|
}, "authorityRelaxationPermitted");
|
|
836
848
|
authorityRevocationPermitted = /* @__PURE__ */ __name((overrideAuthority, actorAuthority) => {
|
|
837
|
-
|
|
838
|
-
|
|
849
|
+
exports.validateGovernanceAuthority(overrideAuthority);
|
|
850
|
+
exports.validateGovernanceAuthority(actorAuthority);
|
|
839
851
|
if (actorAuthority.kind === "HI") return true;
|
|
840
852
|
return overrideAuthority.kind === "operator";
|
|
841
853
|
}, "authorityRevocationPermitted");
|
|
@@ -2238,7 +2250,7 @@ var init_policy_store = __esm({
|
|
|
2238
2250
|
resolveUserPolicyFilePath = /* @__PURE__ */ __name((namespace2) => joinPath(resolveUserPolicyDir2(), `${namespace2}.policy.json`), "resolveUserPolicyFilePath");
|
|
2239
2251
|
validatePolicyAuthority2 = /* @__PURE__ */ __name((value) => {
|
|
2240
2252
|
try {
|
|
2241
|
-
return
|
|
2253
|
+
return exports.validateGovernanceAuthority(value);
|
|
2242
2254
|
} catch (error) {
|
|
2243
2255
|
emitPolicyAuthorityDetection(value);
|
|
2244
2256
|
throw error instanceof PolicyStoreError ? error : new PolicyStoreError(error.message);
|
|
@@ -2414,6 +2426,7 @@ var init_policy_store = __esm({
|
|
|
2414
2426
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2415
2427
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2416
2428
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2429
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2417
2430
|
return built;
|
|
2418
2431
|
}, "validatePolicyAuditEntry");
|
|
2419
2432
|
validatePolicyDocument = /* @__PURE__ */ __name((value) => {
|
|
@@ -2491,6 +2504,7 @@ var init_policy_store = __esm({
|
|
|
2491
2504
|
if (entry.target !== void 0) built.target = entry.target;
|
|
2492
2505
|
if (entry.reason !== void 0) built.reason = entry.reason;
|
|
2493
2506
|
if (entry.detail !== void 0) built.detail = entry.detail;
|
|
2507
|
+
if (entry.mandatory !== void 0) built.mandatory = entry.mandatory;
|
|
2494
2508
|
return built;
|
|
2495
2509
|
}, "buildAuditEntry");
|
|
2496
2510
|
boundedAudit = /* @__PURE__ */ __name((entries2, overrides) => {
|
|
@@ -2503,9 +2517,10 @@ var init_policy_store = __esm({
|
|
|
2503
2517
|
for (let index = entries2.length - 1; index >= 0; index--) {
|
|
2504
2518
|
const entry = entries2[index];
|
|
2505
2519
|
const activeLinked = entry.target !== void 0 && activeOverrideIds.has(entry.target) && (entry.action === "override" || entry.action === "revoke");
|
|
2506
|
-
|
|
2520
|
+
const mandatory = entry.mandatory === true;
|
|
2521
|
+
if (activeLinked || mandatory || budget > 0) {
|
|
2507
2522
|
kept.unshift(entry);
|
|
2508
|
-
if (!activeLinked) budget--;
|
|
2523
|
+
if (!activeLinked && !mandatory) budget--;
|
|
2509
2524
|
}
|
|
2510
2525
|
}
|
|
2511
2526
|
return kept;
|
|
@@ -2546,6 +2561,12 @@ var init_policy_store = __esm({
|
|
|
2546
2561
|
try {
|
|
2547
2562
|
await fs.writeFile(temporary, `${JSON.stringify(document, null, 2)}
|
|
2548
2563
|
`, "utf8");
|
|
2564
|
+
const handle = await fs.open(temporary, "r+");
|
|
2565
|
+
try {
|
|
2566
|
+
await handle.sync();
|
|
2567
|
+
} finally {
|
|
2568
|
+
await handle.close();
|
|
2569
|
+
}
|
|
2549
2570
|
await fs.rename(temporary, filePath);
|
|
2550
2571
|
} catch (error) {
|
|
2551
2572
|
await fs.rm(temporary, { force: true }).catch(() => void 0);
|
|
@@ -4464,7 +4485,7 @@ function encodeMemFile(stateValue, preserved) {
|
|
|
4464
4485
|
function decodeMemDocument(value) {
|
|
4465
4486
|
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid .mem file format");
|
|
4466
4487
|
const file = value;
|
|
4467
|
-
if (file.format !== MEM_FORMAT) throw new Error("Invalid .mem file format");
|
|
4488
|
+
if (file.format !== MEM_FORMAT && file.format !== MEM_FORMAT_LEGACY) throw new Error("Invalid .mem file format");
|
|
4468
4489
|
if (file.version === MEM_FORMAT_VERSION_V1) {
|
|
4469
4490
|
if (!file.knowledge) throw new Error("Invalid .mem file format");
|
|
4470
4491
|
return { state: validateSourceState(file.knowledge) };
|
|
@@ -4546,11 +4567,12 @@ function decodeMemDocument(value) {
|
|
|
4546
4567
|
preserved: { map: structuredClone(map), data: structuredClone(stateRecord) }
|
|
4547
4568
|
};
|
|
4548
4569
|
}
|
|
4549
|
-
var MEM_FORMAT, MEM_FORMAT_VERSION, MEM_FORMAT_VERSION_V1, expectedFields, epistemicTypes, revisionKinds, claimKinds, isJsonValue2, createMemInterpretationMap, validateSourceState, trimRecord, fieldIndexes, validateVocabulary, put, get, optionalGet, requireRecord, putOptional;
|
|
4570
|
+
var MEM_FORMAT, MEM_FORMAT_LEGACY, MEM_FORMAT_VERSION, MEM_FORMAT_VERSION_V1, expectedFields, epistemicTypes, revisionKinds, claimKinds, isJsonValue2, createMemInterpretationMap, validateSourceState, trimRecord, fieldIndexes, validateVocabulary, put, get, optionalGet, requireRecord, putOptional;
|
|
4550
4571
|
var init_mem_codec = __esm({
|
|
4551
4572
|
"core/knowledge/mem-codec.ts"() {
|
|
4552
4573
|
init_state();
|
|
4553
4574
|
MEM_FORMAT = "dotmem.knowledge";
|
|
4575
|
+
MEM_FORMAT_LEGACY = "memorio.knowledge";
|
|
4554
4576
|
MEM_FORMAT_VERSION = 2;
|
|
4555
4577
|
MEM_FORMAT_VERSION_V1 = 1;
|
|
4556
4578
|
expectedFields = {
|
|
@@ -4729,12 +4751,13 @@ async function inspectMem(input) {
|
|
|
4729
4751
|
return { status: artifactError.code, representation: "unknown", message: artifactError.message, parts: [] };
|
|
4730
4752
|
}
|
|
4731
4753
|
}
|
|
4732
|
-
var MEM_PACKAGE_FORMAT, MEM_PACKAGE_VERSION, MEM_PACKAGE_LIMITS, MEM_PACKAGE_WRITE_MARGIN_BYTES, MEM_PACKAGE_WRITE_LIMITS, MANIFEST_PATH, MAP_PATH, DATA_PATH, INTEGRITY_PATH, requiredParts, encoder2, decoder, MemArtifactError, stableValue, jsonBytes, sha256, parseJson, validEntryPath, concatenate, readZipParts, createManifest, digestTableIdentity, signaturePayload, base64UrlEncode, base64UrlDecode, keyIdentity, signaturePath, encodePackage, isZip, decodePackage, verifySignature;
|
|
4754
|
+
var MEM_PACKAGE_FORMAT, MEM_PACKAGE_FORMAT_LEGACY, MEM_PACKAGE_VERSION, MEM_PACKAGE_LIMITS, MEM_PACKAGE_WRITE_MARGIN_BYTES, MEM_PACKAGE_WRITE_LIMITS, MANIFEST_PATH, MAP_PATH, DATA_PATH, INTEGRITY_PATH, requiredParts, encoder2, decoder, MemArtifactError, stableValue, jsonBytes, sha256, parseJson, validEntryPath, concatenate, readZipParts, createManifest, digestTableIdentity, signaturePayload, base64UrlEncode, base64UrlDecode, keyIdentity, signaturePath, encodePackage, isZip, decodePackage, verifySignature;
|
|
4733
4755
|
var init_mem_package = __esm({
|
|
4734
4756
|
"core/knowledge/mem-package.ts"() {
|
|
4735
4757
|
init_browser();
|
|
4736
4758
|
init_mem_codec();
|
|
4737
4759
|
MEM_PACKAGE_FORMAT = "dotmem.package";
|
|
4760
|
+
MEM_PACKAGE_FORMAT_LEGACY = "memorio.package";
|
|
4738
4761
|
MEM_PACKAGE_VERSION = 1;
|
|
4739
4762
|
MEM_PACKAGE_LIMITS = Object.freeze({
|
|
4740
4763
|
maximumPackageBytes: 10 * 1024 * 1024,
|
|
@@ -4925,10 +4948,10 @@ ${contentId}
|
|
|
4925
4948
|
if (!parts[path]) throw new MemArtifactError("invalid", `Missing MEM package part: ${path}`);
|
|
4926
4949
|
}
|
|
4927
4950
|
const manifest = parseJson(parts[MANIFEST_PATH], MANIFEST_PATH);
|
|
4928
|
-
if (manifest.format !== MEM_PACKAGE_FORMAT && manifest.format || manifest.version !== MEM_PACKAGE_VERSION) {
|
|
4951
|
+
if (manifest.format !== MEM_PACKAGE_FORMAT && manifest.format !== MEM_PACKAGE_FORMAT_LEGACY || manifest.version !== MEM_PACKAGE_VERSION) {
|
|
4929
4952
|
throw new MemArtifactError("unsupported", "Unsupported MEM package format or version");
|
|
4930
4953
|
}
|
|
4931
|
-
if (manifest.memory?.format !== MEM_FORMAT && manifest.memory?.format || manifest.memory.version !== MEM_FORMAT_VERSION || manifest.parts?.map !== MAP_PATH || manifest.parts?.data !== DATA_PATH || manifest.parts?.integrity !== INTEGRITY_PATH) {
|
|
4954
|
+
if (manifest.memory?.format !== MEM_FORMAT && manifest.memory?.format !== MEM_FORMAT_LEGACY || manifest.memory.version !== MEM_FORMAT_VERSION || manifest.parts?.map !== MAP_PATH || manifest.parts?.data !== DATA_PATH || manifest.parts?.integrity !== INTEGRITY_PATH) {
|
|
4932
4955
|
throw new MemArtifactError("invalid", "Invalid MEM package manifest");
|
|
4933
4956
|
}
|
|
4934
4957
|
const integrity = parseJson(parts[INTEGRITY_PATH], INTEGRITY_PATH);
|
|
@@ -5040,6 +5063,109 @@ ${contentId}
|
|
|
5040
5063
|
}
|
|
5041
5064
|
});
|
|
5042
5065
|
|
|
5066
|
+
// core/storage-coordinator/index.ts
|
|
5067
|
+
var nodeFs4, withStorageLock, readStorageGeneration, writeStorageGeneration;
|
|
5068
|
+
var init_storage_coordinator = __esm({
|
|
5069
|
+
"core/storage-coordinator/index.ts"() {
|
|
5070
|
+
nodeFs4 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
5071
|
+
withStorageLock = /* @__PURE__ */ __name(async (directoryPath, operationOrOptions, operation, _options = {}) => {
|
|
5072
|
+
const opts = typeof operationOrOptions === "function" ? operation ?? {} : operationOrOptions ?? {};
|
|
5073
|
+
const op = typeof operationOrOptions === "function" ? operationOrOptions : operation;
|
|
5074
|
+
const timeoutMs = opts.timeoutMs ?? 3e4;
|
|
5075
|
+
const staleMs = opts.staleMs ?? 6e4;
|
|
5076
|
+
const lockName = opts.lockName ?? ".storage.lock";
|
|
5077
|
+
const lockPath = `${directoryPath}/${lockName}`;
|
|
5078
|
+
const fs = await nodeFs4();
|
|
5079
|
+
const started = Date.now();
|
|
5080
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
5081
|
+
while (true) {
|
|
5082
|
+
try {
|
|
5083
|
+
await fs.mkdir(lockPath);
|
|
5084
|
+
const token2 = `${process.pid}-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
|
5085
|
+
await fs.writeFile(`${lockPath}/owner.json`, JSON.stringify({ token: token2, pid: process.pid, createdAt: Date.now() }), { flag: "wx" });
|
|
5086
|
+
break;
|
|
5087
|
+
} catch (error) {
|
|
5088
|
+
const code = error.code;
|
|
5089
|
+
if (code !== "EEXIST" && code !== "ENOENT" && code !== "EPERM" && code !== "ENOTEMPTY") throw error;
|
|
5090
|
+
let stale = false;
|
|
5091
|
+
let processDead = false;
|
|
5092
|
+
try {
|
|
5093
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5094
|
+
const parsed = JSON.parse(raw);
|
|
5095
|
+
const ageStale = typeof parsed.createdAt === "number" && Date.now() - parsed.createdAt > staleMs;
|
|
5096
|
+
if (typeof parsed.pid === "number") {
|
|
5097
|
+
try {
|
|
5098
|
+
process.kill(parsed.pid, 0);
|
|
5099
|
+
} catch {
|
|
5100
|
+
processDead = true;
|
|
5101
|
+
}
|
|
5102
|
+
} else {
|
|
5103
|
+
processDead = true;
|
|
5104
|
+
}
|
|
5105
|
+
if (ageStale || processDead) stale = true;
|
|
5106
|
+
} catch {
|
|
5107
|
+
stale = true;
|
|
5108
|
+
}
|
|
5109
|
+
if (stale) {
|
|
5110
|
+
if (processDead) {
|
|
5111
|
+
try {
|
|
5112
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
5113
|
+
continue;
|
|
5114
|
+
} catch {
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
try {
|
|
5118
|
+
await fs.rmdir(lockPath);
|
|
5119
|
+
continue;
|
|
5120
|
+
} catch {
|
|
5121
|
+
}
|
|
5122
|
+
}
|
|
5123
|
+
if (Date.now() - started > timeoutMs) {
|
|
5124
|
+
throw new Error(`Storage lock timeout for ${directoryPath}`);
|
|
5125
|
+
}
|
|
5126
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
5127
|
+
}
|
|
5128
|
+
}
|
|
5129
|
+
let token = "";
|
|
5130
|
+
try {
|
|
5131
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5132
|
+
token = JSON.parse(raw).token ?? "";
|
|
5133
|
+
} catch {
|
|
5134
|
+
token = "";
|
|
5135
|
+
}
|
|
5136
|
+
try {
|
|
5137
|
+
return await op();
|
|
5138
|
+
} finally {
|
|
5139
|
+
try {
|
|
5140
|
+
const raw = await fs.readFile(`${lockPath}/owner.json`, "utf8");
|
|
5141
|
+
const parsed = JSON.parse(raw);
|
|
5142
|
+
if (parsed.token === token && token) {
|
|
5143
|
+
await fs.rm(lockPath, { recursive: true, force: true });
|
|
5144
|
+
}
|
|
5145
|
+
} catch {
|
|
5146
|
+
}
|
|
5147
|
+
}
|
|
5148
|
+
}, "withStorageLock");
|
|
5149
|
+
readStorageGeneration = /* @__PURE__ */ __name(async (directoryPath) => {
|
|
5150
|
+
const fs = await nodeFs4();
|
|
5151
|
+
try {
|
|
5152
|
+
const raw = await fs.readFile(`${directoryPath}/.storage.generation`, "utf8");
|
|
5153
|
+
const value = Number(raw.trim());
|
|
5154
|
+
return Number.isFinite(value) && value >= 0 ? value : 0;
|
|
5155
|
+
} catch {
|
|
5156
|
+
return 0;
|
|
5157
|
+
}
|
|
5158
|
+
}, "readStorageGeneration");
|
|
5159
|
+
writeStorageGeneration = /* @__PURE__ */ __name(async (directoryPath, value) => {
|
|
5160
|
+
const fs = await nodeFs4();
|
|
5161
|
+
await fs.mkdir(directoryPath, { recursive: true });
|
|
5162
|
+
const temp = `${directoryPath}/.storage.generation.${process.pid}.${Math.random().toString(36).slice(2)}.tmp`;
|
|
5163
|
+
await fs.writeFile(temp, String(value));
|
|
5164
|
+
await fs.rename(temp, `${directoryPath}/.storage.generation`);
|
|
5165
|
+
}, "writeStorageGeneration");
|
|
5166
|
+
}
|
|
5167
|
+
});
|
|
5168
|
+
|
|
5043
5169
|
// core/knowledge/mem-repository.ts
|
|
5044
5170
|
function resolveDuplicate(existing, existingSource, current, currentSource) {
|
|
5045
5171
|
const existingRetracts = existing.revision?.kind === "retracts";
|
|
@@ -5080,19 +5206,20 @@ async function parseMemFile(value, sourceId) {
|
|
|
5080
5206
|
throw new Error(`${message} in ${sourceId}`);
|
|
5081
5207
|
}
|
|
5082
5208
|
}
|
|
5083
|
-
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE,
|
|
5209
|
+
var DOTMEM_DIRECTORY, DEFAULT_MEM_FILE, nodeFs5, repositoryLoadCaches, separator2, normalizePath, isAbsolutePath, resolvePath, joinPath2, parentPath, uniqueSuffix, isMissingDirectory, sourcePriority, memFilePath, MemDirectoryKnowledgeRepository;
|
|
5084
5210
|
var init_mem_repository = __esm({
|
|
5085
5211
|
"core/knowledge/mem-repository.ts"() {
|
|
5086
5212
|
init_mem_codec();
|
|
5087
5213
|
init_mem_package();
|
|
5088
5214
|
init_state();
|
|
5089
5215
|
init_acquisition_limits();
|
|
5216
|
+
init_storage_coordinator();
|
|
5090
5217
|
DOTMEM_DIRECTORY = ".dotmem";
|
|
5091
5218
|
DEFAULT_MEM_FILE = "project.mem";
|
|
5092
|
-
|
|
5093
|
-
nodeFs4 = /* @__PURE__ */ __name(async () => {
|
|
5219
|
+
nodeFs5 = /* @__PURE__ */ __name(async () => {
|
|
5094
5220
|
return import('fs/promises');
|
|
5095
5221
|
}, "nodeFs");
|
|
5222
|
+
repositoryLoadCaches = /* @__PURE__ */ new Map();
|
|
5096
5223
|
separator2 = /* @__PURE__ */ __name(() => process.platform === "win32" ? "\\" : "/", "separator");
|
|
5097
5224
|
normalizePath = /* @__PURE__ */ __name((value) => value.replace(/[\\/]+/g, separator2()), "normalizePath");
|
|
5098
5225
|
isAbsolutePath = /* @__PURE__ */ __name((value) => /^(?:[A-Za-z]:[\\/]|[\\/])/.test(value), "isAbsolutePath");
|
|
@@ -5104,25 +5231,8 @@ var init_mem_repository = __esm({
|
|
|
5104
5231
|
return index <= 0 ? "." : normalized.slice(0, index);
|
|
5105
5232
|
}, "parentPath");
|
|
5106
5233
|
uniqueSuffix = /* @__PURE__ */ __name(() => globalThis.crypto?.randomUUID?.() ?? `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`, "uniqueSuffix");
|
|
5107
|
-
withDirectoryLock = /* @__PURE__ */ __name(async (directoryPath, operation) => {
|
|
5108
|
-
const previous = directoryLocks.get(directoryPath) ?? Promise.resolve();
|
|
5109
|
-
let release = /* @__PURE__ */ __name(() => void 0, "release");
|
|
5110
|
-
const gate = new Promise((resolveGate) => {
|
|
5111
|
-
release = resolveGate;
|
|
5112
|
-
});
|
|
5113
|
-
const queued = previous.then(() => gate);
|
|
5114
|
-
directoryLocks.set(directoryPath, queued);
|
|
5115
|
-
await previous;
|
|
5116
|
-
try {
|
|
5117
|
-
return await operation();
|
|
5118
|
-
} finally {
|
|
5119
|
-
release();
|
|
5120
|
-
if (directoryLocks.get(directoryPath) === queued) directoryLocks.delete(directoryPath);
|
|
5121
|
-
}
|
|
5122
|
-
}, "withDirectoryLock");
|
|
5123
5234
|
isMissingDirectory = /* @__PURE__ */ __name((error) => error.code === "ENOENT", "isMissingDirectory");
|
|
5124
5235
|
sourcePriority = /* @__PURE__ */ __name((sourceId, scope) => {
|
|
5125
|
-
if (sourceId.endsWith(".mem") && sourceId !== "project.mem" && sourceId !== "user.mem" && sourceId !== "agent.mem") return 0;
|
|
5126
5236
|
if (scope === "project" || sourceId === "project.mem") return 4;
|
|
5127
5237
|
if (scope === "user" || sourceId === "user.mem") return 3;
|
|
5128
5238
|
if (sourceId === "agent.mem" || sourceId === "agent") return 2;
|
|
@@ -5150,7 +5260,7 @@ var init_mem_repository = __esm({
|
|
|
5150
5260
|
this.loadWritableOnly = loadWritableOnly;
|
|
5151
5261
|
}
|
|
5152
5262
|
async discover() {
|
|
5153
|
-
const { readdir } = await
|
|
5263
|
+
const { readdir } = await nodeFs5();
|
|
5154
5264
|
try {
|
|
5155
5265
|
const entries2 = await readdir(this.directoryPath, { withFileTypes: true });
|
|
5156
5266
|
return entries2.filter((entry) => entry.isFile() && entry.name.endsWith(".mem") && entry.name !== "@brain.mem").map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
@@ -5165,7 +5275,7 @@ var init_mem_repository = __esm({
|
|
|
5165
5275
|
* receive a clear failure instead of discovering corruption on first use.
|
|
5166
5276
|
*/
|
|
5167
5277
|
async initialize() {
|
|
5168
|
-
const { mkdir } = await
|
|
5278
|
+
const { mkdir } = await nodeFs5();
|
|
5169
5279
|
await mkdir(this.directoryPath, { recursive: true });
|
|
5170
5280
|
await this.load();
|
|
5171
5281
|
}
|
|
@@ -5180,7 +5290,7 @@ var init_mem_repository = __esm({
|
|
|
5180
5290
|
* that does not delete is not a delete.
|
|
5181
5291
|
*/
|
|
5182
5292
|
async clearWritable() {
|
|
5183
|
-
const { rm, readdir } = await
|
|
5293
|
+
const { rm, readdir } = await nodeFs5();
|
|
5184
5294
|
const filePath = memFilePath(this.directoryPath, this.writableSourceId);
|
|
5185
5295
|
await rm(filePath, { force: true });
|
|
5186
5296
|
await rm(`${filePath}.bak`, { force: true });
|
|
@@ -5197,15 +5307,28 @@ var init_mem_repository = __esm({
|
|
|
5197
5307
|
}
|
|
5198
5308
|
}
|
|
5199
5309
|
async load() {
|
|
5200
|
-
const
|
|
5201
|
-
|
|
5310
|
+
const cacheKey = `${this.directoryPath}:${this.writableSourceId}:${this.loadWritableOnly}`;
|
|
5311
|
+
for (let attempt = 0; attempt < 5; attempt += 1) {
|
|
5312
|
+
const generationBefore = await readStorageGeneration(this.directoryPath);
|
|
5313
|
+
const cached = repositoryLoadCaches.get(cacheKey);
|
|
5314
|
+
if (cached && cached.signature === String(generationBefore)) {
|
|
5315
|
+
return structuredClone(cached.state);
|
|
5316
|
+
}
|
|
5317
|
+
const loaded = this.loadWritableOnly ? await this.loadSources([this.writableSourceId]) : await this.loadSources();
|
|
5318
|
+
const state3 = this.combine(loaded);
|
|
5319
|
+
const generationAfter = await readStorageGeneration(this.directoryPath);
|
|
5320
|
+
if (generationBefore !== generationAfter && attempt < 4) continue;
|
|
5321
|
+
repositoryLoadCaches.set(cacheKey, { signature: String(generationAfter), state: structuredClone(state3) });
|
|
5322
|
+
return state3;
|
|
5323
|
+
}
|
|
5324
|
+
throw new Error("Unable to load stable repository state");
|
|
5202
5325
|
}
|
|
5203
|
-
async save(state3, expectedVersion) {
|
|
5326
|
+
async save(state3, expectedVersion, locked = false) {
|
|
5204
5327
|
const validated = validateKnowledgeState(state3);
|
|
5205
5328
|
if (!Number.isInteger(expectedVersion) || expectedVersion < 0 || validated.version !== expectedVersion + 1) {
|
|
5206
5329
|
throw new Error("Invalid expected persisted version");
|
|
5207
5330
|
}
|
|
5208
|
-
|
|
5331
|
+
const doWrite = /* @__PURE__ */ __name(async () => {
|
|
5209
5332
|
const loaded = await this.loadSources();
|
|
5210
5333
|
const writable = loaded.find((source) => source.id === this.writableSourceId) ?? {
|
|
5211
5334
|
id: this.writableSourceId,
|
|
@@ -5227,10 +5350,16 @@ var init_mem_repository = __esm({
|
|
|
5227
5350
|
claims: [...writable.state.claims, ...newClaims]
|
|
5228
5351
|
};
|
|
5229
5352
|
await this.writeSource(writable.filePath, nextWritable, writable.preserved, writable.package);
|
|
5230
|
-
|
|
5353
|
+
await writeStorageGeneration(this.directoryPath, await readStorageGeneration(this.directoryPath) + 1);
|
|
5354
|
+
}, "doWrite");
|
|
5355
|
+
if (locked) {
|
|
5356
|
+
await doWrite();
|
|
5357
|
+
} else {
|
|
5358
|
+
await withStorageLock(this.directoryPath, doWrite);
|
|
5359
|
+
}
|
|
5231
5360
|
}
|
|
5232
5361
|
async loadSources(sourceIds) {
|
|
5233
|
-
const { readFile: readFile5 } = await
|
|
5362
|
+
const { readFile: readFile5 } = await nodeFs5();
|
|
5234
5363
|
const ids = sourceIds ?? await this.discover();
|
|
5235
5364
|
const sources = [];
|
|
5236
5365
|
for (const sourceId of ids) {
|
|
@@ -5320,7 +5449,7 @@ var init_mem_repository = __esm({
|
|
|
5320
5449
|
* valid package, so the backup is always a version this library can read.
|
|
5321
5450
|
*/
|
|
5322
5451
|
async keepLastValidVersion(filePath) {
|
|
5323
|
-
const { readFile: readFile5, copyFile, rename, rm } = await
|
|
5452
|
+
const { readFile: readFile5, copyFile, rename, rm } = await nodeFs5();
|
|
5324
5453
|
let current;
|
|
5325
5454
|
try {
|
|
5326
5455
|
current = await readFile5(filePath);
|
|
@@ -5344,7 +5473,7 @@ var init_mem_repository = __esm({
|
|
|
5344
5473
|
}
|
|
5345
5474
|
/** Best-effort directory sync: the rename is durable once the directory is. */
|
|
5346
5475
|
async syncDirectory(directoryPath) {
|
|
5347
|
-
const { open } = await
|
|
5476
|
+
const { open } = await nodeFs5();
|
|
5348
5477
|
try {
|
|
5349
5478
|
const handle = await open(directoryPath, "r");
|
|
5350
5479
|
try {
|
|
@@ -5356,7 +5485,7 @@ var init_mem_repository = __esm({
|
|
|
5356
5485
|
}
|
|
5357
5486
|
}
|
|
5358
5487
|
async writeSource(filePath, state3, preserved, preservedPackage) {
|
|
5359
|
-
const { mkdir, rename, rm, open, readFile: readFile5 } = await
|
|
5488
|
+
const { mkdir, rename, rm, open, readFile: readFile5 } = await nodeFs5();
|
|
5360
5489
|
if (!Number.isInteger(state3.version) || state3.version < 0) throw new Error("Invalid writable .mem knowledge version");
|
|
5361
5490
|
for (const claim of state3.claims) validateClaim(claim);
|
|
5362
5491
|
await mkdir(parentPath(filePath), { recursive: true });
|
|
@@ -5884,7 +6013,7 @@ var init_project_identity = __esm({
|
|
|
5884
6013
|
});
|
|
5885
6014
|
|
|
5886
6015
|
// core/knowledge/staging.ts
|
|
5887
|
-
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor,
|
|
6016
|
+
var DOTMEM_STAGE_FORMAT, DOTMEM_STAGE_VERSION, StageFileError, generateRequestId, nodeCwd, resolveStagingDirectory, stagePathFor, nodeFs6, normalizeClaimIds, revisionFromOptions, writeStageFile, readStageFile, validateStageFile, commitStageFile, cleanupStageFile;
|
|
5888
6017
|
var init_staging = __esm({
|
|
5889
6018
|
"core/knowledge/staging.ts"() {
|
|
5890
6019
|
init_claim_record();
|
|
@@ -5919,7 +6048,7 @@ var init_staging = __esm({
|
|
|
5919
6048
|
return `${base}/dotmem`;
|
|
5920
6049
|
}, "resolveStagingDirectory");
|
|
5921
6050
|
stagePathFor = /* @__PURE__ */ __name((requestId) => `${resolveStagingDirectory()}/${requestId}.stage`, "stagePathFor");
|
|
5922
|
-
|
|
6051
|
+
nodeFs6 = /* @__PURE__ */ __name(async () => import('fs/promises'), "nodeFs");
|
|
5923
6052
|
normalizeClaimIds = /* @__PURE__ */ __name((value) => {
|
|
5924
6053
|
if (!value) return [];
|
|
5925
6054
|
return Array.isArray(value) ? value : [value];
|
|
@@ -5944,7 +6073,7 @@ var init_staging = __esm({
|
|
|
5944
6073
|
};
|
|
5945
6074
|
const requestId = generateRequestId();
|
|
5946
6075
|
const stagePath = stagePathFor(requestId);
|
|
5947
|
-
const fs = await
|
|
6076
|
+
const fs = await nodeFs6();
|
|
5948
6077
|
await fs.mkdir(resolveStagingDirectory(), { recursive: true });
|
|
5949
6078
|
const stageFile = {
|
|
5950
6079
|
format: DOTMEM_STAGE_FORMAT,
|
|
@@ -5964,7 +6093,7 @@ var init_staging = __esm({
|
|
|
5964
6093
|
}, "writeStageFile");
|
|
5965
6094
|
readStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
5966
6095
|
const stagePath = stagePathFor(requestId);
|
|
5967
|
-
const fs = await
|
|
6096
|
+
const fs = await nodeFs6();
|
|
5968
6097
|
try {
|
|
5969
6098
|
const content = await fs.readFile(stagePath, "utf8");
|
|
5970
6099
|
return JSON.parse(content);
|
|
@@ -6075,7 +6204,7 @@ var init_staging = __esm({
|
|
|
6075
6204
|
}, "commitStageFile");
|
|
6076
6205
|
cleanupStageFile = /* @__PURE__ */ __name(async (requestId) => {
|
|
6077
6206
|
const stagePath = stagePathFor(requestId);
|
|
6078
|
-
const fs = await
|
|
6207
|
+
const fs = await nodeFs6();
|
|
6079
6208
|
try {
|
|
6080
6209
|
await fs.unlink(stagePath);
|
|
6081
6210
|
} catch (error) {
|
|
@@ -6846,6 +6975,7 @@ function _inferConfidence(claim) {
|
|
|
6846
6975
|
var LOGIC_MEM_FILE, nodeCwd2, cachedRuntime, LogicEngine, logic, logic_default;
|
|
6847
6976
|
var init_logic = __esm({
|
|
6848
6977
|
"core/logic/index.ts"() {
|
|
6978
|
+
init_suppress_idb_warning();
|
|
6849
6979
|
init_knowledge();
|
|
6850
6980
|
init_mem_repository();
|
|
6851
6981
|
init_acquisition_boundary();
|
|
@@ -7113,6 +7243,9 @@ Do not store secrets unless dotmem explicitly supports secure secret storage.
|
|
|
7113
7243
|
}
|
|
7114
7244
|
});
|
|
7115
7245
|
|
|
7246
|
+
// index.ts
|
|
7247
|
+
init_suppress_idb_warning();
|
|
7248
|
+
|
|
7116
7249
|
// core/idb.ts
|
|
7117
7250
|
var idb = {
|
|
7118
7251
|
db: {},
|
|
@@ -7452,7 +7585,11 @@ var security = {
|
|
|
7452
7585
|
createSanitizer
|
|
7453
7586
|
};
|
|
7454
7587
|
|
|
7588
|
+
// core/memory/index.ts
|
|
7589
|
+
init_suppress_idb_warning();
|
|
7590
|
+
|
|
7455
7591
|
// core/memory/journal.ts
|
|
7592
|
+
init_suppress_idb_warning();
|
|
7456
7593
|
init_env();
|
|
7457
7594
|
var g = globalThis;
|
|
7458
7595
|
function namespace() {
|
|
@@ -7659,6 +7796,7 @@ var journal_default = journal;
|
|
|
7659
7796
|
init_brain();
|
|
7660
7797
|
|
|
7661
7798
|
// core/memory/knowledge-runtime.ts
|
|
7799
|
+
init_suppress_idb_warning();
|
|
7662
7800
|
init_knowledge();
|
|
7663
7801
|
init_project_identity();
|
|
7664
7802
|
init_user_paths();
|
|
@@ -8007,6 +8145,7 @@ var policy = {
|
|
|
8007
8145
|
};
|
|
8008
8146
|
|
|
8009
8147
|
// core/memory/entries.ts
|
|
8148
|
+
init_suppress_idb_warning();
|
|
8010
8149
|
init_env();
|
|
8011
8150
|
var MEMORY_INDEX_KEY = "__dotmem_memory_index__";
|
|
8012
8151
|
var SUPERSEDED_PREFIX = "__dotmem_superseded__";
|
|
@@ -8551,15 +8690,18 @@ var context = {
|
|
|
8551
8690
|
};
|
|
8552
8691
|
|
|
8553
8692
|
// core/memory/acquire.ts
|
|
8693
|
+
init_suppress_idb_warning();
|
|
8554
8694
|
init_env();
|
|
8555
8695
|
init_project_identity();
|
|
8556
8696
|
init_project_anchor();
|
|
8557
8697
|
init_acquisition_boundary();
|
|
8558
8698
|
init_claim_record();
|
|
8699
|
+
init_state();
|
|
8559
8700
|
init_router();
|
|
8560
8701
|
init_store_queue();
|
|
8561
8702
|
init_staging();
|
|
8562
8703
|
init_user_paths();
|
|
8704
|
+
init_storage_coordinator();
|
|
8563
8705
|
init_mem_repository();
|
|
8564
8706
|
function normalizeClaimIds3(value) {
|
|
8565
8707
|
if (!value) return [];
|
|
@@ -8637,23 +8779,27 @@ var acquire = {
|
|
|
8637
8779
|
);
|
|
8638
8780
|
}
|
|
8639
8781
|
const revision = revisionFromAcquire2(opts);
|
|
8782
|
+
const repository = target.repository;
|
|
8640
8783
|
await withStoreQueue(acquisitionStoreKey(kind, scope, sourceProject), async () => {
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
|
|
8644
|
-
|
|
8645
|
-
|
|
8646
|
-
|
|
8647
|
-
|
|
8648
|
-
|
|
8649
|
-
|
|
8650
|
-
|
|
8651
|
-
|
|
8652
|
-
|
|
8653
|
-
|
|
8654
|
-
|
|
8655
|
-
|
|
8656
|
-
|
|
8784
|
+
await withStorageLock(repository.directoryPath, async () => {
|
|
8785
|
+
const state3 = await repository.load();
|
|
8786
|
+
const next = updateKnowledge(state3, {
|
|
8787
|
+
basedOnVersion: state3.version,
|
|
8788
|
+
claims: [buildClaimRecord({
|
|
8789
|
+
id: opts.id,
|
|
8790
|
+
knowledge: boundary.knowledge,
|
|
8791
|
+
applicability: opts.applicability,
|
|
8792
|
+
evidence: opts.evidence,
|
|
8793
|
+
epistemicType: opts.epistemicType,
|
|
8794
|
+
provenance: opts.provenance,
|
|
8795
|
+
acquiredAt: opts.acquiredAt,
|
|
8796
|
+
revision,
|
|
8797
|
+
scope,
|
|
8798
|
+
sourceProject,
|
|
8799
|
+
kind
|
|
8800
|
+
})]
|
|
8801
|
+
});
|
|
8802
|
+
await repository.save(next, state3.version, true);
|
|
8657
8803
|
});
|
|
8658
8804
|
});
|
|
8659
8805
|
const verification = await target.repository.load();
|
|
@@ -10194,6 +10340,442 @@ var saveContinuityDocument = /* @__PURE__ */ __name(async (path, document) => {
|
|
|
10194
10340
|
}
|
|
10195
10341
|
}, "saveContinuityDocument");
|
|
10196
10342
|
|
|
10343
|
+
// core/continuity-ledger/index.ts
|
|
10344
|
+
var EVENT_ID_PREFIX = "evt.";
|
|
10345
|
+
var LEDGER_APPLICABILITY = { area: "continuity", baseline: false };
|
|
10346
|
+
var EVENT_TYPES = /* @__PURE__ */ new Set([
|
|
10347
|
+
"decision",
|
|
10348
|
+
"discovery",
|
|
10349
|
+
"correction",
|
|
10350
|
+
"conversation",
|
|
10351
|
+
"commitment",
|
|
10352
|
+
"open-loop",
|
|
10353
|
+
"project-change",
|
|
10354
|
+
"activity"
|
|
10355
|
+
]);
|
|
10356
|
+
var MEMORY_DOMAINS = /* @__PURE__ */ new Set(["environmental", "professional", "conversational", "activity"]);
|
|
10357
|
+
var EPITEMIC_STATUSES = /* @__PURE__ */ new Set([
|
|
10358
|
+
"verified-fact",
|
|
10359
|
+
"user-statement",
|
|
10360
|
+
"ai-inference",
|
|
10361
|
+
"hypothesis",
|
|
10362
|
+
"proposal",
|
|
10363
|
+
"decision",
|
|
10364
|
+
"verified-result"
|
|
10365
|
+
]);
|
|
10366
|
+
var CONFIDENCE_VALUES = /* @__PURE__ */ new Set(["high", "medium", "low", "unknown"]);
|
|
10367
|
+
var AUTHORS = /* @__PURE__ */ new Set(["human", "ai", "system"]);
|
|
10368
|
+
var CHANNELS = /* @__PURE__ */ new Set(["conversation", "acquisition", "tool", "migration"]);
|
|
10369
|
+
var SOURCE_KINDS = /* @__PURE__ */ new Set(["claim", "file", "commit", "tool-output", "conversation"]);
|
|
10370
|
+
var RELATIONSHIP_KEYS = [
|
|
10371
|
+
"refines",
|
|
10372
|
+
"supersedes",
|
|
10373
|
+
"retracts",
|
|
10374
|
+
"dependsOn",
|
|
10375
|
+
"resolves",
|
|
10376
|
+
"relatedTo"
|
|
10377
|
+
];
|
|
10378
|
+
var isRecord = /* @__PURE__ */ __name((value) => !!value && typeof value === "object" && !Array.isArray(value), "isRecord");
|
|
10379
|
+
var normalizeRelationships = /* @__PURE__ */ __name((value) => {
|
|
10380
|
+
const result = {
|
|
10381
|
+
refines: [],
|
|
10382
|
+
supersedes: [],
|
|
10383
|
+
retracts: [],
|
|
10384
|
+
dependsOn: [],
|
|
10385
|
+
resolves: [],
|
|
10386
|
+
relatedTo: []
|
|
10387
|
+
};
|
|
10388
|
+
if (!isRecord(value)) return result;
|
|
10389
|
+
for (const key of RELATIONSHIP_KEYS) {
|
|
10390
|
+
const raw = value[key];
|
|
10391
|
+
if (Array.isArray(raw)) {
|
|
10392
|
+
result[key] = raw.filter((item) => typeof item === "string");
|
|
10393
|
+
}
|
|
10394
|
+
}
|
|
10395
|
+
return result;
|
|
10396
|
+
}, "normalizeRelationships");
|
|
10397
|
+
var validateEvent = /* @__PURE__ */ __name((event) => {
|
|
10398
|
+
if (!isRecord(event)) throw new Error("Continuity event must be an object");
|
|
10399
|
+
if (event.schemaVersion !== 1) throw new Error("Unsupported continuity event schemaVersion");
|
|
10400
|
+
if (typeof event.eventId !== "string" || !event.eventId.startsWith(EVENT_ID_PREFIX)) {
|
|
10401
|
+
throw new Error("Continuity event requires a valid eventId");
|
|
10402
|
+
}
|
|
10403
|
+
if (typeof event.eventType !== "string" || !EVENT_TYPES.has(event.eventType)) {
|
|
10404
|
+
throw new Error("Continuity event requires a valid eventType");
|
|
10405
|
+
}
|
|
10406
|
+
if (typeof event.memoryDomain !== "string" || !MEMORY_DOMAINS.has(event.memoryDomain)) {
|
|
10407
|
+
throw new Error("Continuity event requires a valid memoryDomain");
|
|
10408
|
+
}
|
|
10409
|
+
if (typeof event.epistemicStatus !== "string" || !EPITEMIC_STATUSES.has(event.epistemicStatus)) {
|
|
10410
|
+
throw new Error("Continuity event requires a valid epistemicStatus");
|
|
10411
|
+
}
|
|
10412
|
+
if (typeof event.confidence !== "string" || !CONFIDENCE_VALUES.has(event.confidence)) {
|
|
10413
|
+
throw new Error("Continuity event requires a valid confidence");
|
|
10414
|
+
}
|
|
10415
|
+
if (typeof event.createdAt !== "string") throw new Error("Continuity event requires createdAt");
|
|
10416
|
+
if (typeof event.sessionId !== "string") throw new Error("Continuity event requires sessionId");
|
|
10417
|
+
if (event.projectId !== null && typeof event.projectId !== "string") {
|
|
10418
|
+
throw new Error("Continuity event requires projectId to be string or null");
|
|
10419
|
+
}
|
|
10420
|
+
if (typeof event.subject !== "string") throw new Error("Continuity event requires subject");
|
|
10421
|
+
if (!isRecord(event.content)) throw new Error("Continuity event requires content object");
|
|
10422
|
+
if (!Array.isArray(event.sourceRefs)) throw new Error("Continuity event requires sourceRefs array");
|
|
10423
|
+
for (const ref of event.sourceRefs) {
|
|
10424
|
+
if (!isRecord(ref) || typeof ref.kind !== "string" || !SOURCE_KINDS.has(ref.kind) || typeof ref.id !== "string") {
|
|
10425
|
+
throw new Error("Continuity event contains an invalid sourceRef");
|
|
10426
|
+
}
|
|
10427
|
+
}
|
|
10428
|
+
if (!isRecord(event.relationships)) throw new Error("Continuity event requires relationships object");
|
|
10429
|
+
if (!isRecord(event.provenance)) throw new Error("Continuity event requires provenance object");
|
|
10430
|
+
const provenance = event.provenance;
|
|
10431
|
+
if (typeof provenance.author !== "string" || !AUTHORS.has(provenance.author)) {
|
|
10432
|
+
throw new Error("Continuity event requires a valid provenance.author");
|
|
10433
|
+
}
|
|
10434
|
+
if (typeof provenance.origin !== "string") throw new Error("Continuity event requires provenance.origin");
|
|
10435
|
+
if (typeof provenance.channel !== "string" || !CHANNELS.has(provenance.channel)) {
|
|
10436
|
+
throw new Error("Continuity event requires a valid provenance.channel");
|
|
10437
|
+
}
|
|
10438
|
+
if (typeof provenance.recordedAt !== "string") throw new Error("Continuity event requires provenance.recordedAt");
|
|
10439
|
+
return {
|
|
10440
|
+
schemaVersion: 1,
|
|
10441
|
+
eventId: event.eventId,
|
|
10442
|
+
eventType: event.eventType,
|
|
10443
|
+
memoryDomain: event.memoryDomain,
|
|
10444
|
+
epistemicStatus: event.epistemicStatus,
|
|
10445
|
+
confidence: event.confidence,
|
|
10446
|
+
createdAt: event.createdAt,
|
|
10447
|
+
sessionId: event.sessionId,
|
|
10448
|
+
projectId: event.projectId,
|
|
10449
|
+
subject: event.subject,
|
|
10450
|
+
content: event.content,
|
|
10451
|
+
sourceRefs: event.sourceRefs,
|
|
10452
|
+
relationships: normalizeRelationships(event.relationships),
|
|
10453
|
+
provenance
|
|
10454
|
+
};
|
|
10455
|
+
}, "validateEvent");
|
|
10456
|
+
var isLedgerEvent = /* @__PURE__ */ __name((claim) => {
|
|
10457
|
+
return isRecord(claim.knowledge) && claim.knowledge.schemaVersion === 1 && typeof claim.knowledge.eventId === "string";
|
|
10458
|
+
}, "isLedgerEvent");
|
|
10459
|
+
var loadEvents = /* @__PURE__ */ __name(async () => {
|
|
10460
|
+
const state3 = await loadCombinedKnowledgeState();
|
|
10461
|
+
const events = [];
|
|
10462
|
+
for (const claim of state3.claims) {
|
|
10463
|
+
if (isLedgerEvent(claim)) {
|
|
10464
|
+
try {
|
|
10465
|
+
events.push(validateEvent(claim.knowledge));
|
|
10466
|
+
} catch {
|
|
10467
|
+
}
|
|
10468
|
+
}
|
|
10469
|
+
}
|
|
10470
|
+
return events.sort((left, right) => left.eventId.localeCompare(right.eventId));
|
|
10471
|
+
}, "loadEvents");
|
|
10472
|
+
var findEvent = /* @__PURE__ */ __name((events, eventId) => events.find((event) => event.eventId === eventId), "findEvent");
|
|
10473
|
+
var epistemicTypeFor = /* @__PURE__ */ __name((status) => {
|
|
10474
|
+
if (status === "verified-fact" || status === "verified-result" || status === "decision") return "verified";
|
|
10475
|
+
if (status === "user-statement") return "human-stated";
|
|
10476
|
+
return "inferred";
|
|
10477
|
+
}, "epistemicTypeFor");
|
|
10478
|
+
var EVENT_ID_PATTERN = /^evt\.[A-Za-z0-9][A-Za-z0-9._-]*$/;
|
|
10479
|
+
var validateEventId = /* @__PURE__ */ __name((eventId) => {
|
|
10480
|
+
if (!EVENT_ID_PATTERN.test(eventId)) throw new Error(`Invalid continuity event ID: ${eventId}`);
|
|
10481
|
+
}, "validateEventId");
|
|
10482
|
+
var hasRelationshipPath = /* @__PURE__ */ __name((events, startId, targetId, relationKey) => {
|
|
10483
|
+
const visited = /* @__PURE__ */ new Set();
|
|
10484
|
+
const stack = [startId];
|
|
10485
|
+
while (stack.length > 0) {
|
|
10486
|
+
const current = stack.pop();
|
|
10487
|
+
if (current === targetId) return true;
|
|
10488
|
+
if (visited.has(current)) continue;
|
|
10489
|
+
visited.add(current);
|
|
10490
|
+
const event = findEvent(events, current);
|
|
10491
|
+
if (!event) continue;
|
|
10492
|
+
for (const next of event.relationships[relationKey]) {
|
|
10493
|
+
if (!visited.has(next)) stack.push(next);
|
|
10494
|
+
}
|
|
10495
|
+
}
|
|
10496
|
+
return false;
|
|
10497
|
+
}, "hasRelationshipPath");
|
|
10498
|
+
var CYCLIC_RELATIONSHIP_KEYS = /* @__PURE__ */ new Set([
|
|
10499
|
+
"refines",
|
|
10500
|
+
"supersedes",
|
|
10501
|
+
"retracts",
|
|
10502
|
+
"dependsOn",
|
|
10503
|
+
"resolves",
|
|
10504
|
+
"relatedTo"
|
|
10505
|
+
]);
|
|
10506
|
+
var validateRelationshipTargets = /* @__PURE__ */ __name(async (event, existing) => {
|
|
10507
|
+
validateEventId(event.eventId);
|
|
10508
|
+
for (const key of RELATIONSHIP_KEYS) {
|
|
10509
|
+
for (const targetId of event.relationships[key]) {
|
|
10510
|
+
validateEventId(targetId);
|
|
10511
|
+
if (targetId === event.eventId) throw new Error(`Continuity event cannot reference itself: ${targetId}`);
|
|
10512
|
+
const target = findEvent(existing, targetId);
|
|
10513
|
+
if (!target) throw new Error(`Continuity relationship references a nonexistent event: ${targetId}`);
|
|
10514
|
+
if (target.projectId !== event.projectId) {
|
|
10515
|
+
throw new Error(`Cross-scope continuity relationship is not allowed: ${event.eventId} -> ${targetId}`);
|
|
10516
|
+
}
|
|
10517
|
+
if (CYCLIC_RELATIONSHIP_KEYS.has(key) && hasRelationshipPath(existing, targetId, event.eventId, key)) {
|
|
10518
|
+
throw new Error(`Continuity relationship would create a cycle: ${event.eventId} -> ${targetId}`);
|
|
10519
|
+
}
|
|
10520
|
+
}
|
|
10521
|
+
}
|
|
10522
|
+
}, "validateRelationshipTargets");
|
|
10523
|
+
var validateResolutionRequest = /* @__PURE__ */ __name(async (resolution, existing) => {
|
|
10524
|
+
validateEventId(resolution.resolutionEventId);
|
|
10525
|
+
if (!Array.isArray(resolution.conflictingEventIds) || resolution.conflictingEventIds.length === 0) {
|
|
10526
|
+
throw new Error("Conflict resolution requires at least one conflicting event");
|
|
10527
|
+
}
|
|
10528
|
+
if (typeof resolution.selectedEventId !== "string" || !resolution.selectedEventId) {
|
|
10529
|
+
throw new Error("Conflict resolution requires a selected event");
|
|
10530
|
+
}
|
|
10531
|
+
if (typeof resolution.reason !== "string" || !resolution.reason.trim()) {
|
|
10532
|
+
throw new Error("Conflict resolution requires a reason");
|
|
10533
|
+
}
|
|
10534
|
+
const conflicting = resolution.conflictingEventIds.map((id) => {
|
|
10535
|
+
validateEventId(id);
|
|
10536
|
+
const event = findEvent(existing, id);
|
|
10537
|
+
if (!event) throw new Error(`Conflict resolution references a nonexistent event: ${id}`);
|
|
10538
|
+
if (event.relationships.supersedes.length !== 1) {
|
|
10539
|
+
throw new Error(`Conflict resolution references a non-supersession event: ${id}`);
|
|
10540
|
+
}
|
|
10541
|
+
return event;
|
|
10542
|
+
});
|
|
10543
|
+
const targets = new Set(conflicting.map((event) => event.relationships.supersedes[0]));
|
|
10544
|
+
if (targets.size !== 1) {
|
|
10545
|
+
throw new Error("Conflict resolution references supersession events targeting different decisions");
|
|
10546
|
+
}
|
|
10547
|
+
const targetId = [...targets][0];
|
|
10548
|
+
const knownCompeting = supersedingEvents(existing, targetId).map((event) => event.eventId).sort();
|
|
10549
|
+
const requested = [...resolution.conflictingEventIds].sort();
|
|
10550
|
+
if (requested.length !== knownCompeting.length || requested.some((id, index) => id !== knownCompeting[index])) {
|
|
10551
|
+
throw new Error("Conflict resolution must include every competing supersession event");
|
|
10552
|
+
}
|
|
10553
|
+
if (!resolution.conflictingEventIds.includes(resolution.selectedEventId)) {
|
|
10554
|
+
throw new Error("Conflict resolution selectedEventId must be one of the conflicting events");
|
|
10555
|
+
}
|
|
10556
|
+
const existingResolutions = existing.filter(
|
|
10557
|
+
(event) => event.relationships.resolves.includes(targetId) || resolution.conflictingEventIds.some((id) => event.relationships.resolves.includes(id))
|
|
10558
|
+
);
|
|
10559
|
+
if (existingResolutions.length > 0) {
|
|
10560
|
+
const selectedOutcomes = new Set(existingResolutions.map((event) => {
|
|
10561
|
+
const content = isRecord(event.content) ? event.content : {};
|
|
10562
|
+
return typeof content.selectedEventId === "string" ? content.selectedEventId : null;
|
|
10563
|
+
}).filter((value) => value !== null));
|
|
10564
|
+
if (existingResolutions.some((event) => event.eventId === resolution.resolutionEventId)) {
|
|
10565
|
+
throw new Error(`Duplicate continuity resolution event: ${resolution.resolutionEventId}`);
|
|
10566
|
+
}
|
|
10567
|
+
if (selectedOutcomes.size > 0 && !selectedOutcomes.has(resolution.selectedEventId)) {
|
|
10568
|
+
throw new Error("Incompatible continuity conflict resolution");
|
|
10569
|
+
}
|
|
10570
|
+
throw new Error("Continuity conflict is already resolved");
|
|
10571
|
+
}
|
|
10572
|
+
const resolutionEventId = resolution.resolutionEventId;
|
|
10573
|
+
for (const conflictingId of resolution.conflictingEventIds) {
|
|
10574
|
+
const event = findEvent(existing, conflictingId);
|
|
10575
|
+
if (event && hasRelationshipPath(existing, conflictingId, resolutionEventId, "resolves")) {
|
|
10576
|
+
throw new Error(`Continuity resolution would create a cycle: ${resolutionEventId}`);
|
|
10577
|
+
}
|
|
10578
|
+
}
|
|
10579
|
+
}, "validateResolutionRequest");
|
|
10580
|
+
var recordEventOnce = /* @__PURE__ */ __name(async (event) => {
|
|
10581
|
+
const validated = validateEvent(event);
|
|
10582
|
+
const existing = await loadEvents();
|
|
10583
|
+
await validateRelationshipTargets(validated, existing);
|
|
10584
|
+
if (existing.some((item) => item.eventId === validated.eventId)) {
|
|
10585
|
+
throw new Error(`Continuity event already exists: ${validated.eventId}`);
|
|
10586
|
+
}
|
|
10587
|
+
const scope = validated.projectId ? "project" : "user";
|
|
10588
|
+
const evidence = validated.sourceRefs.length > 0 ? validated.sourceRefs.map((ref) => ({
|
|
10589
|
+
id: ref.id,
|
|
10590
|
+
source: ref.kind,
|
|
10591
|
+
observedAt: validated.createdAt,
|
|
10592
|
+
supports: validated.eventId
|
|
10593
|
+
})) : [{ id: validated.eventId, source: "continuity-ledger", observedAt: validated.createdAt, supports: validated.eventId }];
|
|
10594
|
+
await memory.acquire({
|
|
10595
|
+
id: validated.eventId,
|
|
10596
|
+
knowledge: validated,
|
|
10597
|
+
applicability: { ...LEDGER_APPLICABILITY, topic: validated.eventType },
|
|
10598
|
+
evidence,
|
|
10599
|
+
epistemicType: epistemicTypeFor(validated.epistemicStatus),
|
|
10600
|
+
provenance: {
|
|
10601
|
+
source: validated.provenance.origin,
|
|
10602
|
+
detail: validated.provenance.channel
|
|
10603
|
+
},
|
|
10604
|
+
scope,
|
|
10605
|
+
sourceProject: validated.projectId ?? void 0,
|
|
10606
|
+
kind: "experience"
|
|
10607
|
+
});
|
|
10608
|
+
}, "recordEventOnce");
|
|
10609
|
+
var recordEvent = /* @__PURE__ */ __name(async (event) => {
|
|
10610
|
+
let lastError;
|
|
10611
|
+
for (let attempt = 0; attempt < 10; attempt += 1) {
|
|
10612
|
+
try {
|
|
10613
|
+
await recordEventOnce(event);
|
|
10614
|
+
return;
|
|
10615
|
+
} catch (error) {
|
|
10616
|
+
lastError = error;
|
|
10617
|
+
if (error instanceof Error && /Stale update/i.test(error.message) && attempt < 9) {
|
|
10618
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
10619
|
+
continue;
|
|
10620
|
+
}
|
|
10621
|
+
throw error;
|
|
10622
|
+
}
|
|
10623
|
+
}
|
|
10624
|
+
throw lastError instanceof Error ? lastError : new Error("Continuity event recording failed");
|
|
10625
|
+
}, "recordEvent");
|
|
10626
|
+
var getEvent = /* @__PURE__ */ __name(async (eventId) => {
|
|
10627
|
+
const events = await loadEvents();
|
|
10628
|
+
return findEvent(events, eventId) ?? null;
|
|
10629
|
+
}, "getEvent");
|
|
10630
|
+
var listEvents = /* @__PURE__ */ __name(async (filter = {}) => {
|
|
10631
|
+
const events = await loadEvents();
|
|
10632
|
+
return events.filter((event) => {
|
|
10633
|
+
if (filter.eventId && event.eventId !== filter.eventId) return false;
|
|
10634
|
+
if (filter.eventType && event.eventType !== filter.eventType) return false;
|
|
10635
|
+
if (filter.memoryDomain && event.memoryDomain !== filter.memoryDomain) return false;
|
|
10636
|
+
if (filter.sessionId && event.sessionId !== filter.sessionId) return false;
|
|
10637
|
+
if (filter.projectId !== void 0 && event.projectId !== filter.projectId) return false;
|
|
10638
|
+
if (filter.epistemicStatus && event.epistemicStatus !== filter.epistemicStatus) return false;
|
|
10639
|
+
return true;
|
|
10640
|
+
});
|
|
10641
|
+
}, "listEvents");
|
|
10642
|
+
var supersedingEvents = /* @__PURE__ */ __name((events, eventId) => events.filter((event) => event.relationships.supersedes.includes(eventId)), "supersedingEvents");
|
|
10643
|
+
var retractingEvents = /* @__PURE__ */ __name((events, eventId) => events.filter((event) => event.relationships.retracts.includes(eventId)), "retractingEvents");
|
|
10644
|
+
var resolutionEventsFor = /* @__PURE__ */ __name((events, eventId, conflictingIds) => events.filter(
|
|
10645
|
+
(event) => event.relationships.resolves.includes(eventId) || conflictingIds.some((id) => event.relationships.resolves.includes(id))
|
|
10646
|
+
), "resolutionEventsFor");
|
|
10647
|
+
var deriveValidity = /* @__PURE__ */ __name(async (eventId) => {
|
|
10648
|
+
let events = await loadEvents();
|
|
10649
|
+
for (let stabilityAttempt = 0; stabilityAttempt < 5; stabilityAttempt += 1) {
|
|
10650
|
+
const nextEvents = await loadEvents();
|
|
10651
|
+
if (nextEvents.length === events.length) break;
|
|
10652
|
+
events = nextEvents;
|
|
10653
|
+
await new Promise((resolve) => setTimeout(resolve, 25));
|
|
10654
|
+
}
|
|
10655
|
+
const supersededBy = supersedingEvents(events, eventId).map((event) => event.eventId);
|
|
10656
|
+
const retractedBy = retractingEvents(events, eventId).map((event) => event.eventId);
|
|
10657
|
+
const contestedBy = supersededBy.length > 1 ? supersededBy : [];
|
|
10658
|
+
const resolvedBy = resolutionEventsFor(events, eventId, contestedBy).map((event) => event.eventId);
|
|
10659
|
+
let state3 = "current";
|
|
10660
|
+
if (retractedBy.length > 0) state3 = "retracted";
|
|
10661
|
+
else if (supersededBy.length > 0) state3 = supersededBy.length > 1 && resolvedBy.length === 0 ? "contested" : "superseded";
|
|
10662
|
+
else if (events.some((event) => event.eventId === eventId && event.eventType === "decision") === false) state3 = "current";
|
|
10663
|
+
if (supersededBy.length > 1 && resolvedBy.length === 0) state3 = "contested";
|
|
10664
|
+
return {
|
|
10665
|
+
eventId,
|
|
10666
|
+
state: state3,
|
|
10667
|
+
supersededBy,
|
|
10668
|
+
retractedBy,
|
|
10669
|
+
contestedBy,
|
|
10670
|
+
resolvedBy
|
|
10671
|
+
};
|
|
10672
|
+
}, "deriveValidity");
|
|
10673
|
+
var listContested = /* @__PURE__ */ __name(async () => {
|
|
10674
|
+
const events = await loadEvents();
|
|
10675
|
+
const targets = /* @__PURE__ */ new Set();
|
|
10676
|
+
for (const event of events) {
|
|
10677
|
+
for (const target of event.relationships.supersedes) targets.add(target);
|
|
10678
|
+
}
|
|
10679
|
+
const contested = [];
|
|
10680
|
+
for (const target of targets) {
|
|
10681
|
+
const supersededBy = supersedingEvents(events, target);
|
|
10682
|
+
if (supersededBy.length <= 1) continue;
|
|
10683
|
+
const conflictingIds = supersededBy.map((event) => event.eventId);
|
|
10684
|
+
const resolvedBy = resolutionEventsFor(events, target, conflictingIds);
|
|
10685
|
+
if (resolvedBy.length === 0) contested.push(target);
|
|
10686
|
+
}
|
|
10687
|
+
return contested.sort();
|
|
10688
|
+
}, "listContested");
|
|
10689
|
+
var resolveConflictOnce = /* @__PURE__ */ __name(async (resolution) => {
|
|
10690
|
+
const existing = await loadEvents();
|
|
10691
|
+
await validateResolutionRequest(resolution, existing);
|
|
10692
|
+
const resolutionEvent = {
|
|
10693
|
+
schemaVersion: 1,
|
|
10694
|
+
eventId: resolution.resolutionEventId,
|
|
10695
|
+
eventType: "correction",
|
|
10696
|
+
memoryDomain: "professional",
|
|
10697
|
+
epistemicStatus: "decision",
|
|
10698
|
+
confidence: "high",
|
|
10699
|
+
createdAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
10700
|
+
sessionId: "resolution",
|
|
10701
|
+
projectId: null,
|
|
10702
|
+
subject: "Continuity conflict resolution",
|
|
10703
|
+
content: {
|
|
10704
|
+
selectedEventId: resolution.selectedEventId,
|
|
10705
|
+
reason: resolution.reason
|
|
10706
|
+
},
|
|
10707
|
+
sourceRefs: resolution.conflictingEventIds.map((id) => ({ kind: "claim", id })),
|
|
10708
|
+
relationships: {
|
|
10709
|
+
refines: [],
|
|
10710
|
+
supersedes: [],
|
|
10711
|
+
retracts: [],
|
|
10712
|
+
dependsOn: [],
|
|
10713
|
+
resolves: [...resolution.conflictingEventIds],
|
|
10714
|
+
relatedTo: [resolution.selectedEventId]
|
|
10715
|
+
},
|
|
10716
|
+
provenance: {
|
|
10717
|
+
author: "system",
|
|
10718
|
+
origin: "continuity-ledger",
|
|
10719
|
+
channel: "acquisition",
|
|
10720
|
+
recordedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
10721
|
+
}
|
|
10722
|
+
};
|
|
10723
|
+
await recordEvent(resolutionEvent);
|
|
10724
|
+
}, "resolveConflictOnce");
|
|
10725
|
+
var reconstructValidity = /* @__PURE__ */ __name(async () => {
|
|
10726
|
+
const events = await loadEvents();
|
|
10727
|
+
const result = [];
|
|
10728
|
+
const targets = new Set(events.map((event) => event.eventId));
|
|
10729
|
+
for (const event of events) {
|
|
10730
|
+
for (const target of event.relationships.supersedes) targets.add(target);
|
|
10731
|
+
for (const target of event.relationships.retracts) targets.add(target);
|
|
10732
|
+
}
|
|
10733
|
+
for (const target of targets) {
|
|
10734
|
+
result.push(await deriveValidity(target));
|
|
10735
|
+
}
|
|
10736
|
+
return result.sort((left, right) => left.eventId.localeCompare(right.eventId));
|
|
10737
|
+
}, "reconstructValidity");
|
|
10738
|
+
var resolveConflict = /* @__PURE__ */ __name(async (resolution) => {
|
|
10739
|
+
let lastError;
|
|
10740
|
+
for (let attempt = 0; attempt < 3; attempt += 1) {
|
|
10741
|
+
try {
|
|
10742
|
+
await resolveConflictOnce(resolution);
|
|
10743
|
+
return;
|
|
10744
|
+
} catch (error) {
|
|
10745
|
+
lastError = error;
|
|
10746
|
+
if (error instanceof Error && /Stale update/i.test(error.message) && attempt < 9) {
|
|
10747
|
+
await new Promise((resolve) => setTimeout(resolve, 20));
|
|
10748
|
+
continue;
|
|
10749
|
+
}
|
|
10750
|
+
throw error;
|
|
10751
|
+
}
|
|
10752
|
+
}
|
|
10753
|
+
throw lastError instanceof Error ? lastError : new Error("Continuity conflict resolution failed");
|
|
10754
|
+
}, "resolveConflict");
|
|
10755
|
+
var validateRelationships = validateRelationshipTargets;
|
|
10756
|
+
var continuityLedger = {
|
|
10757
|
+
recordEvent,
|
|
10758
|
+
record: recordEvent,
|
|
10759
|
+
append: recordEvent,
|
|
10760
|
+
add: recordEvent,
|
|
10761
|
+
getEvent,
|
|
10762
|
+
get: getEvent,
|
|
10763
|
+
find: getEvent,
|
|
10764
|
+
listEvents,
|
|
10765
|
+
list: listEvents,
|
|
10766
|
+
query: listEvents,
|
|
10767
|
+
deriveValidity,
|
|
10768
|
+
validity: deriveValidity,
|
|
10769
|
+
listContested,
|
|
10770
|
+
contested: listContested,
|
|
10771
|
+
resolveConflict,
|
|
10772
|
+
resolve: resolveConflict,
|
|
10773
|
+
reconstructValidity,
|
|
10774
|
+
validateRelationships,
|
|
10775
|
+
validateRelationshipTargets,
|
|
10776
|
+
validateResolution: validateResolutionRequest
|
|
10777
|
+
};
|
|
10778
|
+
|
|
10197
10779
|
// core/continuity/index.ts
|
|
10198
10780
|
var continuity = {
|
|
10199
10781
|
async context(options) {
|
|
@@ -12022,6 +12604,1232 @@ var createCompactionEngine = /* @__PURE__ */ __name((options) => new CompactionE
|
|
|
12022
12604
|
// index.ts
|
|
12023
12605
|
init_user_identity();
|
|
12024
12606
|
init_user_paths();
|
|
12607
|
+
|
|
12608
|
+
// core/governance/index.ts
|
|
12609
|
+
init_user_paths();
|
|
12610
|
+
init_policy();
|
|
12611
|
+
init_policy_namespace_lock();
|
|
12612
|
+
init_detection_events();
|
|
12613
|
+
init_policy();
|
|
12614
|
+
var GOVERNANCE_FILE_NAME = "governance.policy.json";
|
|
12615
|
+
var GOVERNANCE_FORMAT = "dotmem.governance-policy";
|
|
12616
|
+
var GOVERNANCE_VERSION = 1;
|
|
12617
|
+
var GOVERNANCE_AUDIT_RETENTION = 200;
|
|
12618
|
+
var GovernancePolicyError = class extends Error {
|
|
12619
|
+
static {
|
|
12620
|
+
__name(this, "GovernancePolicyError");
|
|
12621
|
+
}
|
|
12622
|
+
constructor(message) {
|
|
12623
|
+
super(message);
|
|
12624
|
+
this.name = "GovernancePolicyError";
|
|
12625
|
+
}
|
|
12626
|
+
};
|
|
12627
|
+
var requiredText3 = /* @__PURE__ */ __name((v) => typeof v === "string" && v.trim().length > 0, "requiredText");
|
|
12628
|
+
var nowIso2 = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString(), "nowIso");
|
|
12629
|
+
var joinPath7 = /* @__PURE__ */ __name((base, child) => {
|
|
12630
|
+
const sep = process.platform === "win32" ? "\\" : "/";
|
|
12631
|
+
return `${base.replace(/[\\/]+/g, sep)}${sep}${child}`;
|
|
12632
|
+
}, "joinPath");
|
|
12633
|
+
var resolveGovernanceDir = /* @__PURE__ */ __name((directory) => directory ?? joinPath7(resolveUserDotMemPath(), "policy"), "resolveGovernanceDir");
|
|
12634
|
+
var resolveGovernanceFilePath = /* @__PURE__ */ __name((directory) => joinPath7(resolveGovernanceDir(directory), GOVERNANCE_FILE_NAME), "resolveGovernanceFilePath");
|
|
12635
|
+
var ROLE_CAPABILITIES = {
|
|
12636
|
+
"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"]),
|
|
12637
|
+
"human-approver": /* @__PURE__ */ new Set(["policy.review", "policy.approve", "policy.reject", "governance.read", "governance.audit.read", "guardrail.stop.activate", "guardrail.stop.release"]),
|
|
12638
|
+
"coordinator-jo": /* @__PURE__ */ new Set(["policy.propose", "policy.review", "policy.activate", "governance.read", "governance.audit.read", "agent.execute"]),
|
|
12639
|
+
"specialist-agent": /* @__PURE__ */ new Set(["policy.propose", "governance.read"]),
|
|
12640
|
+
"policy-authority": /* @__PURE__ */ new Set(["policy.propose", "policy.review", "policy.activate", "governance.read", "governance.audit.read", "agent.execute"]),
|
|
12641
|
+
"auditor-reviewer": /* @__PURE__ */ new Set(["governance.read", "governance.audit.read"])
|
|
12642
|
+
};
|
|
12643
|
+
var defaultRoleBindings = /* @__PURE__ */ __name(() => [
|
|
12644
|
+
{ principalId: "hi", role: "human-owner", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
12645
|
+
{ principalId: "human-owner", role: "human-owner", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
12646
|
+
{ principalId: "jo", role: "coordinator-jo", scope: "*", grantedBy: "system", grantedAt: (/* @__PURE__ */ new Date(0)).toISOString() }
|
|
12647
|
+
], "defaultRoleBindings");
|
|
12648
|
+
var emptyDocument2 = /* @__PURE__ */ __name(() => ({
|
|
12649
|
+
format: GOVERNANCE_FORMAT,
|
|
12650
|
+
version: GOVERNANCE_VERSION,
|
|
12651
|
+
revision: 0,
|
|
12652
|
+
policies: [],
|
|
12653
|
+
roleBindings: defaultRoleBindings(),
|
|
12654
|
+
audit: []
|
|
12655
|
+
}), "emptyDocument");
|
|
12656
|
+
var validateAuthority = /* @__PURE__ */ __name((value) => {
|
|
12657
|
+
try {
|
|
12658
|
+
return exports.validateGovernanceAuthority(value);
|
|
12659
|
+
} catch (err2) {
|
|
12660
|
+
void detectAuthorityEscalationAttempt({ claimed: "invalid governance authority", channel: "governance" }).catch(() => void 0);
|
|
12661
|
+
throw err2 instanceof GovernancePolicyError ? err2 : new GovernancePolicyError(err2.message);
|
|
12662
|
+
}
|
|
12663
|
+
}, "validateAuthority");
|
|
12664
|
+
var buildAudit = /* @__PURE__ */ __name((entry) => ({
|
|
12665
|
+
id: entry.id ?? `gov-audit.${entry.action}.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`,
|
|
12666
|
+
action: entry.action,
|
|
12667
|
+
at: entry.at,
|
|
12668
|
+
...entry.actorPrincipal !== void 0 ? { actorPrincipal: entry.actorPrincipal } : {},
|
|
12669
|
+
...entry.actorRole !== void 0 ? { actorRole: entry.actorRole } : {},
|
|
12670
|
+
...entry.targetPolicyId !== void 0 ? { targetPolicyId: entry.targetPolicyId } : {},
|
|
12671
|
+
...entry.targetVersion !== void 0 ? { targetVersion: entry.targetVersion } : {},
|
|
12672
|
+
...entry.scope !== void 0 ? { scope: entry.scope } : {},
|
|
12673
|
+
outcome: entry.outcome,
|
|
12674
|
+
...entry.reason !== void 0 ? { reason: entry.reason } : {},
|
|
12675
|
+
...entry.detail !== void 0 ? { detail: entry.detail } : {},
|
|
12676
|
+
...entry.correlationId !== void 0 ? { correlationId: entry.correlationId } : {},
|
|
12677
|
+
...entry.mandatory !== void 0 ? { mandatory: entry.mandatory } : {}
|
|
12678
|
+
}), "buildAudit");
|
|
12679
|
+
var boundedAudit2 = /* @__PURE__ */ __name((entries2) => {
|
|
12680
|
+
if (entries2.length <= GOVERNANCE_AUDIT_RETENTION) return [...entries2];
|
|
12681
|
+
const kept = [];
|
|
12682
|
+
let budget = GOVERNANCE_AUDIT_RETENTION;
|
|
12683
|
+
for (let i2 = entries2.length - 1; i2 >= 0; i2--) {
|
|
12684
|
+
const e = entries2[i2];
|
|
12685
|
+
const mandatory = e.mandatory === true;
|
|
12686
|
+
if (mandatory || budget > 0) {
|
|
12687
|
+
kept.unshift(e);
|
|
12688
|
+
if (!mandatory) budget--;
|
|
12689
|
+
}
|
|
12690
|
+
}
|
|
12691
|
+
return kept;
|
|
12692
|
+
}, "boundedAudit");
|
|
12693
|
+
var validatePolicy = /* @__PURE__ */ __name((p) => {
|
|
12694
|
+
if (!p || typeof p !== "object" || Array.isArray(p)) throw new GovernancePolicyError("Governance policy must be an object");
|
|
12695
|
+
const r = p;
|
|
12696
|
+
if (!requiredText3(r.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
12697
|
+
if (typeof r.version !== "number" || !Number.isInteger(r.version) || r.version < 1) throw new GovernancePolicyError("version must be a positive integer");
|
|
12698
|
+
if (!requiredText3(r.scope)) throw new GovernancePolicyError("scope is required");
|
|
12699
|
+
if (!requiredText3(r.rationale)) throw new GovernancePolicyError("rationale is required");
|
|
12700
|
+
if (!requiredText3(r.proposedBy)) throw new GovernancePolicyError("proposedBy is required");
|
|
12701
|
+
if (!requiredText3(r.proposedAt)) throw new GovernancePolicyError("proposedAt is required");
|
|
12702
|
+
if (typeof r.status !== "string" || !["proposed", "under_review", "approved", "active", "rejected", "suspended", "retired"].includes(r.status)) {
|
|
12703
|
+
throw new GovernancePolicyError("invalid status");
|
|
12704
|
+
}
|
|
12705
|
+
if (typeof r.revision !== "number" || !Number.isInteger(r.revision) || r.revision < 0) throw new GovernancePolicyError("revision must be a non-negative integer");
|
|
12706
|
+
if (!Array.isArray(r.applicablePrincipals)) throw new GovernancePolicyError("applicablePrincipals must be an array");
|
|
12707
|
+
if (!Array.isArray(r.applicableResources)) throw new GovernancePolicyError("applicableResources must be an array");
|
|
12708
|
+
const authority = validateAuthority(r.ownerAuthority);
|
|
12709
|
+
return {
|
|
12710
|
+
policyId: r.policyId,
|
|
12711
|
+
version: r.version,
|
|
12712
|
+
...requiredText3(r.title) ? { title: r.title } : {},
|
|
12713
|
+
...requiredText3(r.description) ? { description: r.description } : {},
|
|
12714
|
+
scope: r.scope,
|
|
12715
|
+
applicablePrincipals: r.applicablePrincipals,
|
|
12716
|
+
applicableResources: r.applicableResources,
|
|
12717
|
+
...requiredText3(r.effectiveAt) ? { effectiveAt: r.effectiveAt } : {},
|
|
12718
|
+
...requiredText3(r.expiresAt) ? { expiresAt: r.expiresAt } : {},
|
|
12719
|
+
rationale: r.rationale,
|
|
12720
|
+
status: r.status,
|
|
12721
|
+
ownerAuthority: authority,
|
|
12722
|
+
proposedBy: r.proposedBy,
|
|
12723
|
+
proposedAt: r.proposedAt,
|
|
12724
|
+
...requiredText3(r.reviewedBy) ? { reviewedBy: r.reviewedBy } : {},
|
|
12725
|
+
...requiredText3(r.reviewedAt) ? { reviewedAt: r.reviewedAt } : {},
|
|
12726
|
+
...r.approval && typeof r.approval === "object" ? { approval: r.approval } : {},
|
|
12727
|
+
...requiredText3(r.activatedBy) ? { activatedBy: r.activatedBy } : {},
|
|
12728
|
+
...requiredText3(r.activatedAt) ? { activatedAt: r.activatedAt } : {},
|
|
12729
|
+
...requiredText3(r.suspendedBy) ? { suspendedBy: r.suspendedBy } : {},
|
|
12730
|
+
...requiredText3(r.suspendedAt) ? { suspendedAt: r.suspendedAt } : {},
|
|
12731
|
+
...requiredText3(r.retiredBy) ? { retiredBy: r.retiredBy } : {},
|
|
12732
|
+
...requiredText3(r.retiredAt) ? { retiredAt: r.retiredAt } : {},
|
|
12733
|
+
...requiredText3(r.supersedes) ? { supersedes: r.supersedes } : {},
|
|
12734
|
+
...requiredText3(r.supersededBy) ? { supersededBy: r.supersededBy } : {},
|
|
12735
|
+
...requiredText3(r.correlationId) ? { correlationId: r.correlationId } : {},
|
|
12736
|
+
revision: r.revision,
|
|
12737
|
+
...requiredText3(r.fingerprint) ? { fingerprint: r.fingerprint } : {}
|
|
12738
|
+
};
|
|
12739
|
+
}, "validatePolicy");
|
|
12740
|
+
var validateRoleBinding = /* @__PURE__ */ __name((rb) => {
|
|
12741
|
+
if (!rb || typeof rb !== "object" || Array.isArray(rb)) throw new GovernancePolicyError("role binding must be an object");
|
|
12742
|
+
const r = rb;
|
|
12743
|
+
if (!requiredText3(r.principalId)) throw new GovernancePolicyError("principalId is required");
|
|
12744
|
+
if (typeof r.role !== "string" || !["human-owner", "human-approver", "coordinator-jo", "specialist-agent", "policy-authority", "auditor-reviewer"].includes(r.role)) {
|
|
12745
|
+
throw new GovernancePolicyError("invalid role");
|
|
12746
|
+
}
|
|
12747
|
+
if (!requiredText3(r.scope)) throw new GovernancePolicyError("scope is required");
|
|
12748
|
+
return {
|
|
12749
|
+
principalId: r.principalId,
|
|
12750
|
+
role: r.role,
|
|
12751
|
+
scope: r.scope,
|
|
12752
|
+
...requiredText3(r.grantedBy) ? { grantedBy: r.grantedBy } : {},
|
|
12753
|
+
...requiredText3(r.grantedAt) ? { grantedAt: r.grantedAt } : {}
|
|
12754
|
+
};
|
|
12755
|
+
}, "validateRoleBinding");
|
|
12756
|
+
var validateDocument2 = /* @__PURE__ */ __name((value) => {
|
|
12757
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw new GovernancePolicyError("document must be an object");
|
|
12758
|
+
const d = value;
|
|
12759
|
+
if (d.format !== GOVERNANCE_FORMAT) throw new GovernancePolicyError("invalid format");
|
|
12760
|
+
if (d.version !== GOVERNANCE_VERSION) throw new GovernancePolicyError("invalid version");
|
|
12761
|
+
if (typeof d.revision !== "number" || !Number.isInteger(d.revision) || d.revision < 0) throw new GovernancePolicyError("invalid revision");
|
|
12762
|
+
if (!Array.isArray(d.policies)) throw new GovernancePolicyError("policies must be an array");
|
|
12763
|
+
if (!Array.isArray(d.roleBindings)) throw new GovernancePolicyError("roleBindings must be an array");
|
|
12764
|
+
if (!Array.isArray(d.audit)) throw new GovernancePolicyError("audit must be an array");
|
|
12765
|
+
const policies = d.policies.map(validatePolicy);
|
|
12766
|
+
const roleBindings = d.roleBindings.map(validateRoleBinding);
|
|
12767
|
+
const audit = d.audit.map((e) => {
|
|
12768
|
+
if (!e || typeof e !== "object" || Array.isArray(e)) throw new GovernancePolicyError("audit entry must be an object");
|
|
12769
|
+
const r = e;
|
|
12770
|
+
if (!requiredText3(r.id)) throw new GovernancePolicyError("audit id required");
|
|
12771
|
+
if (!requiredText3(r.action)) throw new GovernancePolicyError("audit action required");
|
|
12772
|
+
if (!requiredText3(r.at)) throw new GovernancePolicyError("audit at required");
|
|
12773
|
+
if (r.outcome !== "applied" && r.outcome !== "rejected") throw new GovernancePolicyError("audit outcome invalid");
|
|
12774
|
+
return r;
|
|
12775
|
+
});
|
|
12776
|
+
return { format: GOVERNANCE_FORMAT, version: GOVERNANCE_VERSION, revision: d.revision, policies, roleBindings, audit };
|
|
12777
|
+
}, "validateDocument");
|
|
12778
|
+
var scopeCovers = /* @__PURE__ */ __name((bindingScope, targetScope) => {
|
|
12779
|
+
if (bindingScope === "*") return true;
|
|
12780
|
+
if (bindingScope === targetScope) return true;
|
|
12781
|
+
return targetScope.startsWith(bindingScope.endsWith("/") ? bindingScope : bindingScope + "/");
|
|
12782
|
+
}, "scopeCovers");
|
|
12783
|
+
var GovernancePolicyStore = class {
|
|
12784
|
+
constructor(directory) {
|
|
12785
|
+
this.directory = directory;
|
|
12786
|
+
}
|
|
12787
|
+
directory;
|
|
12788
|
+
static {
|
|
12789
|
+
__name(this, "GovernancePolicyStore");
|
|
12790
|
+
}
|
|
12791
|
+
dir() {
|
|
12792
|
+
return resolveGovernanceDir(this.directory);
|
|
12793
|
+
}
|
|
12794
|
+
filePath() {
|
|
12795
|
+
return resolveGovernanceFilePath(this.directory);
|
|
12796
|
+
}
|
|
12797
|
+
async load() {
|
|
12798
|
+
const fs = await import('fs/promises');
|
|
12799
|
+
let raw;
|
|
12800
|
+
try {
|
|
12801
|
+
raw = await fs.readFile(this.filePath(), "utf8");
|
|
12802
|
+
} catch (err2) {
|
|
12803
|
+
if (err2.code === "ENOENT") return emptyDocument2();
|
|
12804
|
+
throw err2;
|
|
12805
|
+
}
|
|
12806
|
+
return validateDocument2(JSON.parse(raw));
|
|
12807
|
+
}
|
|
12808
|
+
async readDoc(fs) {
|
|
12809
|
+
try {
|
|
12810
|
+
const raw = await fs.readFile(this.filePath(), "utf8");
|
|
12811
|
+
return validateDocument2(JSON.parse(raw));
|
|
12812
|
+
} catch (err2) {
|
|
12813
|
+
if (err2.code === "ENOENT") return emptyDocument2();
|
|
12814
|
+
throw err2;
|
|
12815
|
+
}
|
|
12816
|
+
}
|
|
12817
|
+
async writeDoc(fs, doc) {
|
|
12818
|
+
await fs.mkdir(this.dir(), { recursive: true });
|
|
12819
|
+
const fp = this.filePath();
|
|
12820
|
+
const tmp = `${fp}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
12821
|
+
try {
|
|
12822
|
+
await fs.writeFile(tmp, `${JSON.stringify(doc, null, 2)}
|
|
12823
|
+
`, "utf8");
|
|
12824
|
+
const handle = await fs.open(tmp, "r+");
|
|
12825
|
+
try {
|
|
12826
|
+
await handle.sync();
|
|
12827
|
+
} finally {
|
|
12828
|
+
await handle.close();
|
|
12829
|
+
}
|
|
12830
|
+
await fs.rename(tmp, fp);
|
|
12831
|
+
} catch (err2) {
|
|
12832
|
+
await fs.rm(tmp, { force: true }).catch(() => void 0);
|
|
12833
|
+
throw err2;
|
|
12834
|
+
}
|
|
12835
|
+
}
|
|
12836
|
+
async mutate(operation, options = {}) {
|
|
12837
|
+
const directory = this.dir();
|
|
12838
|
+
return withPolicyNamespaceLock(async (session3) => {
|
|
12839
|
+
const fs = await import('fs/promises');
|
|
12840
|
+
const doc = await this.readDoc(fs);
|
|
12841
|
+
if (options.basedOnRevision !== void 0 && options.basedOnRevision !== doc.revision) {
|
|
12842
|
+
throw new GovernancePolicyError(`Stale governance revision: expected ${options.basedOnRevision}, current ${doc.revision}`);
|
|
12843
|
+
}
|
|
12844
|
+
const outcome = operation(doc);
|
|
12845
|
+
let next = outcome.document;
|
|
12846
|
+
validateDocument2(next);
|
|
12847
|
+
await session3.assertOwnership();
|
|
12848
|
+
await this.writeDoc(fs, next);
|
|
12849
|
+
return outcome.result;
|
|
12850
|
+
}, { directory });
|
|
12851
|
+
}
|
|
12852
|
+
// --- role helpers ---
|
|
12853
|
+
findRoleBinding(doc, principalId, scope) {
|
|
12854
|
+
const candidates = doc.roleBindings.filter((rb) => rb.principalId === principalId && scopeCovers(rb.scope, scope));
|
|
12855
|
+
if (candidates.length === 0) return null;
|
|
12856
|
+
return candidates.sort((a, b) => b.scope.length - a.scope.length)[0];
|
|
12857
|
+
}
|
|
12858
|
+
resolveRole(principalId, scope = "*") {
|
|
12859
|
+
return { principalId, role: null, scope, reason: "use resolveRoleAsync" };
|
|
12860
|
+
}
|
|
12861
|
+
async resolveRoleAsync(principalId, scope = "*") {
|
|
12862
|
+
const doc = await this.load();
|
|
12863
|
+
const rb = this.findRoleBinding(doc, principalId, scope);
|
|
12864
|
+
if (!rb) return { principalId, role: null, scope, reason: "no role binding for principal in scope" };
|
|
12865
|
+
return { principalId, role: rb.role, scope, reason: `bound role ${rb.role} (scope ${rb.scope})` };
|
|
12866
|
+
}
|
|
12867
|
+
async hasPermission(principalId, action, scope) {
|
|
12868
|
+
const res = await this.resolveRoleAsync(principalId, scope);
|
|
12869
|
+
if (!res.role) return false;
|
|
12870
|
+
return ROLE_CAPABILITIES[res.role].has(action);
|
|
12871
|
+
}
|
|
12872
|
+
// --- role binding governed ---
|
|
12873
|
+
async bindRole(input, basedOnRevision) {
|
|
12874
|
+
const authority = validateAuthority(input.authority);
|
|
12875
|
+
if (!requiredText3(input.principalId)) throw new GovernancePolicyError("principalId is required");
|
|
12876
|
+
if (!requiredText3(input.grantedBy)) throw new GovernancePolicyError("grantedBy is required");
|
|
12877
|
+
if (input.principalId === input.grantedBy && (input.role === "human-owner" || input.role === "human-approver" || input.role === "policy-authority")) {
|
|
12878
|
+
void detectSelfPromoted({ claim: `self-grant ${input.role}`, channel: "governance.role.bind" }).catch(() => void 0);
|
|
12879
|
+
throw new GovernancePolicyError("self-grant of elevated role denied");
|
|
12880
|
+
}
|
|
12881
|
+
const doc0 = await this.load();
|
|
12882
|
+
const grantedByRole = this.findRoleBinding(doc0, input.grantedBy, input.scope ?? "*");
|
|
12883
|
+
if (!grantedByRole || !ROLE_CAPABILITIES[grantedByRole.role].has("role.bind")) {
|
|
12884
|
+
throw new GovernancePolicyError("grantedBy lacks role.bind authority");
|
|
12885
|
+
}
|
|
12886
|
+
if (authority.kind !== "HI" && authority.kind !== "operator") {
|
|
12887
|
+
throw new GovernancePolicyError("invalid authority for role binding");
|
|
12888
|
+
}
|
|
12889
|
+
const binding = {
|
|
12890
|
+
principalId: input.principalId,
|
|
12891
|
+
role: input.role,
|
|
12892
|
+
scope: input.scope || "*",
|
|
12893
|
+
grantedBy: input.grantedBy,
|
|
12894
|
+
grantedAt: nowIso2()
|
|
12895
|
+
};
|
|
12896
|
+
return this.mutate((doc) => {
|
|
12897
|
+
const nextBindings = [...doc.roleBindings.filter((rb) => !(rb.principalId === binding.principalId && rb.scope === binding.scope)), binding];
|
|
12898
|
+
const revision = doc.revision + 1;
|
|
12899
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
12900
|
+
action: "role.bind",
|
|
12901
|
+
at: binding.grantedAt,
|
|
12902
|
+
actorPrincipal: input.grantedBy,
|
|
12903
|
+
actorRole: grantedByRole.role,
|
|
12904
|
+
scope: binding.scope,
|
|
12905
|
+
outcome: "applied",
|
|
12906
|
+
detail: `bound ${binding.principalId} to ${binding.role} in scope ${binding.scope}`,
|
|
12907
|
+
correlationId: void 0
|
|
12908
|
+
})]);
|
|
12909
|
+
return { document: { ...doc, revision, roleBindings: nextBindings, audit }, result: binding };
|
|
12910
|
+
}, { basedOnRevision });
|
|
12911
|
+
}
|
|
12912
|
+
// --- propose ---
|
|
12913
|
+
async propose(input, basedOnRevision) {
|
|
12914
|
+
if (!input || typeof input !== "object") throw new GovernancePolicyError("proposal input required");
|
|
12915
|
+
if (!requiredText3(input.scope)) throw new GovernancePolicyError("scope is required");
|
|
12916
|
+
if (!requiredText3(input.rationale)) throw new GovernancePolicyError("rationale is required");
|
|
12917
|
+
if (!requiredText3(input.proposedBy)) throw new GovernancePolicyError("proposedBy is required");
|
|
12918
|
+
const authority = validateAuthority(input.authority);
|
|
12919
|
+
const doc0 = await this.load();
|
|
12920
|
+
if (!await this.hasPermission(input.proposedBy, "policy.propose", input.scope)) {
|
|
12921
|
+
void detectAuthorityEscalationAttempt({ claimed: `${input.proposedBy} propose without permission`, channel: "governance.propose" }).catch(() => void 0);
|
|
12922
|
+
await this.recordDenial(doc0, "policy.propose", input.proposedBy, input.scope, "permission denied");
|
|
12923
|
+
throw new GovernancePolicyError("proposer lacks policy.propose permission");
|
|
12924
|
+
}
|
|
12925
|
+
const policyId = requiredText3(input.policyId) ? input.policyId : `gov-policy.${Date.now()}.${Math.random().toString(36).slice(2, 8)}`;
|
|
12926
|
+
const existing = doc0.policies.filter((p) => p.policyId === policyId);
|
|
12927
|
+
const version = existing.length > 0 ? Math.max(...existing.map((p) => p.version)) + 1 : 1;
|
|
12928
|
+
const supersedes = input.supersedes;
|
|
12929
|
+
const policy2 = {
|
|
12930
|
+
policyId,
|
|
12931
|
+
version,
|
|
12932
|
+
...requiredText3(input.title) ? { title: input.title } : {},
|
|
12933
|
+
...requiredText3(input.description) ? { description: input.description } : {},
|
|
12934
|
+
scope: input.scope,
|
|
12935
|
+
applicablePrincipals: input.applicablePrincipals ?? [],
|
|
12936
|
+
applicableResources: input.applicableResources ?? [],
|
|
12937
|
+
...requiredText3(input.effectiveAt) ? { effectiveAt: input.effectiveAt } : {},
|
|
12938
|
+
...requiredText3(input.expiresAt) ? { expiresAt: input.expiresAt } : {},
|
|
12939
|
+
rationale: input.rationale,
|
|
12940
|
+
status: "proposed",
|
|
12941
|
+
ownerAuthority: authority,
|
|
12942
|
+
proposedBy: input.proposedBy,
|
|
12943
|
+
proposedAt: nowIso2(),
|
|
12944
|
+
...requiredText3(supersedes) ? { supersedes } : {},
|
|
12945
|
+
...requiredText3(input.correlationId) ? { correlationId: input.correlationId } : {},
|
|
12946
|
+
revision: 0
|
|
12947
|
+
};
|
|
12948
|
+
return this.mutate((doc) => {
|
|
12949
|
+
const revision = doc.revision + 1;
|
|
12950
|
+
const fingerprint = `${policy2.policyId}@v${policy2.version}`;
|
|
12951
|
+
const stored = { ...policy2, revision, fingerprint };
|
|
12952
|
+
const policies = [...doc.policies, stored];
|
|
12953
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
12954
|
+
action: "policy.proposed",
|
|
12955
|
+
at: stored.proposedAt,
|
|
12956
|
+
actorPrincipal: input.proposedBy,
|
|
12957
|
+
targetPolicyId: stored.policyId,
|
|
12958
|
+
targetVersion: stored.version,
|
|
12959
|
+
scope: stored.scope,
|
|
12960
|
+
outcome: "applied",
|
|
12961
|
+
detail: `policy ${stored.policyId} v${stored.version} proposed`,
|
|
12962
|
+
correlationId: input.correlationId
|
|
12963
|
+
})]);
|
|
12964
|
+
return { document: { ...doc, revision, policies, audit }, result: stored };
|
|
12965
|
+
}, { basedOnRevision });
|
|
12966
|
+
}
|
|
12967
|
+
async recordDenial(doc, action, actor, scope, reason) {
|
|
12968
|
+
try {
|
|
12969
|
+
await this.mutate((d) => {
|
|
12970
|
+
const revision = d.revision + 1;
|
|
12971
|
+
const audit = boundedAudit2([...d.audit, buildAudit({
|
|
12972
|
+
action: `${action}.denied`,
|
|
12973
|
+
at: nowIso2(),
|
|
12974
|
+
actorPrincipal: actor,
|
|
12975
|
+
scope,
|
|
12976
|
+
outcome: "rejected",
|
|
12977
|
+
reason
|
|
12978
|
+
})]);
|
|
12979
|
+
return { document: { ...d, revision, audit }, result: void 0 };
|
|
12980
|
+
});
|
|
12981
|
+
} catch {
|
|
12982
|
+
}
|
|
12983
|
+
}
|
|
12984
|
+
// --- review ---
|
|
12985
|
+
async review(input) {
|
|
12986
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
12987
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
12988
|
+
if (!requiredText3(input.reviewerPrincipal)) throw new GovernancePolicyError("reviewerPrincipal is required");
|
|
12989
|
+
const doc0 = await this.load();
|
|
12990
|
+
if (!await this.hasPermission(input.reviewerPrincipal, "policy.review", "*")) {
|
|
12991
|
+
const pol = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
12992
|
+
const scope = pol?.scope ?? "*";
|
|
12993
|
+
if (!await this.hasPermission(input.reviewerPrincipal, "policy.review", scope)) {
|
|
12994
|
+
await this.recordDenial(doc0, "policy.review", input.reviewerPrincipal, scope, "permission denied");
|
|
12995
|
+
throw new GovernancePolicyError("reviewer lacks policy.review permission");
|
|
12996
|
+
}
|
|
12997
|
+
}
|
|
12998
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
12999
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13000
|
+
if (policy2.status !== "proposed") throw new GovernancePolicyError(`cannot review policy in status ${policy2.status}`);
|
|
13001
|
+
return this.mutate((doc) => {
|
|
13002
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13003
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13004
|
+
const updated = {
|
|
13005
|
+
...doc.policies[idx],
|
|
13006
|
+
status: "under_review",
|
|
13007
|
+
reviewedBy: input.reviewerPrincipal,
|
|
13008
|
+
reviewedAt: nowIso2()
|
|
13009
|
+
};
|
|
13010
|
+
const policies = [...doc.policies];
|
|
13011
|
+
policies[idx] = updated;
|
|
13012
|
+
const revision = doc.revision + 1;
|
|
13013
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13014
|
+
action: "policy.reviewed",
|
|
13015
|
+
at: updated.reviewedAt,
|
|
13016
|
+
actorPrincipal: input.reviewerPrincipal,
|
|
13017
|
+
targetPolicyId: updated.policyId,
|
|
13018
|
+
targetVersion: updated.version,
|
|
13019
|
+
scope: updated.scope,
|
|
13020
|
+
outcome: "applied",
|
|
13021
|
+
correlationId: input.correlationId
|
|
13022
|
+
})]);
|
|
13023
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13024
|
+
});
|
|
13025
|
+
}
|
|
13026
|
+
// --- decide (approve/reject) ---
|
|
13027
|
+
async decide(input) {
|
|
13028
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13029
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13030
|
+
if (!requiredText3(input.approverPrincipal)) throw new GovernancePolicyError("approverPrincipal is required");
|
|
13031
|
+
if (input.decision !== "approved" && input.decision !== "rejected") throw new GovernancePolicyError("decision must be approved or rejected");
|
|
13032
|
+
const doc0 = await this.load();
|
|
13033
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13034
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13035
|
+
const scope = policy2.scope;
|
|
13036
|
+
const action = input.decision === "approved" ? "policy.approve" : "policy.reject";
|
|
13037
|
+
if (!await this.hasPermission(input.approverPrincipal, "policy.approve", scope) && !await this.hasPermission(input.approverPrincipal, "policy.reject", scope)) {
|
|
13038
|
+
if (!await this.hasPermission(input.approverPrincipal, "policy.approve", scope)) {
|
|
13039
|
+
await this.recordDenial(doc0, action, input.approverPrincipal, scope, "permission denied");
|
|
13040
|
+
throw new GovernancePolicyError("approver lacks approval permission");
|
|
13041
|
+
}
|
|
13042
|
+
}
|
|
13043
|
+
if (input.approverPrincipal === policy2.proposedBy) {
|
|
13044
|
+
void detectSelfPromoted({ claim: `self-approval ${policy2.policyId}`, channel: "governance.approve" }).catch(() => void 0);
|
|
13045
|
+
await this.recordDenial(doc0, action, input.approverPrincipal, scope, "self-approval denied");
|
|
13046
|
+
throw new GovernancePolicyError("self-approval denied: proposer cannot be sole approver");
|
|
13047
|
+
}
|
|
13048
|
+
if (policy2.status !== "under_review" && policy2.status !== "proposed") {
|
|
13049
|
+
throw new GovernancePolicyError(`cannot decide policy in status ${policy2.status}`);
|
|
13050
|
+
}
|
|
13051
|
+
return this.mutate((doc) => {
|
|
13052
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13053
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13054
|
+
const current = doc.policies[idx];
|
|
13055
|
+
const newStatus = input.decision === "approved" ? "approved" : "rejected";
|
|
13056
|
+
const updated = {
|
|
13057
|
+
...current,
|
|
13058
|
+
status: newStatus,
|
|
13059
|
+
approval: {
|
|
13060
|
+
approverPrincipal: input.approverPrincipal,
|
|
13061
|
+
decision: input.decision,
|
|
13062
|
+
...requiredText3(input.reason) ? { reason: input.reason } : {},
|
|
13063
|
+
at: nowIso2()
|
|
13064
|
+
}
|
|
13065
|
+
};
|
|
13066
|
+
const policies = [...doc.policies];
|
|
13067
|
+
policies[idx] = updated;
|
|
13068
|
+
const revision = doc.revision + 1;
|
|
13069
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13070
|
+
action: input.decision === "approved" ? "policy.approved" : "policy.rejected",
|
|
13071
|
+
at: updated.approval.at,
|
|
13072
|
+
actorPrincipal: input.approverPrincipal,
|
|
13073
|
+
targetPolicyId: updated.policyId,
|
|
13074
|
+
targetVersion: updated.version,
|
|
13075
|
+
scope: updated.scope,
|
|
13076
|
+
outcome: "applied",
|
|
13077
|
+
reason: input.reason,
|
|
13078
|
+
correlationId: input.correlationId
|
|
13079
|
+
})]);
|
|
13080
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13081
|
+
});
|
|
13082
|
+
}
|
|
13083
|
+
// --- activate ---
|
|
13084
|
+
async activate(input) {
|
|
13085
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13086
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13087
|
+
if (!requiredText3(input.activatorPrincipal)) throw new GovernancePolicyError("activatorPrincipal is required");
|
|
13088
|
+
const doc0 = await this.load();
|
|
13089
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13090
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13091
|
+
if (policy2.status !== "approved") throw new GovernancePolicyError(`cannot activate policy in status ${policy2.status}`);
|
|
13092
|
+
const scope = policy2.scope;
|
|
13093
|
+
if (!await this.hasPermission(input.activatorPrincipal, "policy.activate", scope)) {
|
|
13094
|
+
await this.recordDenial(doc0, "policy.activate", input.activatorPrincipal, scope, "permission denied");
|
|
13095
|
+
throw new GovernancePolicyError("activator lacks policy.activate permission");
|
|
13096
|
+
}
|
|
13097
|
+
const now3 = /* @__PURE__ */ new Date();
|
|
13098
|
+
if (policy2.effectiveAt && Date.parse(policy2.effectiveAt) > now3.getTime()) {
|
|
13099
|
+
throw new GovernancePolicyError("policy not yet effective");
|
|
13100
|
+
}
|
|
13101
|
+
if (policy2.expiresAt && Date.parse(policy2.expiresAt) <= now3.getTime()) {
|
|
13102
|
+
throw new GovernancePolicyError("policy expired");
|
|
13103
|
+
}
|
|
13104
|
+
return this.mutate((doc) => {
|
|
13105
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13106
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13107
|
+
const current = doc.policies[idx];
|
|
13108
|
+
if (current.status !== "approved") throw new GovernancePolicyError(`cannot activate policy in status ${current.status}`);
|
|
13109
|
+
const policies = doc.policies.map((p) => {
|
|
13110
|
+
if (p.policyId === current.policyId && p.version !== current.version && p.status === "active") {
|
|
13111
|
+
return { ...p, status: "retired", retiredBy: input.activatorPrincipal, retiredAt: nowIso2(), supersededBy: `${current.policyId}@v${current.version}` };
|
|
13112
|
+
}
|
|
13113
|
+
return p;
|
|
13114
|
+
});
|
|
13115
|
+
const nowStr = nowIso2();
|
|
13116
|
+
policies[idx] = {
|
|
13117
|
+
...current,
|
|
13118
|
+
status: "active",
|
|
13119
|
+
activatedBy: input.activatorPrincipal,
|
|
13120
|
+
activatedAt: nowStr
|
|
13121
|
+
};
|
|
13122
|
+
const updated = policies[idx];
|
|
13123
|
+
const revision = doc.revision + 1;
|
|
13124
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13125
|
+
action: "policy.activated",
|
|
13126
|
+
at: nowStr,
|
|
13127
|
+
actorPrincipal: input.activatorPrincipal,
|
|
13128
|
+
targetPolicyId: updated.policyId,
|
|
13129
|
+
targetVersion: updated.version,
|
|
13130
|
+
scope: updated.scope,
|
|
13131
|
+
outcome: "applied",
|
|
13132
|
+
correlationId: input.correlationId
|
|
13133
|
+
})]);
|
|
13134
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13135
|
+
});
|
|
13136
|
+
}
|
|
13137
|
+
async suspend(input) {
|
|
13138
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13139
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13140
|
+
if (!requiredText3(input.actorPrincipal)) throw new GovernancePolicyError("actorPrincipal is required");
|
|
13141
|
+
const doc0 = await this.load();
|
|
13142
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13143
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13144
|
+
const scope = policy2.scope;
|
|
13145
|
+
if (!await this.hasPermission(input.actorPrincipal, "policy.suspend", scope)) {
|
|
13146
|
+
const role = await this.resolveRoleAsync(input.actorPrincipal, scope);
|
|
13147
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13148
|
+
await this.recordDenial(doc0, "policy.suspend", input.actorPrincipal, scope, "permission denied");
|
|
13149
|
+
throw new GovernancePolicyError("actor lacks suspend permission");
|
|
13150
|
+
}
|
|
13151
|
+
}
|
|
13152
|
+
return this.mutate((doc) => {
|
|
13153
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13154
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13155
|
+
const current = doc.policies[idx];
|
|
13156
|
+
if (current.status !== "active" && current.status !== "approved") throw new GovernancePolicyError(`cannot suspend policy in status ${current.status}`);
|
|
13157
|
+
const updated = {
|
|
13158
|
+
...current,
|
|
13159
|
+
status: "suspended",
|
|
13160
|
+
suspendedBy: input.actorPrincipal,
|
|
13161
|
+
suspendedAt: nowIso2()
|
|
13162
|
+
};
|
|
13163
|
+
const policies = [...doc.policies];
|
|
13164
|
+
policies[idx] = updated;
|
|
13165
|
+
const revision = doc.revision + 1;
|
|
13166
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13167
|
+
action: "policy.suspended",
|
|
13168
|
+
at: updated.suspendedAt,
|
|
13169
|
+
actorPrincipal: input.actorPrincipal,
|
|
13170
|
+
targetPolicyId: updated.policyId,
|
|
13171
|
+
targetVersion: updated.version,
|
|
13172
|
+
scope: updated.scope,
|
|
13173
|
+
outcome: "applied",
|
|
13174
|
+
reason: input.reason,
|
|
13175
|
+
correlationId: input.correlationId
|
|
13176
|
+
})]);
|
|
13177
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13178
|
+
});
|
|
13179
|
+
}
|
|
13180
|
+
async retire(input) {
|
|
13181
|
+
if (!input || !requiredText3(input.policyId)) throw new GovernancePolicyError("policyId is required");
|
|
13182
|
+
if (typeof input.version !== "number") throw new GovernancePolicyError("version is required");
|
|
13183
|
+
if (!requiredText3(input.actorPrincipal)) throw new GovernancePolicyError("actorPrincipal is required");
|
|
13184
|
+
const doc0 = await this.load();
|
|
13185
|
+
const policy2 = doc0.policies.find((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13186
|
+
if (!policy2) throw new GovernancePolicyError("policy not found");
|
|
13187
|
+
const scope = policy2.scope;
|
|
13188
|
+
const role = await this.resolveRoleAsync(input.actorPrincipal, scope);
|
|
13189
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13190
|
+
await this.recordDenial(doc0, "policy.retire", input.actorPrincipal, scope, "permission denied");
|
|
13191
|
+
throw new GovernancePolicyError("actor lacks retire permission");
|
|
13192
|
+
}
|
|
13193
|
+
return this.mutate((doc) => {
|
|
13194
|
+
const idx = doc.policies.findIndex((p) => p.policyId === input.policyId && p.version === input.version);
|
|
13195
|
+
if (idx < 0) throw new GovernancePolicyError("policy not found");
|
|
13196
|
+
const current = doc.policies[idx];
|
|
13197
|
+
const updated = {
|
|
13198
|
+
...current,
|
|
13199
|
+
status: "retired",
|
|
13200
|
+
retiredBy: input.actorPrincipal,
|
|
13201
|
+
retiredAt: nowIso2()
|
|
13202
|
+
};
|
|
13203
|
+
const policies = [...doc.policies];
|
|
13204
|
+
policies[idx] = updated;
|
|
13205
|
+
const revision = doc.revision + 1;
|
|
13206
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13207
|
+
action: "policy.retired",
|
|
13208
|
+
at: updated.retiredAt,
|
|
13209
|
+
actorPrincipal: input.actorPrincipal,
|
|
13210
|
+
targetPolicyId: updated.policyId,
|
|
13211
|
+
targetVersion: updated.version,
|
|
13212
|
+
scope: updated.scope,
|
|
13213
|
+
outcome: "applied",
|
|
13214
|
+
reason: input.reason,
|
|
13215
|
+
correlationId: input.correlationId
|
|
13216
|
+
})]);
|
|
13217
|
+
return { document: { ...doc, revision, policies, audit }, result: updated };
|
|
13218
|
+
});
|
|
13219
|
+
}
|
|
13220
|
+
// --- resolution (fail-closed) ---
|
|
13221
|
+
async resolvePolicy(policyId, version, requestedScope, now3 = /* @__PURE__ */ new Date()) {
|
|
13222
|
+
const doc = await this.load();
|
|
13223
|
+
const policy2 = doc.policies.find((p) => p.policyId === policyId && p.version === version);
|
|
13224
|
+
if (!policy2) {
|
|
13225
|
+
return { outcome: "none", policy: null, reason: "no applicable policy" };
|
|
13226
|
+
}
|
|
13227
|
+
if (!scopeCovers(policy2.scope, requestedScope)) {
|
|
13228
|
+
return { outcome: "insufficient-scope", policy: null, reason: "policy scope does not cover requested scope" };
|
|
13229
|
+
}
|
|
13230
|
+
if (!policy2.ownerAuthority || policy2.ownerAuthority.kind !== "HI" && policy2.ownerAuthority.kind !== "operator") {
|
|
13231
|
+
return { outcome: "unresolved-authority", policy: null, reason: "unresolved or invalid authority" };
|
|
13232
|
+
}
|
|
13233
|
+
if (policy2.effectiveAt && Date.parse(policy2.effectiveAt) > now3.getTime()) {
|
|
13234
|
+
return { outcome: "not-yet-effective", policy: null, reason: "policy not yet effective" };
|
|
13235
|
+
}
|
|
13236
|
+
if (policy2.expiresAt && Date.parse(policy2.expiresAt) <= now3.getTime()) {
|
|
13237
|
+
return { outcome: "expired", policy: null, reason: "policy expired" };
|
|
13238
|
+
}
|
|
13239
|
+
if (policy2.status !== "active") {
|
|
13240
|
+
return { outcome: "none", policy: null, reason: `policy status ${policy2.status} is not active` };
|
|
13241
|
+
}
|
|
13242
|
+
if (policy2.fingerprint && policy2.fingerprint !== `${policy2.policyId}@v${policy2.version}`) {
|
|
13243
|
+
return { outcome: "invalid-provenance", policy: null, reason: "fingerprint mismatch" };
|
|
13244
|
+
}
|
|
13245
|
+
return { outcome: "applicable", policy: policy2, reason: "applicable and valid" };
|
|
13246
|
+
}
|
|
13247
|
+
async resolveActivePolicies(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13248
|
+
const doc = await this.load();
|
|
13249
|
+
const active = doc.policies.filter((p) => p.status === "active" && scopeCovers(p.scope, scope));
|
|
13250
|
+
return active.filter((p) => {
|
|
13251
|
+
if (p.effectiveAt && Date.parse(p.effectiveAt) > now3.getTime()) return false;
|
|
13252
|
+
if (p.expiresAt && Date.parse(p.expiresAt) <= now3.getTime()) return false;
|
|
13253
|
+
return true;
|
|
13254
|
+
});
|
|
13255
|
+
}
|
|
13256
|
+
async checkConflicts(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13257
|
+
const actives = await this.resolveActivePolicies(scope, now3);
|
|
13258
|
+
const conflicting = actives.length > 1;
|
|
13259
|
+
return { conflicting, policies: actives };
|
|
13260
|
+
}
|
|
13261
|
+
async failClosedResolve(scope, now3 = /* @__PURE__ */ new Date()) {
|
|
13262
|
+
const actives = await this.resolveActivePolicies(scope, now3);
|
|
13263
|
+
if (actives.length === 0) {
|
|
13264
|
+
return { outcome: "none", policy: null, reason: "no applicable policy" };
|
|
13265
|
+
}
|
|
13266
|
+
if (actives.length > 1) {
|
|
13267
|
+
return { outcome: "conflicting", policy: null, reason: "conflicting active policies" };
|
|
13268
|
+
}
|
|
13269
|
+
const p = actives[0];
|
|
13270
|
+
if (!p.ownerAuthority || p.ownerAuthority.kind !== "HI" && p.ownerAuthority.kind !== "operator") {
|
|
13271
|
+
return { outcome: "unresolved-authority", policy: null, reason: "unresolved authority" };
|
|
13272
|
+
}
|
|
13273
|
+
return { outcome: "applicable", policy: p, reason: "applicable and valid" };
|
|
13274
|
+
}
|
|
13275
|
+
// --- authorization granted/denied audit ---
|
|
13276
|
+
async recordAuthorization(decision) {
|
|
13277
|
+
await this.mutate((doc) => {
|
|
13278
|
+
const revision = doc.revision + 1;
|
|
13279
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13280
|
+
action: decision.allowed ? "authorization.granted" : "authorization.denied",
|
|
13281
|
+
at: nowIso2(),
|
|
13282
|
+
actorPrincipal: decision.principalId,
|
|
13283
|
+
scope: decision.scope ?? decision.resource,
|
|
13284
|
+
outcome: decision.allowed ? "applied" : "rejected",
|
|
13285
|
+
reason: decision.reason,
|
|
13286
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13287
|
+
correlationId: decision.correlationId
|
|
13288
|
+
})]);
|
|
13289
|
+
return { document: { ...doc, revision, audit }, result: void 0 };
|
|
13290
|
+
});
|
|
13291
|
+
}
|
|
13292
|
+
// --- list ---
|
|
13293
|
+
async listPolicies() {
|
|
13294
|
+
const doc = await this.load();
|
|
13295
|
+
return [...doc.policies].sort((a, b) => a.policyId.localeCompare(b.policyId) || a.version - b.version);
|
|
13296
|
+
}
|
|
13297
|
+
async listRoleBindings() {
|
|
13298
|
+
const doc = await this.load();
|
|
13299
|
+
return [...doc.roleBindings];
|
|
13300
|
+
}
|
|
13301
|
+
async auditTrail() {
|
|
13302
|
+
const doc = await this.load();
|
|
13303
|
+
return [...doc.audit];
|
|
13304
|
+
}
|
|
13305
|
+
async recordAuditEvent(input) {
|
|
13306
|
+
await this.mutate((doc) => {
|
|
13307
|
+
const revision = doc.revision + 1;
|
|
13308
|
+
const audit = boundedAudit2([...doc.audit, buildAudit({
|
|
13309
|
+
action: input.action,
|
|
13310
|
+
at: input.at ?? nowIso2(),
|
|
13311
|
+
...input.actorPrincipal !== void 0 ? { actorPrincipal: input.actorPrincipal } : {},
|
|
13312
|
+
...input.actorRole !== void 0 ? { actorRole: input.actorRole } : {},
|
|
13313
|
+
...input.targetPolicyId !== void 0 ? { targetPolicyId: input.targetPolicyId } : {},
|
|
13314
|
+
...input.targetVersion !== void 0 ? { targetVersion: input.targetVersion } : {},
|
|
13315
|
+
...input.scope !== void 0 ? { scope: input.scope } : {},
|
|
13316
|
+
outcome: input.outcome,
|
|
13317
|
+
...input.reason !== void 0 ? { reason: input.reason } : {},
|
|
13318
|
+
...input.detail !== void 0 ? { detail: input.detail } : {},
|
|
13319
|
+
...input.correlationId !== void 0 ? { correlationId: input.correlationId } : {},
|
|
13320
|
+
...input.mandatory !== void 0 ? { mandatory: input.mandatory } : {}
|
|
13321
|
+
})]);
|
|
13322
|
+
return { document: { ...doc, revision, audit }, result: void 0 };
|
|
13323
|
+
});
|
|
13324
|
+
}
|
|
13325
|
+
};
|
|
13326
|
+
var roleHasPermission = /* @__PURE__ */ __name((role, action) => ROLE_CAPABILITIES[role].has(action), "roleHasPermission");
|
|
13327
|
+
var GOVERNANCE_ROLES = [
|
|
13328
|
+
"human-owner",
|
|
13329
|
+
"human-approver",
|
|
13330
|
+
"coordinator-jo",
|
|
13331
|
+
"specialist-agent",
|
|
13332
|
+
"policy-authority",
|
|
13333
|
+
"auditor-reviewer"
|
|
13334
|
+
];
|
|
13335
|
+
var GOVERNANCE_POLICY_STATUSES = [
|
|
13336
|
+
"proposed",
|
|
13337
|
+
"under_review",
|
|
13338
|
+
"approved",
|
|
13339
|
+
"active",
|
|
13340
|
+
"rejected",
|
|
13341
|
+
"suspended",
|
|
13342
|
+
"retired"
|
|
13343
|
+
];
|
|
13344
|
+
|
|
13345
|
+
// core/guardrails/index.ts
|
|
13346
|
+
init_user_paths();
|
|
13347
|
+
init_policy_namespace_lock();
|
|
13348
|
+
var STOP_FILE_NAME = "guardrails.stop.json";
|
|
13349
|
+
var STOP_FORMAT = "dotmem.guardrails-stop";
|
|
13350
|
+
var STOP_VERSION = 1;
|
|
13351
|
+
var GuardrailError = class extends Error {
|
|
13352
|
+
static {
|
|
13353
|
+
__name(this, "GuardrailError");
|
|
13354
|
+
}
|
|
13355
|
+
constructor(message) {
|
|
13356
|
+
super(message);
|
|
13357
|
+
this.name = "GuardrailError";
|
|
13358
|
+
}
|
|
13359
|
+
};
|
|
13360
|
+
var requiredText4 = /* @__PURE__ */ __name((v) => typeof v === "string" && v.trim().length > 0, "requiredText");
|
|
13361
|
+
var nowIso3 = /* @__PURE__ */ __name(() => (/* @__PURE__ */ new Date()).toISOString(), "nowIso");
|
|
13362
|
+
var joinPath8 = /* @__PURE__ */ __name((base, child) => {
|
|
13363
|
+
const sep = process.platform === "win32" ? "\\" : "/";
|
|
13364
|
+
return `${base.replace(/[\\/]+/g, sep)}${sep}${child}`;
|
|
13365
|
+
}, "joinPath");
|
|
13366
|
+
var resolveGuardrailDir = /* @__PURE__ */ __name((directory) => directory ?? joinPath8(resolveUserDotMemPath(), "policy"), "resolveGuardrailDir");
|
|
13367
|
+
var resolveStopFilePath = /* @__PURE__ */ __name((directory) => joinPath8(resolveGuardrailDir(directory), STOP_FILE_NAME), "resolveStopFilePath");
|
|
13368
|
+
var ACTIONS_REQUIRING_TRUSTED_IDENTITY = /* @__PURE__ */ new Set([
|
|
13369
|
+
"agent.execute",
|
|
13370
|
+
"guardrail.stop.activate",
|
|
13371
|
+
"guardrail.stop.release"
|
|
13372
|
+
]);
|
|
13373
|
+
var GuardrailService = class {
|
|
13374
|
+
static {
|
|
13375
|
+
__name(this, "GuardrailService");
|
|
13376
|
+
}
|
|
13377
|
+
directory;
|
|
13378
|
+
governance;
|
|
13379
|
+
constructor(directory, governance) {
|
|
13380
|
+
this.directory = directory;
|
|
13381
|
+
this.governance = governance ?? new GovernancePolicyStore(directory);
|
|
13382
|
+
}
|
|
13383
|
+
dir() {
|
|
13384
|
+
return resolveGuardrailDir(this.directory);
|
|
13385
|
+
}
|
|
13386
|
+
// --- stop controller persistence ---
|
|
13387
|
+
async readStopState() {
|
|
13388
|
+
const fs = await import('fs/promises');
|
|
13389
|
+
let raw;
|
|
13390
|
+
try {
|
|
13391
|
+
raw = await fs.readFile(resolveStopFilePath(this.directory), "utf8");
|
|
13392
|
+
} catch (err2) {
|
|
13393
|
+
if (err2.code === "ENOENT") {
|
|
13394
|
+
return { stopped: false, revision: 0 };
|
|
13395
|
+
}
|
|
13396
|
+
throw err2;
|
|
13397
|
+
}
|
|
13398
|
+
const parsed = JSON.parse(raw);
|
|
13399
|
+
if (parsed.format !== STOP_FORMAT || parsed.version !== STOP_VERSION) {
|
|
13400
|
+
throw new GuardrailError("invalid stop state document");
|
|
13401
|
+
}
|
|
13402
|
+
return {
|
|
13403
|
+
stopped: parsed.stopped === true,
|
|
13404
|
+
...requiredText4(parsed.activatedBy) ? { activatedBy: parsed.activatedBy } : {},
|
|
13405
|
+
...requiredText4(parsed.activatedAt) ? { activatedAt: parsed.activatedAt } : {},
|
|
13406
|
+
...requiredText4(parsed.reason) ? { reason: parsed.reason } : {},
|
|
13407
|
+
...requiredText4(parsed.releasedBy) ? { releasedBy: parsed.releasedBy } : {},
|
|
13408
|
+
...requiredText4(parsed.releasedAt) ? { releasedAt: parsed.releasedAt } : {},
|
|
13409
|
+
revision: typeof parsed.revision === "number" ? parsed.revision : 0
|
|
13410
|
+
};
|
|
13411
|
+
}
|
|
13412
|
+
async writeStopState(state3) {
|
|
13413
|
+
const fs = await import('fs/promises');
|
|
13414
|
+
const dir = this.dir();
|
|
13415
|
+
await fs.mkdir(dir, { recursive: true });
|
|
13416
|
+
const fp = resolveStopFilePath(this.directory);
|
|
13417
|
+
const tmp = `${fp}.${process.pid}.${Date.now().toString(36)}.tmp`;
|
|
13418
|
+
const doc = { format: STOP_FORMAT, version: STOP_VERSION, ...state3 };
|
|
13419
|
+
try {
|
|
13420
|
+
await fs.writeFile(tmp, `${JSON.stringify(doc, null, 2)}
|
|
13421
|
+
`, "utf8");
|
|
13422
|
+
const handle = await fs.open(tmp, "r+");
|
|
13423
|
+
try {
|
|
13424
|
+
await handle.sync();
|
|
13425
|
+
} finally {
|
|
13426
|
+
await handle.close();
|
|
13427
|
+
}
|
|
13428
|
+
await fs.rename(tmp, fp);
|
|
13429
|
+
} catch (err2) {
|
|
13430
|
+
await fs.rm(tmp, { force: true }).catch(() => void 0);
|
|
13431
|
+
throw err2;
|
|
13432
|
+
}
|
|
13433
|
+
}
|
|
13434
|
+
async getStopState() {
|
|
13435
|
+
return this.readStopState();
|
|
13436
|
+
}
|
|
13437
|
+
async isStopped() {
|
|
13438
|
+
const s = await this.readStopState();
|
|
13439
|
+
return s.stopped;
|
|
13440
|
+
}
|
|
13441
|
+
async activateStop(input) {
|
|
13442
|
+
if (!requiredText4(input.actorPrincipal)) throw new GuardrailError("actorPrincipal is required");
|
|
13443
|
+
const role = await this.governance.resolveRoleAsync(input.actorPrincipal, "*");
|
|
13444
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13445
|
+
await this.governance.recordAuditEvent({
|
|
13446
|
+
action: "guardrail.stop.activate.denied",
|
|
13447
|
+
actorPrincipal: input.actorPrincipal,
|
|
13448
|
+
actorRole: role.role ?? void 0,
|
|
13449
|
+
outcome: "rejected",
|
|
13450
|
+
reason: "insufficient authority to activate stop",
|
|
13451
|
+
correlationId: input.correlationId
|
|
13452
|
+
}).catch(() => void 0);
|
|
13453
|
+
throw new GuardrailError("unauthorized stop activation");
|
|
13454
|
+
}
|
|
13455
|
+
const current = await this.readStopState();
|
|
13456
|
+
if (current.stopped) {
|
|
13457
|
+
await this.governance.recordAuditEvent({
|
|
13458
|
+
action: "guardrail.stop.activate",
|
|
13459
|
+
actorPrincipal: input.actorPrincipal,
|
|
13460
|
+
actorRole: role.role ?? void 0,
|
|
13461
|
+
outcome: "applied",
|
|
13462
|
+
reason: "already stopped (idempotent)",
|
|
13463
|
+
correlationId: input.correlationId
|
|
13464
|
+
}).catch(() => void 0);
|
|
13465
|
+
return current;
|
|
13466
|
+
}
|
|
13467
|
+
const now3 = nowIso3();
|
|
13468
|
+
const next = {
|
|
13469
|
+
stopped: true,
|
|
13470
|
+
activatedBy: input.actorPrincipal,
|
|
13471
|
+
activatedAt: now3,
|
|
13472
|
+
...requiredText4(input.reason) ? { reason: input.reason } : {},
|
|
13473
|
+
revision: current.revision + 1
|
|
13474
|
+
};
|
|
13475
|
+
await this.governance.recordAuditEvent({
|
|
13476
|
+
action: "guardrail.stop.activated",
|
|
13477
|
+
actorPrincipal: input.actorPrincipal,
|
|
13478
|
+
actorRole: role.role ?? void 0,
|
|
13479
|
+
outcome: "applied",
|
|
13480
|
+
reason: input.reason,
|
|
13481
|
+
correlationId: input.correlationId
|
|
13482
|
+
});
|
|
13483
|
+
await withPolicyNamespaceLock(async (session3) => {
|
|
13484
|
+
await this.writeStopState(next);
|
|
13485
|
+
await session3.assertOwnership();
|
|
13486
|
+
}, { directory: this.dir() });
|
|
13487
|
+
return next;
|
|
13488
|
+
}
|
|
13489
|
+
async releaseStop(input) {
|
|
13490
|
+
if (!requiredText4(input.actorPrincipal)) throw new GuardrailError("actorPrincipal is required");
|
|
13491
|
+
const role = await this.governance.resolveRoleAsync(input.actorPrincipal, "*");
|
|
13492
|
+
if (role.role !== "human-owner" && role.role !== "human-approver") {
|
|
13493
|
+
await this.governance.recordAuditEvent({
|
|
13494
|
+
action: "guardrail.stop.release.denied",
|
|
13495
|
+
actorPrincipal: input.actorPrincipal,
|
|
13496
|
+
actorRole: role.role ?? void 0,
|
|
13497
|
+
outcome: "rejected",
|
|
13498
|
+
reason: "insufficient authority to release stop",
|
|
13499
|
+
correlationId: input.correlationId
|
|
13500
|
+
}).catch(() => void 0);
|
|
13501
|
+
throw new GuardrailError("unauthorized stop release");
|
|
13502
|
+
}
|
|
13503
|
+
const current = await this.readStopState();
|
|
13504
|
+
if (!current.stopped) {
|
|
13505
|
+
await this.governance.recordAuditEvent({
|
|
13506
|
+
action: "guardrail.stop.release",
|
|
13507
|
+
actorPrincipal: input.actorPrincipal,
|
|
13508
|
+
actorRole: role.role ?? void 0,
|
|
13509
|
+
outcome: "applied",
|
|
13510
|
+
reason: "already running (idempotent)",
|
|
13511
|
+
correlationId: input.correlationId
|
|
13512
|
+
}).catch(() => void 0);
|
|
13513
|
+
return current;
|
|
13514
|
+
}
|
|
13515
|
+
const now3 = nowIso3();
|
|
13516
|
+
const next = {
|
|
13517
|
+
stopped: false,
|
|
13518
|
+
activatedBy: current.activatedBy,
|
|
13519
|
+
activatedAt: current.activatedAt,
|
|
13520
|
+
reason: current.reason,
|
|
13521
|
+
releasedBy: input.actorPrincipal,
|
|
13522
|
+
releasedAt: now3,
|
|
13523
|
+
revision: current.revision + 1
|
|
13524
|
+
};
|
|
13525
|
+
await this.governance.recordAuditEvent({
|
|
13526
|
+
action: "guardrail.stop.released",
|
|
13527
|
+
actorPrincipal: input.actorPrincipal,
|
|
13528
|
+
actorRole: role.role ?? void 0,
|
|
13529
|
+
outcome: "applied",
|
|
13530
|
+
reason: input.reason,
|
|
13531
|
+
correlationId: input.correlationId
|
|
13532
|
+
});
|
|
13533
|
+
await withPolicyNamespaceLock(async (session3) => {
|
|
13534
|
+
await this.writeStopState(next);
|
|
13535
|
+
await session3.assertOwnership();
|
|
13536
|
+
}, { directory: this.dir() });
|
|
13537
|
+
return next;
|
|
13538
|
+
}
|
|
13539
|
+
auditModes = {
|
|
13540
|
+
"agent.execute": "mandatory",
|
|
13541
|
+
"guardrail.stop.activate": "mandatory",
|
|
13542
|
+
"guardrail.stop.release": "mandatory"
|
|
13543
|
+
};
|
|
13544
|
+
auditModeFor(action) {
|
|
13545
|
+
return this.auditModes[action] ?? "best_effort";
|
|
13546
|
+
}
|
|
13547
|
+
async recordAudit(decision, auditMode) {
|
|
13548
|
+
const persisted = await this.auditDecision(decision);
|
|
13549
|
+
decision.auditPersisted = persisted;
|
|
13550
|
+
decision.auditMode = auditMode;
|
|
13551
|
+
if (!persisted && auditMode === "mandatory" && decision.allowed) {
|
|
13552
|
+
decision.outcome = "audit_failure";
|
|
13553
|
+
decision.allowed = false;
|
|
13554
|
+
decision.reason = "mandatory audit persistence failed";
|
|
13555
|
+
}
|
|
13556
|
+
return persisted;
|
|
13557
|
+
}
|
|
13558
|
+
// --- evaluation ---
|
|
13559
|
+
async evaluate(input) {
|
|
13560
|
+
const evaluatedAt = nowIso3();
|
|
13561
|
+
const auditMode = this.auditModeFor(input.action);
|
|
13562
|
+
const principalId = input.principalContext.trusted ? input.principalContext.trustedPrincipalId : input.principalContext.assertedPrincipalId;
|
|
13563
|
+
const stopState = await this.readStopState().catch(() => ({ stopped: false, revision: 0 }));
|
|
13564
|
+
const base = {
|
|
13565
|
+
outcome: "deny",
|
|
13566
|
+
allowed: false,
|
|
13567
|
+
reason: "",
|
|
13568
|
+
evaluatedAt,
|
|
13569
|
+
principalId,
|
|
13570
|
+
action: input.action,
|
|
13571
|
+
resource: input.resource,
|
|
13572
|
+
scope: input.scope,
|
|
13573
|
+
phase: input.phase,
|
|
13574
|
+
stopState: stopState.stopped ? "stopped" : "running",
|
|
13575
|
+
correlationId: input.correlationId,
|
|
13576
|
+
auditMode,
|
|
13577
|
+
auditPersisted: false
|
|
13578
|
+
};
|
|
13579
|
+
if (stopState.stopped && input.action !== "guardrail.stop.release") {
|
|
13580
|
+
const decision2 = { ...base, outcome: "stop_active", reason: "global stop active" };
|
|
13581
|
+
await this.recordAudit(decision2, auditMode);
|
|
13582
|
+
return decision2;
|
|
13583
|
+
}
|
|
13584
|
+
const requiresTrusted = ACTIONS_REQUIRING_TRUSTED_IDENTITY.has(input.action);
|
|
13585
|
+
if (requiresTrusted && !input.principalContext.trusted) {
|
|
13586
|
+
const decision2 = { ...base, outcome: "identity_unverified", reason: "trusted identity required but not verified" };
|
|
13587
|
+
await this.recordAudit(decision2, auditMode);
|
|
13588
|
+
return decision2;
|
|
13589
|
+
}
|
|
13590
|
+
if (!requiredText4(input.action) || !requiredText4(input.resource) || !requiredText4(input.scope)) {
|
|
13591
|
+
const decision2 = { ...base, outcome: "invalid_context", reason: "missing action/resource/scope" };
|
|
13592
|
+
await this.recordAudit(decision2, auditMode);
|
|
13593
|
+
return decision2;
|
|
13594
|
+
}
|
|
13595
|
+
let policyId;
|
|
13596
|
+
let policyVersion;
|
|
13597
|
+
try {
|
|
13598
|
+
const resolution = await this.governance.failClosedResolve(input.scope, input.now ?? /* @__PURE__ */ new Date());
|
|
13599
|
+
if (resolution.outcome !== "applicable" || !resolution.policy) {
|
|
13600
|
+
const decision2 = {
|
|
13601
|
+
...base,
|
|
13602
|
+
outcome: resolution.outcome === "conflicting" ? "deny" : "policy_unavailable",
|
|
13603
|
+
reason: `policy resolution: ${resolution.outcome} - ${resolution.reason}`
|
|
13604
|
+
};
|
|
13605
|
+
await this.recordAudit(decision2, auditMode);
|
|
13606
|
+
return decision2;
|
|
13607
|
+
}
|
|
13608
|
+
policyId = resolution.policy.policyId;
|
|
13609
|
+
policyVersion = resolution.policy.version;
|
|
13610
|
+
const actorForPermission = principalId ?? "";
|
|
13611
|
+
const allowed = await this.governance.hasPermission(actorForPermission, input.action, input.scope);
|
|
13612
|
+
if (!allowed) {
|
|
13613
|
+
const decision2 = {
|
|
13614
|
+
...base,
|
|
13615
|
+
outcome: "deny",
|
|
13616
|
+
reason: "permission denied by governance role binding",
|
|
13617
|
+
policyId,
|
|
13618
|
+
policyVersion
|
|
13619
|
+
};
|
|
13620
|
+
await this.recordAudit(decision2, auditMode);
|
|
13621
|
+
return decision2;
|
|
13622
|
+
}
|
|
13623
|
+
} catch (err2) {
|
|
13624
|
+
const decision2 = {
|
|
13625
|
+
...base,
|
|
13626
|
+
outcome: "policy_unavailable",
|
|
13627
|
+
reason: `governance unavailable: ${err2.message}`
|
|
13628
|
+
};
|
|
13629
|
+
await this.recordAudit(decision2, auditMode);
|
|
13630
|
+
return decision2;
|
|
13631
|
+
}
|
|
13632
|
+
const decision = {
|
|
13633
|
+
...base,
|
|
13634
|
+
outcome: "allow",
|
|
13635
|
+
allowed: true,
|
|
13636
|
+
reason: "allowed by governance policy",
|
|
13637
|
+
policyId,
|
|
13638
|
+
policyVersion
|
|
13639
|
+
};
|
|
13640
|
+
await this.recordAudit(decision, auditMode);
|
|
13641
|
+
return decision;
|
|
13642
|
+
}
|
|
13643
|
+
async auditDecision(decision) {
|
|
13644
|
+
try {
|
|
13645
|
+
await this.governance.recordAuditEvent({
|
|
13646
|
+
action: decision.allowed ? "guardrail.evaluation.allowed" : "guardrail.evaluation.denied",
|
|
13647
|
+
actorPrincipal: decision.principalId,
|
|
13648
|
+
scope: decision.scope,
|
|
13649
|
+
outcome: decision.allowed ? "applied" : "rejected",
|
|
13650
|
+
reason: decision.reason,
|
|
13651
|
+
detail: `${decision.action} on ${decision.resource} (${decision.outcome})`,
|
|
13652
|
+
correlationId: decision.correlationId,
|
|
13653
|
+
mandatory: decision.auditMode === "mandatory"
|
|
13654
|
+
});
|
|
13655
|
+
return true;
|
|
13656
|
+
} catch {
|
|
13657
|
+
return false;
|
|
13658
|
+
}
|
|
13659
|
+
}
|
|
13660
|
+
async execute(input, operation) {
|
|
13661
|
+
const decision = await this.evaluate(input);
|
|
13662
|
+
if (!decision.allowed) {
|
|
13663
|
+
return { decision, executed: false, error: decision.reason };
|
|
13664
|
+
}
|
|
13665
|
+
await this.governance.recordAuditEvent({
|
|
13666
|
+
action: "guardrail.operation.started",
|
|
13667
|
+
actorPrincipal: decision.principalId,
|
|
13668
|
+
scope: decision.scope,
|
|
13669
|
+
outcome: "applied",
|
|
13670
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13671
|
+
correlationId: decision.correlationId
|
|
13672
|
+
}).catch(() => void 0);
|
|
13673
|
+
let result;
|
|
13674
|
+
try {
|
|
13675
|
+
result = await operation();
|
|
13676
|
+
} catch (err2) {
|
|
13677
|
+
await this.governance.recordAuditEvent({
|
|
13678
|
+
action: "guardrail.operation.failed",
|
|
13679
|
+
actorPrincipal: decision.principalId,
|
|
13680
|
+
scope: decision.scope,
|
|
13681
|
+
outcome: "rejected",
|
|
13682
|
+
reason: err2.message,
|
|
13683
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13684
|
+
correlationId: decision.correlationId
|
|
13685
|
+
}).catch(() => void 0);
|
|
13686
|
+
return { decision, executed: true, error: err2.message };
|
|
13687
|
+
}
|
|
13688
|
+
await this.governance.recordAuditEvent({
|
|
13689
|
+
action: "guardrail.operation.completed",
|
|
13690
|
+
actorPrincipal: decision.principalId,
|
|
13691
|
+
scope: decision.scope,
|
|
13692
|
+
outcome: "applied",
|
|
13693
|
+
detail: `${decision.action} on ${decision.resource}`,
|
|
13694
|
+
correlationId: decision.correlationId
|
|
13695
|
+
}).catch(() => void 0);
|
|
13696
|
+
return { decision, executed: true, result };
|
|
13697
|
+
}
|
|
13698
|
+
};
|
|
13699
|
+
function createGuardrailService(directory, governance) {
|
|
13700
|
+
return new GuardrailService(directory, governance);
|
|
13701
|
+
}
|
|
13702
|
+
__name(createGuardrailService, "createGuardrailService");
|
|
13703
|
+
|
|
13704
|
+
// core/memory-anthology/index.ts
|
|
13705
|
+
var VALID_TRANSITIONS = {
|
|
13706
|
+
unassessed: ["asserted", "corroborated", "reviewed", "disputed", "refuted", "superseded"],
|
|
13707
|
+
asserted: ["corroborated", "reviewed", "disputed", "refuted", "superseded"],
|
|
13708
|
+
corroborated: ["reviewed", "disputed", "refuted", "superseded"],
|
|
13709
|
+
reviewed: ["disputed", "refuted", "superseded"],
|
|
13710
|
+
disputed: ["corroborated", "reviewed", "refuted", "superseded"],
|
|
13711
|
+
refuted: ["superseded"],
|
|
13712
|
+
superseded: []
|
|
13713
|
+
};
|
|
13714
|
+
function canTransitionEpistemic(from, to) {
|
|
13715
|
+
if (from === to) return true;
|
|
13716
|
+
return (VALID_TRANSITIONS[from] ?? []).includes(to);
|
|
13717
|
+
}
|
|
13718
|
+
__name(canTransitionEpistemic, "canTransitionEpistemic");
|
|
13719
|
+
function validateEpistemicTransition(from, to, reason) {
|
|
13720
|
+
if (from === to) return { allowed: true, reason: "no change" };
|
|
13721
|
+
if (!canTransitionEpistemic(from, to)) {
|
|
13722
|
+
return { allowed: false, reason: `invalid transition ${from} -> ${to}` };
|
|
13723
|
+
}
|
|
13724
|
+
if (to === "corroborated" && !reason) {
|
|
13725
|
+
return { allowed: false, reason: "corroborated requires explanation of independent sources" };
|
|
13726
|
+
}
|
|
13727
|
+
if (to === "reviewed" && !reason) {
|
|
13728
|
+
return { allowed: false, reason: "reviewed requires reviewer identity and scope" };
|
|
13729
|
+
}
|
|
13730
|
+
return { allowed: true, reason: reason ?? "ok" };
|
|
13731
|
+
}
|
|
13732
|
+
__name(validateEpistemicTransition, "validateEpistemicTransition");
|
|
13733
|
+
function validateProvenance(p) {
|
|
13734
|
+
const errors = [];
|
|
13735
|
+
if (!p || typeof p !== "object") {
|
|
13736
|
+
errors.push("provenance must be an object");
|
|
13737
|
+
return { valid: false, errors };
|
|
13738
|
+
}
|
|
13739
|
+
const kinds = ["user-statement", "system-observation", "imported-document", "external-source", "agent-inference", "model-summary", "human-correction", "system-event", "unknown"];
|
|
13740
|
+
if (!kinds.includes(p.kind)) errors.push("invalid provenance kind");
|
|
13741
|
+
if (p.observedAt !== void 0 && (typeof p.observedAt !== "string" || isNaN(Date.parse(p.observedAt)))) {
|
|
13742
|
+
errors.push("observedAt must be valid ISO timestamp if provided");
|
|
13743
|
+
}
|
|
13744
|
+
return { valid: errors.length === 0, errors };
|
|
13745
|
+
}
|
|
13746
|
+
__name(validateProvenance, "validateProvenance");
|
|
13747
|
+
function validateMemoryItem(item) {
|
|
13748
|
+
const errors = [];
|
|
13749
|
+
if (!item || typeof item !== "object") {
|
|
13750
|
+
errors.push("item must be an object");
|
|
13751
|
+
return { valid: false, errors };
|
|
13752
|
+
}
|
|
13753
|
+
if (typeof item.itemId !== "string" || !item.itemId.trim()) errors.push("itemId is required");
|
|
13754
|
+
if (typeof item.schemaVersion !== "number" || !Number.isInteger(item.schemaVersion) || item.schemaVersion < 1) errors.push("schemaVersion must be positive integer");
|
|
13755
|
+
if (typeof item.scope !== "string" || !item.scope.trim()) errors.push("scope is required");
|
|
13756
|
+
if (!item.temporal || typeof item.temporal !== "object") errors.push("temporal is required");
|
|
13757
|
+
else if (typeof item.temporal.recordedAt !== "string" || isNaN(Date.parse(item.temporal.recordedAt))) errors.push("recordedAt must be valid ISO timestamp");
|
|
13758
|
+
if (!item.provenance) errors.push("provenance is required");
|
|
13759
|
+
else {
|
|
13760
|
+
const pv = validateProvenance(item.provenance);
|
|
13761
|
+
if (!pv.valid) errors.push(...pv.errors);
|
|
13762
|
+
}
|
|
13763
|
+
if (!item.revision || typeof item.revision !== "object") errors.push("revision is required");
|
|
13764
|
+
else if (typeof item.revision.revision !== "number" || item.revision.revision < 0) errors.push("revision.revision must be non-negative integer");
|
|
13765
|
+
if (item.category === "derived-summary") {
|
|
13766
|
+
if (!item.derivedLineage) errors.push("derived summary requires lineage");
|
|
13767
|
+
}
|
|
13768
|
+
return { valid: errors.length === 0, errors };
|
|
13769
|
+
}
|
|
13770
|
+
__name(validateMemoryItem, "validateMemoryItem");
|
|
13771
|
+
function validateDerivedLineage(lineage, _sourceStatuses) {
|
|
13772
|
+
const errors = [];
|
|
13773
|
+
if (!lineage || typeof lineage !== "object") {
|
|
13774
|
+
errors.push("lineage required");
|
|
13775
|
+
return { valid: false, errors };
|
|
13776
|
+
}
|
|
13777
|
+
if (!Array.isArray(lineage.sourceItemIds) || lineage.sourceItemIds.length === 0) errors.push("sourceItemIds required");
|
|
13778
|
+
if (!Array.isArray(lineage.sourceRevisions) || lineage.sourceRevisions.length !== lineage.sourceItemIds.length) errors.push("sourceRevisions must match sourceItemIds length");
|
|
13779
|
+
if (typeof lineage.derivedAt !== "string" || isNaN(Date.parse(lineage.derivedAt))) errors.push("derivedAt invalid");
|
|
13780
|
+
if (typeof lineage.producingComponent !== "string" || !lineage.producingComponent.trim()) errors.push("producingComponent required");
|
|
13781
|
+
return { valid: errors.length === 0, errors };
|
|
13782
|
+
}
|
|
13783
|
+
__name(validateDerivedLineage, "validateDerivedLineage");
|
|
13784
|
+
function markDerivedStale(lineage) {
|
|
13785
|
+
return { ...lineage, stale: true };
|
|
13786
|
+
}
|
|
13787
|
+
__name(markDerivedStale, "markDerivedStale");
|
|
13788
|
+
function isLegacyRecord(item) {
|
|
13789
|
+
return !item || typeof item !== "object" || !item.provenance || !item.temporal || !item.revision;
|
|
13790
|
+
}
|
|
13791
|
+
__name(isLegacyRecord, "isLegacyRecord");
|
|
13792
|
+
function normalizeLegacyRecord(item) {
|
|
13793
|
+
return {
|
|
13794
|
+
itemId: item?.id ?? item?.itemId ?? `legacy-${Math.random().toString(36).slice(2, 10)}`,
|
|
13795
|
+
schemaVersion: 0,
|
|
13796
|
+
category: "legacy",
|
|
13797
|
+
payload: item,
|
|
13798
|
+
scope: item?.scope ?? "unknown",
|
|
13799
|
+
provenance: { kind: "unknown" },
|
|
13800
|
+
epistemicStatus: "unassessed",
|
|
13801
|
+
sensitivity: "unknown",
|
|
13802
|
+
temporal: { recordedAt: item?.createdAt ? new Date(item.createdAt).toISOString() : (/* @__PURE__ */ new Date(0)).toISOString() },
|
|
13803
|
+
revision: { revision: 0 }
|
|
13804
|
+
};
|
|
13805
|
+
}
|
|
13806
|
+
__name(normalizeLegacyRecord, "normalizeLegacyRecord");
|
|
13807
|
+
var MEMORY_CATEGORIES = [
|
|
13808
|
+
"observation",
|
|
13809
|
+
"claim",
|
|
13810
|
+
"fact-candidate",
|
|
13811
|
+
"hypothesis",
|
|
13812
|
+
"decision",
|
|
13813
|
+
"preference",
|
|
13814
|
+
"instruction",
|
|
13815
|
+
"experience",
|
|
13816
|
+
"goal",
|
|
13817
|
+
"relationship",
|
|
13818
|
+
"derived-summary",
|
|
13819
|
+
"unknown-disputed",
|
|
13820
|
+
"legacy"
|
|
13821
|
+
];
|
|
13822
|
+
var EPITEMIC_STATUSES2 = [
|
|
13823
|
+
"unassessed",
|
|
13824
|
+
"asserted",
|
|
13825
|
+
"corroborated",
|
|
13826
|
+
"reviewed",
|
|
13827
|
+
"disputed",
|
|
13828
|
+
"refuted",
|
|
13829
|
+
"superseded"
|
|
13830
|
+
];
|
|
13831
|
+
|
|
13832
|
+
// index.ts
|
|
12025
13833
|
var dotmem = {
|
|
12026
13834
|
version: "0.0.8",
|
|
12027
13835
|
isBrowser,
|
|
@@ -12056,18 +13864,29 @@ Object.defineProperty(exports, "store", {
|
|
|
12056
13864
|
exports.CompactionEngine = CompactionEngine;
|
|
12057
13865
|
exports.ContextCompiler = ContextCompiler;
|
|
12058
13866
|
exports.ContextLifecycleOrchestrator = ContextLifecycleOrchestrator;
|
|
13867
|
+
exports.EPITEMIC_STATUSES = EPITEMIC_STATUSES2;
|
|
13868
|
+
exports.GOVERNANCE_POLICY_STATUSES = GOVERNANCE_POLICY_STATUSES;
|
|
13869
|
+
exports.GOVERNANCE_ROLES = GOVERNANCE_ROLES;
|
|
13870
|
+
exports.GovernancePolicyError = GovernancePolicyError;
|
|
13871
|
+
exports.GovernancePolicyStore = GovernancePolicyStore;
|
|
13872
|
+
exports.GuardrailError = GuardrailError;
|
|
13873
|
+
exports.GuardrailService = GuardrailService;
|
|
13874
|
+
exports.MEMORY_CATEGORIES = MEMORY_CATEGORIES;
|
|
12059
13875
|
exports.RecoveryService = RecoveryService;
|
|
12060
13876
|
exports.addDecision = addDecision;
|
|
12061
13877
|
exports.addOpenLoopRef = addOpenLoopRef;
|
|
12062
13878
|
exports.addRejectedApproach = addRejectedApproach;
|
|
12063
13879
|
exports.addUnresolvedQuestion = addUnresolvedQuestion;
|
|
12064
13880
|
exports.appendReasoningStep = appendReasoningStep;
|
|
13881
|
+
exports.canTransitionEpistemic = canTransitionEpistemic;
|
|
12065
13882
|
exports.clearUserIdentity = clearUserIdentity;
|
|
12066
13883
|
exports.closeConversationalState = closeConversationalState;
|
|
12067
13884
|
exports.compileContext = compileContext;
|
|
12068
13885
|
exports.continuity = continuity;
|
|
13886
|
+
exports.continuityLedger = continuityLedger;
|
|
12069
13887
|
exports.createCompactionEngine = createCompactionEngine;
|
|
12070
13888
|
exports.createConversationalState = createConversationalState;
|
|
13889
|
+
exports.createGuardrailService = createGuardrailService;
|
|
12071
13890
|
exports.createNoOpHost = createNoOpHost;
|
|
12072
13891
|
exports.createRecoveryService = createRecoveryService;
|
|
12073
13892
|
exports.default = index_default;
|
|
@@ -12082,11 +13901,14 @@ exports.isBrowser = isBrowser;
|
|
|
12082
13901
|
exports.isBun = isBun;
|
|
12083
13902
|
exports.isDeno = isDeno;
|
|
12084
13903
|
exports.isEdge = isEdge;
|
|
13904
|
+
exports.isLegacyRecord = isLegacyRecord;
|
|
12085
13905
|
exports.isNode = isNode;
|
|
12086
13906
|
exports.listUnfinishedConversations = listUnfinishedConversations;
|
|
12087
13907
|
exports.loadAllConversationalStates = loadAllConversationalStates;
|
|
12088
13908
|
exports.loadConversationalStateById = loadConversationalStateById;
|
|
13909
|
+
exports.markDerivedStale = markDerivedStale;
|
|
12089
13910
|
exports.memory = memory;
|
|
13911
|
+
exports.normalizeLegacyRecord = normalizeLegacyRecord;
|
|
12090
13912
|
exports.recoverConversation = recoverConversation;
|
|
12091
13913
|
exports.resolveUserAgentMemoryPath = resolveUserAgentMemoryPath;
|
|
12092
13914
|
exports.resolveUserBrainPath = resolveUserBrainPath;
|
|
@@ -12096,8 +13918,15 @@ exports.resolveUserExperiencePath = resolveUserExperiencePath;
|
|
|
12096
13918
|
exports.resolveUserIdentity = resolveUserIdentity;
|
|
12097
13919
|
exports.resolveUserProjectMemoryPath = resolveUserProjectMemoryPath;
|
|
12098
13920
|
exports.resolveUserReadmePath = resolveUserReadmePath;
|
|
13921
|
+
exports.roleHasPermission = roleHasPermission;
|
|
12099
13922
|
exports.saveConversationalState = saveConversationalState;
|
|
12100
13923
|
exports.security = security;
|
|
12101
13924
|
exports.setUserIdentity = setUserIdentity;
|
|
12102
13925
|
exports.signMemPackage = signMemPackage;
|
|
12103
13926
|
exports.updateConversationalState = updateConversationalState;
|
|
13927
|
+
exports.validateDerivedLineage = validateDerivedLineage;
|
|
13928
|
+
exports.validateEpistemicTransition = validateEpistemicTransition;
|
|
13929
|
+
exports.validateMemoryItem = validateMemoryItem;
|
|
13930
|
+
exports.validateProvenance = validateProvenance;
|
|
13931
|
+
exports.validateRelationshipTargets = validateRelationshipTargets;
|
|
13932
|
+
exports.validateResolutionRequest = validateResolutionRequest;
|