@private.me/xcontinuity 2.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 +123 -0
- package/LICENSE.md +26 -0
- package/MIGRATING.md +77 -0
- package/README.md +601 -0
- package/dist/adjudicator.d.ts +75 -0
- package/dist/adjudicator.js +184 -0
- package/dist/cascade.d.ts +157 -0
- package/dist/cascade.js +323 -0
- package/dist/chronicle.d.ts +76 -0
- package/dist/chronicle.js +173 -0
- package/dist/cjs/adjudicator.js +189 -0
- package/dist/cjs/cascade.js +328 -0
- package/dist/cjs/chronicle.js +178 -0
- package/dist/cjs/enforcement.js +108 -0
- package/dist/cjs/errors.js +72 -0
- package/dist/cjs/index.js +108 -0
- package/dist/cjs/memory-runtime.js +129 -0
- package/dist/cjs/memory-session.js +134 -0
- package/dist/cjs/mission.js +178 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/provenance.js +192 -0
- package/dist/cjs/ratification.js +322 -0
- package/dist/cjs/reverse-xorida.js +506 -0
- package/dist/cjs/session.js +273 -0
- package/dist/cjs/state-serializer.js +300 -0
- package/dist/cjs/store-memory.js +33 -0
- package/dist/cjs/trust.js +133 -0
- package/dist/cjs/types.js +59 -0
- package/dist/enforcement.d.ts +40 -0
- package/dist/enforcement.js +104 -0
- package/dist/errors.d.ts +25 -0
- package/dist/errors.js +68 -0
- package/dist/index.d.ts +34 -0
- package/dist/index.js +43 -0
- package/dist/memory-runtime.d.ts +36 -0
- package/dist/memory-runtime.js +125 -0
- package/dist/memory-session.d.ts +38 -0
- package/dist/memory-session.js +97 -0
- package/dist/mission.d.ts +68 -0
- package/dist/mission.js +172 -0
- package/dist/provenance.d.ts +54 -0
- package/dist/provenance.js +182 -0
- package/dist/ratification.d.ts +113 -0
- package/dist/ratification.js +317 -0
- package/dist/reverse-xorida.d.ts +174 -0
- package/dist/reverse-xorida.js +490 -0
- package/dist/session.d.ts +102 -0
- package/dist/session.js +269 -0
- package/dist/state-serializer.d.ts +37 -0
- package/dist/state-serializer.js +294 -0
- package/dist/store-memory.d.ts +18 -0
- package/dist/store-memory.js +29 -0
- package/dist/trust.d.ts +76 -0
- package/dist/trust.js +121 -0
- package/dist/types.d.ts +320 -0
- package/dist/types.js +56 -0
- package/llms.txt +43 -0
- package/package.json +125 -0
- package/share1.dat +0 -0
|
@@ -0,0 +1,328 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @private.me/xcontinuity — Cascade / Sub-Agent Architecture
|
|
4
|
+
*
|
|
5
|
+
* Enables parent-child session hierarchies where child sessions
|
|
6
|
+
* inherit trust context from their parent. Proves that v2.0.0's
|
|
7
|
+
* trust substrate API composes correctly in multi-agent patterns.
|
|
8
|
+
*
|
|
9
|
+
* @since 2.1.0
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.SubAgentCoordinator = exports.DEFAULT_COORDINATOR_CONFIG = exports.CascadeSession = exports.DEFAULT_CASCADE_POLICY = void 0;
|
|
13
|
+
const shared_1 = require("@private.me/shared");
|
|
14
|
+
const errors_js_1 = require("./errors.js");
|
|
15
|
+
const session_js_1 = require("./session.js");
|
|
16
|
+
const ratification_js_1 = require("./ratification.js");
|
|
17
|
+
const enforcement_js_1 = require("./enforcement.js");
|
|
18
|
+
/** Default cascade policy. */
|
|
19
|
+
exports.DEFAULT_CASCADE_POLICY = {
|
|
20
|
+
propagation: 'inherit',
|
|
21
|
+
maxChildTier: 'ratified',
|
|
22
|
+
escalateToParent: true,
|
|
23
|
+
maxDepth: 5,
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* CascadeSession wraps a SessionManager with parent-child hierarchy.
|
|
27
|
+
* Child sessions inherit trust context from their parent based on CascadePolicy.
|
|
28
|
+
*/
|
|
29
|
+
class CascadeSession {
|
|
30
|
+
session;
|
|
31
|
+
trustStore;
|
|
32
|
+
missionGuard;
|
|
33
|
+
enforcementLoop;
|
|
34
|
+
children = new Map();
|
|
35
|
+
parentId;
|
|
36
|
+
depth;
|
|
37
|
+
policy;
|
|
38
|
+
defaultMaxAge;
|
|
39
|
+
escalations = [];
|
|
40
|
+
constructor(session, trustStore, options) {
|
|
41
|
+
this.session = session;
|
|
42
|
+
this.trustStore = trustStore;
|
|
43
|
+
this.missionGuard = options?.missionGuard;
|
|
44
|
+
this.enforcementLoop = options?.enforcementLoop;
|
|
45
|
+
this.parentId = options?.parentId;
|
|
46
|
+
this.depth = options?.depth ?? 0;
|
|
47
|
+
this.policy = options?.policy ?? exports.DEFAULT_CASCADE_POLICY;
|
|
48
|
+
this.defaultMaxAge = options?.defaultMaxAge;
|
|
49
|
+
}
|
|
50
|
+
/** Get the underlying session manager. */
|
|
51
|
+
getSession() {
|
|
52
|
+
return this.session;
|
|
53
|
+
}
|
|
54
|
+
/** Get the trust store for this cascade node. */
|
|
55
|
+
getTrustStore() {
|
|
56
|
+
return this.trustStore;
|
|
57
|
+
}
|
|
58
|
+
/** Get the cascade depth (0 = root). */
|
|
59
|
+
getDepth() {
|
|
60
|
+
return this.depth;
|
|
61
|
+
}
|
|
62
|
+
/** Get the parent session ID (undefined for root). */
|
|
63
|
+
getParentId() {
|
|
64
|
+
return this.parentId;
|
|
65
|
+
}
|
|
66
|
+
/** Get all child IDs. */
|
|
67
|
+
getChildIds() {
|
|
68
|
+
return Array.from(this.children.keys());
|
|
69
|
+
}
|
|
70
|
+
/** Get a child by ID. */
|
|
71
|
+
getChild(childId) {
|
|
72
|
+
return this.children.get(childId);
|
|
73
|
+
}
|
|
74
|
+
/** Get escalation records from child sessions. */
|
|
75
|
+
getEscalations() {
|
|
76
|
+
return this.escalations;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Spawn a child session that inherits trust context from this parent.
|
|
80
|
+
*
|
|
81
|
+
* The child's trust store is populated based on the cascade policy:
|
|
82
|
+
* - 'inherit': Child receives parent's trust entries at their current tier.
|
|
83
|
+
* - 'downgrade': Child receives entries downgraded one tier level.
|
|
84
|
+
* - 'isolate': Child starts with an empty trust store.
|
|
85
|
+
*/
|
|
86
|
+
spawnChild(childAgentId, store, childPolicy) {
|
|
87
|
+
const mergedPolicy = { ...this.policy, ...childPolicy };
|
|
88
|
+
const childDepth = this.depth + 1;
|
|
89
|
+
if (childDepth > mergedPolicy.maxDepth) {
|
|
90
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('CONSTRAINT_VIOLATION', `Cascade depth ${childDepth} exceeds maximum ${mergedPolicy.maxDepth}`));
|
|
91
|
+
}
|
|
92
|
+
// Create child trust store
|
|
93
|
+
const childTrustStore = new ratification_js_1.TrustStore({
|
|
94
|
+
defaultMaxAge: this.defaultMaxAge,
|
|
95
|
+
});
|
|
96
|
+
// Propagate trust entries from parent to child
|
|
97
|
+
if (mergedPolicy.propagation !== 'isolate') {
|
|
98
|
+
const parentEntries = this.trustStore.all();
|
|
99
|
+
const propagated = [];
|
|
100
|
+
for (const entry of parentEntries) {
|
|
101
|
+
const childTier = this.propagateTier(entry.tier, mergedPolicy);
|
|
102
|
+
if (childTier && this.tierRank(childTier) <= this.tierRank(mergedPolicy.maxChildTier)) {
|
|
103
|
+
propagated.push({
|
|
104
|
+
key: entry.key,
|
|
105
|
+
value: entry.value,
|
|
106
|
+
provenance: entry.provenance,
|
|
107
|
+
tier: childTier,
|
|
108
|
+
contradictions: [],
|
|
109
|
+
maxAge: entry.maxAge,
|
|
110
|
+
ratifiedAt: childTier === 'ratified' ? entry.ratifiedAt : undefined,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
if (propagated.length > 0) {
|
|
115
|
+
childTrustStore.restore(propagated);
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
// Create child enforcement loop that escalates to parent
|
|
119
|
+
let childEnforcement;
|
|
120
|
+
if (this.missionGuard) {
|
|
121
|
+
const escalateToParent = mergedPolicy.escalateToParent;
|
|
122
|
+
childEnforcement = new enforcement_js_1.EnforcementLoop(this.missionGuard, {
|
|
123
|
+
escalationThreshold: 3,
|
|
124
|
+
onEscalate: escalateToParent
|
|
125
|
+
? (result) => {
|
|
126
|
+
this.escalations.push({
|
|
127
|
+
childId: childAgentId,
|
|
128
|
+
agentId: result.action.author,
|
|
129
|
+
violationCount: result.violationCount ?? 0,
|
|
130
|
+
timestamp: Date.now(),
|
|
131
|
+
action: result.action,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
: undefined,
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
// Create child session
|
|
138
|
+
const childConfig = {
|
|
139
|
+
agentId: childAgentId,
|
|
140
|
+
store,
|
|
141
|
+
trustStore: childTrustStore,
|
|
142
|
+
missionGuard: this.missionGuard,
|
|
143
|
+
enforcementLoop: childEnforcement,
|
|
144
|
+
};
|
|
145
|
+
const childSession = session_js_1.SessionManager.create(childConfig);
|
|
146
|
+
const sessionId = childSession.session.sessionId;
|
|
147
|
+
const cascadeChild = new CascadeSession(childSession, childTrustStore, {
|
|
148
|
+
missionGuard: this.missionGuard,
|
|
149
|
+
enforcementLoop: childEnforcement,
|
|
150
|
+
parentId: this.session.session.sessionId,
|
|
151
|
+
depth: childDepth,
|
|
152
|
+
policy: mergedPolicy,
|
|
153
|
+
defaultMaxAge: this.defaultMaxAge,
|
|
154
|
+
});
|
|
155
|
+
this.children.set(sessionId, {
|
|
156
|
+
childId: sessionId,
|
|
157
|
+
session: childSession,
|
|
158
|
+
trustStore: childTrustStore,
|
|
159
|
+
policy: mergedPolicy,
|
|
160
|
+
depth: childDepth,
|
|
161
|
+
createdAt: Date.now(),
|
|
162
|
+
});
|
|
163
|
+
return (0, shared_1.ok)(cascadeChild);
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* Merge a child's trusted state back into the parent.
|
|
167
|
+
* Only entries at or above 'inherited' tier are merged.
|
|
168
|
+
* The child session is closed after merging.
|
|
169
|
+
*/
|
|
170
|
+
mergeChild(childId) {
|
|
171
|
+
const child = this.children.get(childId);
|
|
172
|
+
if (!child) {
|
|
173
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('SNAPSHOT_NOT_FOUND', `Child session "${childId}" not found`));
|
|
174
|
+
}
|
|
175
|
+
let merged = 0;
|
|
176
|
+
const childEntries = child.trustStore.all();
|
|
177
|
+
for (const entry of childEntries) {
|
|
178
|
+
if (this.tierRank(entry.tier) >= this.tierRank('inherited')) {
|
|
179
|
+
// Check parent enforcement before merging
|
|
180
|
+
if (this.enforcementLoop) {
|
|
181
|
+
const check = this.enforcementLoop.check({
|
|
182
|
+
author: child.session.session.agentId,
|
|
183
|
+
type: 'write',
|
|
184
|
+
key: entry.key,
|
|
185
|
+
value: entry.value,
|
|
186
|
+
});
|
|
187
|
+
if (check.ok && check.value.decision !== 'allow') {
|
|
188
|
+
continue; // Skip entries that violate parent constraints
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.trustStore.write(entry.key, entry.value, entry.provenance);
|
|
192
|
+
merged++;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
// Close child session
|
|
196
|
+
child.session.close();
|
|
197
|
+
this.children.delete(childId);
|
|
198
|
+
return (0, shared_1.ok)(merged);
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Close all children and then this session.
|
|
202
|
+
*/
|
|
203
|
+
closeAll() {
|
|
204
|
+
for (const [, child] of this.children) {
|
|
205
|
+
child.session.close();
|
|
206
|
+
}
|
|
207
|
+
this.children.clear();
|
|
208
|
+
this.session.close();
|
|
209
|
+
}
|
|
210
|
+
/** Number of active children. */
|
|
211
|
+
get childCount() {
|
|
212
|
+
return this.children.size;
|
|
213
|
+
}
|
|
214
|
+
propagateTier(parentTier, policy) {
|
|
215
|
+
if (policy.propagation === 'isolate')
|
|
216
|
+
return null;
|
|
217
|
+
if (policy.propagation === 'inherit')
|
|
218
|
+
return parentTier;
|
|
219
|
+
// Downgrade: ratified -> inherited, inherited -> quarantined, quarantined -> null
|
|
220
|
+
if (parentTier === 'ratified')
|
|
221
|
+
return 'inherited';
|
|
222
|
+
if (parentTier === 'inherited')
|
|
223
|
+
return 'quarantined';
|
|
224
|
+
return null; // quarantined cannot be downgraded further
|
|
225
|
+
}
|
|
226
|
+
tierRank(tier) {
|
|
227
|
+
const ranks = {
|
|
228
|
+
ratified: 3,
|
|
229
|
+
inherited: 2,
|
|
230
|
+
quarantined: 1,
|
|
231
|
+
};
|
|
232
|
+
return ranks[tier];
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
exports.CascadeSession = CascadeSession;
|
|
236
|
+
/** Default coordinator configuration. */
|
|
237
|
+
exports.DEFAULT_COORDINATOR_CONFIG = {
|
|
238
|
+
maxAgents: 10,
|
|
239
|
+
defaultPolicy: exports.DEFAULT_CASCADE_POLICY,
|
|
240
|
+
autoMerge: false,
|
|
241
|
+
};
|
|
242
|
+
/**
|
|
243
|
+
* SubAgentCoordinator manages the lifecycle of sub-agent sessions
|
|
244
|
+
* within a cascade hierarchy. It provides:
|
|
245
|
+
* - Spawning with trust delegation
|
|
246
|
+
* - State merging with enforcement checking
|
|
247
|
+
* - Lifecycle management (close, cleanup)
|
|
248
|
+
*/
|
|
249
|
+
class SubAgentCoordinator {
|
|
250
|
+
root;
|
|
251
|
+
config;
|
|
252
|
+
activeAgents = new Map();
|
|
253
|
+
constructor(root, config) {
|
|
254
|
+
this.root = root;
|
|
255
|
+
this.config = { ...exports.DEFAULT_COORDINATOR_CONFIG, ...config };
|
|
256
|
+
}
|
|
257
|
+
/** Get the root cascade session. */
|
|
258
|
+
getRoot() {
|
|
259
|
+
return this.root;
|
|
260
|
+
}
|
|
261
|
+
/** Get the count of active sub-agents. */
|
|
262
|
+
get activeCount() {
|
|
263
|
+
return this.activeAgents.size;
|
|
264
|
+
}
|
|
265
|
+
/**
|
|
266
|
+
* Spawn a sub-agent with trust delegation from the root.
|
|
267
|
+
*/
|
|
268
|
+
spawn(agentId, store, policy) {
|
|
269
|
+
if (this.activeAgents.size >= this.config.maxAgents) {
|
|
270
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('CONSTRAINT_VIOLATION', `Maximum sub-agents (${this.config.maxAgents}) reached`));
|
|
271
|
+
}
|
|
272
|
+
const mergedPolicy = { ...this.config.defaultPolicy, ...policy };
|
|
273
|
+
const result = this.root.spawnChild(agentId, store, mergedPolicy);
|
|
274
|
+
if (!result.ok)
|
|
275
|
+
return result;
|
|
276
|
+
const child = result.value;
|
|
277
|
+
const childId = child.getSession().session.sessionId;
|
|
278
|
+
this.activeAgents.set(childId, child);
|
|
279
|
+
return (0, shared_1.ok)(child);
|
|
280
|
+
}
|
|
281
|
+
/**
|
|
282
|
+
* Complete a sub-agent and optionally merge its state back into the root.
|
|
283
|
+
*/
|
|
284
|
+
complete(childId) {
|
|
285
|
+
const child = this.activeAgents.get(childId);
|
|
286
|
+
if (!child) {
|
|
287
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('SNAPSHOT_NOT_FOUND', `Sub-agent "${childId}" not found in coordinator`));
|
|
288
|
+
}
|
|
289
|
+
let merged = 0;
|
|
290
|
+
if (this.config.autoMerge) {
|
|
291
|
+
const mergeResult = this.root.mergeChild(childId);
|
|
292
|
+
if (mergeResult.ok)
|
|
293
|
+
merged = mergeResult.value;
|
|
294
|
+
}
|
|
295
|
+
else {
|
|
296
|
+
child.getSession().close();
|
|
297
|
+
}
|
|
298
|
+
this.activeAgents.delete(childId);
|
|
299
|
+
return (0, shared_1.ok)(merged);
|
|
300
|
+
}
|
|
301
|
+
/**
|
|
302
|
+
* Merge a specific sub-agent's state into the root (manual merge).
|
|
303
|
+
*/
|
|
304
|
+
merge(childId) {
|
|
305
|
+
if (!this.activeAgents.has(childId)) {
|
|
306
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('SNAPSHOT_NOT_FOUND', `Sub-agent "${childId}" not found in coordinator`));
|
|
307
|
+
}
|
|
308
|
+
const result = this.root.mergeChild(childId);
|
|
309
|
+
if (result.ok) {
|
|
310
|
+
this.activeAgents.delete(childId);
|
|
311
|
+
}
|
|
312
|
+
return result;
|
|
313
|
+
}
|
|
314
|
+
/**
|
|
315
|
+
* Close all sub-agents and the root session.
|
|
316
|
+
*/
|
|
317
|
+
shutdown() {
|
|
318
|
+
this.activeAgents.clear();
|
|
319
|
+
this.root.closeAll();
|
|
320
|
+
}
|
|
321
|
+
/**
|
|
322
|
+
* Get all active sub-agent IDs.
|
|
323
|
+
*/
|
|
324
|
+
getActiveIds() {
|
|
325
|
+
return Array.from(this.activeAgents.keys());
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
exports.SubAgentCoordinator = SubAgentCoordinator;
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @private.me/xcontinuity — Chronicle (State History)
|
|
4
|
+
*
|
|
5
|
+
* Append-only ordered state history with navigation.
|
|
6
|
+
* Secondary index (Map stateId->index) for O(1) lookup.
|
|
7
|
+
* Query with filters: sessionId, time range, tags, limit/offset pagination.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.Chronicle = void 0;
|
|
11
|
+
exports.detectContradiction = detectContradiction;
|
|
12
|
+
const shared_1 = require("@private.me/shared");
|
|
13
|
+
const errors_js_1 = require("./errors.js");
|
|
14
|
+
class Chronicle {
|
|
15
|
+
entries = [];
|
|
16
|
+
indexByStateId = new Map();
|
|
17
|
+
/** Track the latest stateId for each key (for contradiction detection). */
|
|
18
|
+
latestByKey = new Map();
|
|
19
|
+
/** Detected contradictions. */
|
|
20
|
+
contradictions = [];
|
|
21
|
+
/** Append a new entry to the chronicle. */
|
|
22
|
+
append(entry) {
|
|
23
|
+
const idx = this.entries.length;
|
|
24
|
+
this.entries.push(entry);
|
|
25
|
+
this.indexByStateId.set(entry.stateId, idx);
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Append an entry and check for contradictions against the latest
|
|
29
|
+
* value for the same key. Returns detected contradictions (if any).
|
|
30
|
+
*
|
|
31
|
+
* @param entry - The chronicle entry
|
|
32
|
+
* @param key - The state key this entry represents
|
|
33
|
+
* @param value - The new value for the key
|
|
34
|
+
* @param existingValue - The most recent known value for the key (if any)
|
|
35
|
+
* @returns Array of contradictions detected (empty if none)
|
|
36
|
+
*/
|
|
37
|
+
appendWithContradictionCheck(entry, key, value, existingValue) {
|
|
38
|
+
const detected = [];
|
|
39
|
+
const previousStateId = this.latestByKey.get(key);
|
|
40
|
+
if (previousStateId !== undefined && existingValue !== undefined) {
|
|
41
|
+
if (detectContradiction(value, existingValue)) {
|
|
42
|
+
const contradiction = {
|
|
43
|
+
incomingStateId: entry.stateId,
|
|
44
|
+
existingStateId: previousStateId,
|
|
45
|
+
key,
|
|
46
|
+
detectedAt: Date.now(),
|
|
47
|
+
};
|
|
48
|
+
detected.push(contradiction);
|
|
49
|
+
this.contradictions.push(contradiction);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
this.latestByKey.set(key, entry.stateId);
|
|
53
|
+
this.append(entry);
|
|
54
|
+
return detected;
|
|
55
|
+
}
|
|
56
|
+
/** Get all detected contradictions. */
|
|
57
|
+
getContradictions() {
|
|
58
|
+
return this.contradictions.slice();
|
|
59
|
+
}
|
|
60
|
+
/** Get contradictions for a specific key. */
|
|
61
|
+
getContradictionsForKey(key) {
|
|
62
|
+
return this.contradictions.filter(c => c.key === key);
|
|
63
|
+
}
|
|
64
|
+
/** Get the latest stateId recorded for a given key. */
|
|
65
|
+
getLatestForKey(key) {
|
|
66
|
+
return this.latestByKey.get(key);
|
|
67
|
+
}
|
|
68
|
+
/** Get an entry by stateId. O(1) lookup. */
|
|
69
|
+
get(stateId) {
|
|
70
|
+
const idx = this.indexByStateId.get(stateId);
|
|
71
|
+
if (idx === undefined) {
|
|
72
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('ENTRY_NOT_FOUND', `Chronicle entry ${stateId} not found`));
|
|
73
|
+
}
|
|
74
|
+
return (0, shared_1.ok)(this.entries[idx]);
|
|
75
|
+
}
|
|
76
|
+
/** Get the latest entry, or null if empty. */
|
|
77
|
+
latest() {
|
|
78
|
+
if (this.entries.length === 0)
|
|
79
|
+
return null;
|
|
80
|
+
return this.entries[this.entries.length - 1];
|
|
81
|
+
}
|
|
82
|
+
/** Get entry at a specific sequence position. */
|
|
83
|
+
atSequence(sequence) {
|
|
84
|
+
const entry = this.entries.find(e => e.sequence === sequence);
|
|
85
|
+
if (!entry) {
|
|
86
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('ENTRY_NOT_FOUND', `No entry with sequence ${sequence}`));
|
|
87
|
+
}
|
|
88
|
+
return (0, shared_1.ok)(entry);
|
|
89
|
+
}
|
|
90
|
+
/** Get the parent entry (if parentStateId is set). */
|
|
91
|
+
parent(stateId) {
|
|
92
|
+
const entryResult = this.get(stateId);
|
|
93
|
+
if (!entryResult.ok)
|
|
94
|
+
return entryResult;
|
|
95
|
+
const entry = entryResult.value;
|
|
96
|
+
if (!entry.parentStateId) {
|
|
97
|
+
return (0, shared_1.err)((0, errors_js_1.continuityError)('ENTRY_NOT_FOUND', `Entry ${stateId} has no parent`));
|
|
98
|
+
}
|
|
99
|
+
return this.get(entry.parentStateId);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Query chronicle entries with filters.
|
|
103
|
+
*
|
|
104
|
+
* @param query - Filter criteria (sessionId, time range, tags, limit/offset)
|
|
105
|
+
* @returns Matching entries ordered by timestamp
|
|
106
|
+
*/
|
|
107
|
+
query(query = {}) {
|
|
108
|
+
let results = this.entries.slice();
|
|
109
|
+
if (query.sessionId) {
|
|
110
|
+
results = results.filter(e => e.sessionId === query.sessionId);
|
|
111
|
+
}
|
|
112
|
+
if (query.after !== undefined) {
|
|
113
|
+
results = results.filter(e => e.timestamp >= query.after);
|
|
114
|
+
}
|
|
115
|
+
if (query.before !== undefined) {
|
|
116
|
+
results = results.filter(e => e.timestamp <= query.before);
|
|
117
|
+
}
|
|
118
|
+
if (query.tags && query.tags.length > 0) {
|
|
119
|
+
const tagSet = new Set(query.tags);
|
|
120
|
+
results = results.filter(e => e.tags.some(t => tagSet.has(t)));
|
|
121
|
+
}
|
|
122
|
+
// Pagination
|
|
123
|
+
const offset = query.offset ?? 0;
|
|
124
|
+
const limit = query.limit ?? results.length;
|
|
125
|
+
return results.slice(offset, offset + limit);
|
|
126
|
+
}
|
|
127
|
+
/** Total number of entries. */
|
|
128
|
+
get length() {
|
|
129
|
+
return this.entries.length;
|
|
130
|
+
}
|
|
131
|
+
/** Get all entries (readonly copy). */
|
|
132
|
+
all() {
|
|
133
|
+
return this.entries.slice();
|
|
134
|
+
}
|
|
135
|
+
/** Clear all entries. */
|
|
136
|
+
clear() {
|
|
137
|
+
this.entries.length = 0;
|
|
138
|
+
this.indexByStateId.clear();
|
|
139
|
+
this.latestByKey.clear();
|
|
140
|
+
this.contradictions.length = 0;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
exports.Chronicle = Chronicle;
|
|
144
|
+
/**
|
|
145
|
+
* Detect whether two values for the same key contradict each other.
|
|
146
|
+
*
|
|
147
|
+
* Contradiction = values are structurally different.
|
|
148
|
+
* Identical values are not contradictions (they're confirmations).
|
|
149
|
+
*/
|
|
150
|
+
function detectContradiction(newValue, existingValue) {
|
|
151
|
+
// Null checks
|
|
152
|
+
if (newValue === null && existingValue === null)
|
|
153
|
+
return false;
|
|
154
|
+
if (newValue === null || existingValue === null)
|
|
155
|
+
return true;
|
|
156
|
+
// Uint8Array comparison
|
|
157
|
+
if (newValue instanceof Uint8Array && existingValue instanceof Uint8Array) {
|
|
158
|
+
if (newValue.length !== existingValue.length)
|
|
159
|
+
return true;
|
|
160
|
+
for (let i = 0; i < newValue.length; i++) {
|
|
161
|
+
if (newValue[i] !== existingValue[i])
|
|
162
|
+
return true;
|
|
163
|
+
}
|
|
164
|
+
return false;
|
|
165
|
+
}
|
|
166
|
+
// Type mismatch
|
|
167
|
+
if (typeof newValue !== typeof existingValue)
|
|
168
|
+
return true;
|
|
169
|
+
if (newValue instanceof Uint8Array || existingValue instanceof Uint8Array)
|
|
170
|
+
return true;
|
|
171
|
+
// Object comparison (deterministic JSON)
|
|
172
|
+
if (typeof newValue === 'object' && typeof existingValue === 'object') {
|
|
173
|
+
return JSON.stringify(newValue, Object.keys(newValue).sort())
|
|
174
|
+
!== JSON.stringify(existingValue, Object.keys(existingValue).sort());
|
|
175
|
+
}
|
|
176
|
+
// Primitive comparison
|
|
177
|
+
return newValue !== existingValue;
|
|
178
|
+
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @private.me/xcontinuity — Enforcement Loop
|
|
4
|
+
*
|
|
5
|
+
* Evaluates proposed actions against mission constraints and produces
|
|
6
|
+
* enforcement decisions: allow, reject (with reason), rewrite (with
|
|
7
|
+
* suggested alternative), or escalate (on repeated violations).
|
|
8
|
+
*
|
|
9
|
+
* Tracks violation history per agent for escalation logic.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.EnforcementLoop = void 0;
|
|
13
|
+
const shared_1 = require("@private.me/shared");
|
|
14
|
+
const types_js_1 = require("./types.js");
|
|
15
|
+
class EnforcementLoop {
|
|
16
|
+
guard;
|
|
17
|
+
config;
|
|
18
|
+
/** Violation counts per agent. */
|
|
19
|
+
violations = new Map();
|
|
20
|
+
constructor(guard, config) {
|
|
21
|
+
this.guard = guard;
|
|
22
|
+
this.config = {
|
|
23
|
+
...types_js_1.DEFAULT_ENFORCEMENT_CONFIG,
|
|
24
|
+
...config,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Check a proposed action against the enforcement loop.
|
|
29
|
+
*
|
|
30
|
+
* Flow:
|
|
31
|
+
* 1. Evaluate against mission guard
|
|
32
|
+
* 2. If allowed → return allow
|
|
33
|
+
* 3. If denied and has suggestion → return rewrite
|
|
34
|
+
* 4. If denied without suggestion → increment violations
|
|
35
|
+
* 5. If violations >= threshold → return escalate
|
|
36
|
+
* 6. Otherwise → return reject
|
|
37
|
+
*/
|
|
38
|
+
check(action) {
|
|
39
|
+
const guardResult = this.guard.evaluate(action);
|
|
40
|
+
// Guard evaluation error — propagate
|
|
41
|
+
if (!guardResult.ok) {
|
|
42
|
+
return (0, shared_1.err)(guardResult.error);
|
|
43
|
+
}
|
|
44
|
+
const constraintResult = guardResult.value;
|
|
45
|
+
// Action allowed
|
|
46
|
+
if (constraintResult.allowed) {
|
|
47
|
+
return (0, shared_1.ok)({
|
|
48
|
+
decision: 'allow',
|
|
49
|
+
action,
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
// Action denied — check for rewrite suggestion
|
|
53
|
+
if (constraintResult.suggestion) {
|
|
54
|
+
// Verify the suggestion itself passes constraints
|
|
55
|
+
const suggestionCheck = this.guard.evaluate(constraintResult.suggestion);
|
|
56
|
+
if (suggestionCheck.ok && suggestionCheck.value.allowed) {
|
|
57
|
+
return (0, shared_1.ok)({
|
|
58
|
+
decision: 'rewrite',
|
|
59
|
+
action,
|
|
60
|
+
reason: constraintResult.reason,
|
|
61
|
+
rewrittenAction: constraintResult.suggestion,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
// Suggestion also fails — fall through to reject/escalate
|
|
65
|
+
}
|
|
66
|
+
// Increment violation count for this agent
|
|
67
|
+
const currentCount = (this.violations.get(action.author) ?? 0) + 1;
|
|
68
|
+
this.violations.set(action.author, currentCount);
|
|
69
|
+
// Check escalation threshold
|
|
70
|
+
if (currentCount >= this.config.escalationThreshold) {
|
|
71
|
+
const result = {
|
|
72
|
+
decision: 'escalate',
|
|
73
|
+
action,
|
|
74
|
+
reason: constraintResult.reason,
|
|
75
|
+
violationCount: currentCount,
|
|
76
|
+
};
|
|
77
|
+
// Fire escalation callback if configured
|
|
78
|
+
if (this.config.onEscalate) {
|
|
79
|
+
this.config.onEscalate(result);
|
|
80
|
+
}
|
|
81
|
+
return (0, shared_1.ok)(result);
|
|
82
|
+
}
|
|
83
|
+
// Reject
|
|
84
|
+
return (0, shared_1.ok)({
|
|
85
|
+
decision: 'reject',
|
|
86
|
+
action,
|
|
87
|
+
reason: constraintResult.reason,
|
|
88
|
+
violationCount: currentCount,
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
/** Get the current violation count for an agent. */
|
|
92
|
+
getViolationCount(author) {
|
|
93
|
+
return this.violations.get(author) ?? 0;
|
|
94
|
+
}
|
|
95
|
+
/** Reset violation count for an agent. */
|
|
96
|
+
resetViolations(author) {
|
|
97
|
+
this.violations.delete(author);
|
|
98
|
+
}
|
|
99
|
+
/** Reset all violation counts. */
|
|
100
|
+
resetAllViolations() {
|
|
101
|
+
this.violations.clear();
|
|
102
|
+
}
|
|
103
|
+
/** Get the escalation threshold. */
|
|
104
|
+
get escalationThreshold() {
|
|
105
|
+
return this.config.escalationThreshold;
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
exports.EnforcementLoop = EnforcementLoop;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* @private.me/xcontinuity — Error hierarchy
|
|
4
|
+
*
|
|
5
|
+
* Follows the pattern from @private.me/crypto errors.
|
|
6
|
+
* All fallible public functions return Result<T, ContinuityError>.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.ERROR_DETAILS = void 0;
|
|
10
|
+
exports.continuityError = continuityError;
|
|
11
|
+
/* ── Error Details Map ── */
|
|
12
|
+
/** Human-readable descriptions for all error codes. */
|
|
13
|
+
exports.ERROR_DETAILS = {
|
|
14
|
+
// Serialization
|
|
15
|
+
SERIALIZE_FAILED: 'State serialization failed. Check that all state values are valid types (string, number, boolean, Uint8Array, null, or plain object).',
|
|
16
|
+
DESERIALIZE_FAILED: 'State deserialization failed. The TLV data may be corrupt or in an unsupported format.',
|
|
17
|
+
CHECKSUM_MISMATCH: 'SHA-256 checksum verification failed. The serialized data has been modified since snapshot creation.',
|
|
18
|
+
INVALID_TLV: 'Invalid TLV structure. The byte stream has a truncated header or value exceeding data length.',
|
|
19
|
+
MISSING_FIELD: 'Required field missing from serialized data.',
|
|
20
|
+
INVALID_VALUE_TYPE: 'Unknown value type code in serialized entry. Expected STRING(0x01), NUMBER(0x02), BOOLEAN(0x03), BYTES(0x04), NULL(0x05), or JSON(0x06).',
|
|
21
|
+
// Split
|
|
22
|
+
SPLIT_FAILED: 'XorIDA threshold split failed. Verify n >= 2 and 2 <= k <= n.',
|
|
23
|
+
RECONSTRUCT_FAILED: 'XorIDA share reconstruction failed. Ensure shares are valid and indices are correct.',
|
|
24
|
+
HMAC_FAILURE: 'HMAC-SHA256 verification failed after reconstruction. One or more shares may be corrupted or tampered.',
|
|
25
|
+
INSUFFICIENT_SHARES: 'Not enough shares for reconstruction. Provide at least k shares (threshold).',
|
|
26
|
+
INVALID_SHARES: 'Share validation failed. All shares must have matching stateId, n, and k values.',
|
|
27
|
+
PADDING_ERROR: 'PKCS#7 unpadding failed after reconstruction. Data may be corrupted.',
|
|
28
|
+
DELTA_SIZE_MISMATCH: 'Old and new padded state sizes differ. Incremental update requires same padded length; falling back to fresh split.',
|
|
29
|
+
// Session
|
|
30
|
+
SESSION_CLOSED: 'Session is closed (terminal state). Create a new session to continue.',
|
|
31
|
+
SESSION_SUSPENDED: 'Session is suspended. Call resume() before performing operations.',
|
|
32
|
+
SESSION_ACTIVE: 'Session is already active. Suspend before attempting to resume.',
|
|
33
|
+
NO_SNAPSHOTS: 'No snapshots available. Create at least one snapshot before restoring.',
|
|
34
|
+
SNAPSHOT_NOT_FOUND: 'Snapshot not found in store. The stateId may be incorrect or the snapshot was deleted.',
|
|
35
|
+
INVALID_CONFIG: 'Invalid session configuration. Verify agentId and store are provided.',
|
|
36
|
+
// Chronicle
|
|
37
|
+
ENTRY_NOT_FOUND: 'Chronicle entry not found. The stateId or sequence number does not exist in the history.',
|
|
38
|
+
INVALID_QUERY: 'Invalid chronicle query parameters.',
|
|
39
|
+
// Store
|
|
40
|
+
STORE_PUT_FAILED: 'Failed to write split state to store backend.',
|
|
41
|
+
STORE_GET_FAILED: 'Failed to read split state from store backend.',
|
|
42
|
+
STORE_DELETE_FAILED: 'Failed to delete split state from store backend.',
|
|
43
|
+
// Provenance
|
|
44
|
+
INVALID_SIGNATURE: 'Ed25519 signature verification failed. The entry may have been tampered with or signed by a different key.',
|
|
45
|
+
MISSING_AUTHOR: 'Provenance record is missing the author field. Every signed entry must have an AuthorRef.',
|
|
46
|
+
HASH_CHAIN_BREAK: 'Parent hash does not match the expected predecessor. The chronicle chain has a gap or reorder.',
|
|
47
|
+
SIGNATURE_EXPIRED: 'The signature timestamp is older than the maximum allowed age for this trust tier.',
|
|
48
|
+
// Trust
|
|
49
|
+
TIER_DOWNGRADE: 'Trust tier was downgraded due to contradictions or policy violation.',
|
|
50
|
+
QUARANTINED_ENTRY: 'Entry is quarantined and cannot be used until re-verified or ratified.',
|
|
51
|
+
CONTRADICTION_DETECTED: 'A contradicting entry was found for the same key with incompatible value.',
|
|
52
|
+
TRUST_DECAY_EXPIRED: 'Entry exceeded its maxAge TTL and was downgraded from ratified to inherited.',
|
|
53
|
+
// Adjudicator
|
|
54
|
+
CONSENSUS_FAILED: 'Multi-agent consensus could not be reached. Quorum threshold not met.',
|
|
55
|
+
POLICY_VIOLATION: 'The proposed resolution violates the active policy adjudicator rules.',
|
|
56
|
+
QUORUM_NOT_MET: 'Insufficient agent views to form a quorum for consensus adjudication.',
|
|
57
|
+
NO_CANDIDATES: 'No candidate entries provided for adjudication.',
|
|
58
|
+
// Mission
|
|
59
|
+
CONSTRAINT_VIOLATION: 'The proposed action violates a hard constraint defined by the active mission.',
|
|
60
|
+
AUTHORITY_EXPIRED: 'The mission authority signature has expired. A new mission must be issued.',
|
|
61
|
+
SCOPE_EXCEEDED: 'The proposed action falls outside the scopes authorized by the active mission.',
|
|
62
|
+
INVALID_MISSION_SIGNATURE: 'Ed25519 signature on the mission record failed verification.',
|
|
63
|
+
NO_ACTIVE_MISSION: 'No active mission is set. A mission must be loaded before constraint evaluation.',
|
|
64
|
+
// Enforcement
|
|
65
|
+
ACTION_REJECTED: 'The enforcement loop rejected the proposed action based on constraint evaluation.',
|
|
66
|
+
ESCALATION_TRIGGERED: 'Repeated violations exceeded the escalation threshold. Human review required.',
|
|
67
|
+
REWRITE_FAILED: 'The enforcement loop could not produce a valid rewrite for the rejected action.',
|
|
68
|
+
};
|
|
69
|
+
/* ── Error Factory ── */
|
|
70
|
+
function continuityError(code, message, subCode) {
|
|
71
|
+
return subCode != null ? { code, message, subCode } : { code, message };
|
|
72
|
+
}
|