@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.
Files changed (59) hide show
  1. package/AGENTS.md +123 -0
  2. package/LICENSE.md +26 -0
  3. package/MIGRATING.md +77 -0
  4. package/README.md +601 -0
  5. package/dist/adjudicator.d.ts +75 -0
  6. package/dist/adjudicator.js +184 -0
  7. package/dist/cascade.d.ts +157 -0
  8. package/dist/cascade.js +323 -0
  9. package/dist/chronicle.d.ts +76 -0
  10. package/dist/chronicle.js +173 -0
  11. package/dist/cjs/adjudicator.js +189 -0
  12. package/dist/cjs/cascade.js +328 -0
  13. package/dist/cjs/chronicle.js +178 -0
  14. package/dist/cjs/enforcement.js +108 -0
  15. package/dist/cjs/errors.js +72 -0
  16. package/dist/cjs/index.js +108 -0
  17. package/dist/cjs/memory-runtime.js +129 -0
  18. package/dist/cjs/memory-session.js +134 -0
  19. package/dist/cjs/mission.js +178 -0
  20. package/dist/cjs/package.json +1 -0
  21. package/dist/cjs/provenance.js +192 -0
  22. package/dist/cjs/ratification.js +322 -0
  23. package/dist/cjs/reverse-xorida.js +506 -0
  24. package/dist/cjs/session.js +273 -0
  25. package/dist/cjs/state-serializer.js +300 -0
  26. package/dist/cjs/store-memory.js +33 -0
  27. package/dist/cjs/trust.js +133 -0
  28. package/dist/cjs/types.js +59 -0
  29. package/dist/enforcement.d.ts +40 -0
  30. package/dist/enforcement.js +104 -0
  31. package/dist/errors.d.ts +25 -0
  32. package/dist/errors.js +68 -0
  33. package/dist/index.d.ts +34 -0
  34. package/dist/index.js +43 -0
  35. package/dist/memory-runtime.d.ts +36 -0
  36. package/dist/memory-runtime.js +125 -0
  37. package/dist/memory-session.d.ts +38 -0
  38. package/dist/memory-session.js +97 -0
  39. package/dist/mission.d.ts +68 -0
  40. package/dist/mission.js +172 -0
  41. package/dist/provenance.d.ts +54 -0
  42. package/dist/provenance.js +182 -0
  43. package/dist/ratification.d.ts +113 -0
  44. package/dist/ratification.js +317 -0
  45. package/dist/reverse-xorida.d.ts +174 -0
  46. package/dist/reverse-xorida.js +490 -0
  47. package/dist/session.d.ts +102 -0
  48. package/dist/session.js +269 -0
  49. package/dist/state-serializer.d.ts +37 -0
  50. package/dist/state-serializer.js +294 -0
  51. package/dist/store-memory.d.ts +18 -0
  52. package/dist/store-memory.js +29 -0
  53. package/dist/trust.d.ts +76 -0
  54. package/dist/trust.js +121 -0
  55. package/dist/types.d.ts +320 -0
  56. package/dist/types.js +56 -0
  57. package/llms.txt +43 -0
  58. package/package.json +125 -0
  59. package/share1.dat +0 -0
@@ -0,0 +1,192 @@
1
+ "use strict";
2
+ /**
3
+ * @private.me/xcontinuity — Provenance (Ed25519 Signed Entries)
4
+ *
5
+ * Cryptographic provenance for chronicle entries using Ed25519 signatures
6
+ * via the Web Crypto API. Each entry can be signed by an agent, creating
7
+ * a verifiable chain of authorship and integrity.
8
+ *
9
+ * Uses native Web Crypto EdDSA (Node.js 16.9+, modern browsers).
10
+ * Zero external dependencies.
11
+ */
12
+ Object.defineProperty(exports, "__esModule", { value: true });
13
+ exports.generateSigningKeyPair = generateSigningKeyPair;
14
+ exports.publicKeyToAuthorRef = publicKeyToAuthorRef;
15
+ exports.authorRefToPublicKey = authorRefToPublicKey;
16
+ exports.canonicalEntryBytes = canonicalEntryBytes;
17
+ exports.signEntry = signEntry;
18
+ exports.verifyEntry = verifyEntry;
19
+ exports.hashProvenance = hashProvenance;
20
+ exports.verifyChainLink = verifyChainLink;
21
+ const shared_1 = require("@private.me/shared");
22
+ const errors_js_1 = require("./errors.js");
23
+ /**
24
+ * Generate a new Ed25519 key pair for provenance signing.
25
+ */
26
+ async function generateSigningKeyPair() {
27
+ const keyPair = await crypto.subtle.generateKey('Ed25519', true, ['sign', 'verify']);
28
+ return {
29
+ publicKey: keyPair.publicKey,
30
+ privateKey: keyPair.privateKey,
31
+ };
32
+ }
33
+ /**
34
+ * Export the public key as an AuthorRef (base64url-encoded 32-byte key).
35
+ */
36
+ async function publicKeyToAuthorRef(publicKey) {
37
+ const raw = await crypto.subtle.exportKey('raw', publicKey);
38
+ return uint8ToBase64Url(new Uint8Array(raw));
39
+ }
40
+ /**
41
+ * Import an AuthorRef back to a CryptoKey for verification.
42
+ */
43
+ async function authorRefToPublicKey(authorRef) {
44
+ const raw = base64UrlToUint8(authorRef);
45
+ return crypto.subtle.importKey('raw', raw.buffer, 'Ed25519', true, ['verify']);
46
+ }
47
+ /**
48
+ * Produce canonical bytes for a memory entry, suitable for signing.
49
+ *
50
+ * Deterministic serialization: sorted keys, UTF-8 encoded.
51
+ * Format: `key\0type\0valueBytes\0timestamp`
52
+ */
53
+ function canonicalEntryBytes(key, value, timestamp, parentHash) {
54
+ const encoder = new TextEncoder();
55
+ const keyBytes = encoder.encode(key);
56
+ const valueStr = canonicalValueString(value);
57
+ const valueBytes = encoder.encode(valueStr);
58
+ const tsBytes = encoder.encode(String(timestamp));
59
+ // Total: key + \0 + value + \0 + timestamp [+ \0 + parentHash]
60
+ const parentLen = parentHash ? 1 + parentHash.length : 0;
61
+ const total = keyBytes.length + 1 + valueBytes.length + 1 + tsBytes.length + parentLen;
62
+ const buf = new Uint8Array(total);
63
+ let offset = 0;
64
+ buf.set(keyBytes, offset);
65
+ offset += keyBytes.length;
66
+ buf[offset++] = 0;
67
+ buf.set(valueBytes, offset);
68
+ offset += valueBytes.length;
69
+ buf[offset++] = 0;
70
+ buf.set(tsBytes, offset);
71
+ offset += tsBytes.length;
72
+ if (parentHash) {
73
+ buf[offset++] = 0;
74
+ buf.set(parentHash, offset);
75
+ }
76
+ return buf;
77
+ }
78
+ /**
79
+ * Sign a memory entry, producing a ProvenanceRecord.
80
+ */
81
+ async function signEntry(key, value, privateKey, publicKey, parentHash) {
82
+ try {
83
+ const timestamp = Date.now();
84
+ const canonical = canonicalEntryBytes(key, value, timestamp, parentHash);
85
+ const signature = new Uint8Array(await crypto.subtle.sign('Ed25519', privateKey, canonical.buffer));
86
+ const author = await publicKeyToAuthorRef(publicKey);
87
+ return (0, shared_1.ok)({
88
+ author,
89
+ timestamp,
90
+ signature,
91
+ parentHash,
92
+ });
93
+ }
94
+ catch (e) {
95
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('INVALID_SIGNATURE', `Failed to sign entry: ${e instanceof Error ? e.message : String(e)}`));
96
+ }
97
+ }
98
+ /**
99
+ * Verify a provenance record against the entry data and author's public key.
100
+ */
101
+ async function verifyEntry(key, value, provenance, publicKey) {
102
+ try {
103
+ if (!provenance.author) {
104
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('MISSING_AUTHOR', 'Provenance record has no author'));
105
+ }
106
+ const canonical = canonicalEntryBytes(key, value, provenance.timestamp, provenance.parentHash);
107
+ const valid = await crypto.subtle.verify('Ed25519', publicKey, provenance.signature.buffer, canonical.buffer);
108
+ return (0, shared_1.ok)(valid);
109
+ }
110
+ catch (e) {
111
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('INVALID_SIGNATURE', `Verification failed: ${e instanceof Error ? e.message : String(e)}`));
112
+ }
113
+ }
114
+ /**
115
+ * Compute the SHA-256 hash of a provenance record for chain linking.
116
+ */
117
+ async function hashProvenance(provenance) {
118
+ const encoder = new TextEncoder();
119
+ const authorBytes = encoder.encode(provenance.author);
120
+ const tsBytes = encoder.encode(String(provenance.timestamp));
121
+ const parentLen = provenance.parentHash ? provenance.parentHash.length : 0;
122
+ const total = authorBytes.length + 1 + tsBytes.length + 1 + provenance.signature.length + parentLen;
123
+ const buf = new Uint8Array(total);
124
+ let offset = 0;
125
+ buf.set(authorBytes, offset);
126
+ offset += authorBytes.length;
127
+ buf[offset++] = 0;
128
+ buf.set(tsBytes, offset);
129
+ offset += tsBytes.length;
130
+ buf[offset++] = 0;
131
+ buf.set(provenance.signature, offset);
132
+ offset += provenance.signature.length;
133
+ if (provenance.parentHash) {
134
+ buf.set(provenance.parentHash, offset);
135
+ }
136
+ const hash = await crypto.subtle.digest('SHA-256', buf.buffer);
137
+ return new Uint8Array(hash);
138
+ }
139
+ /**
140
+ * Verify a parent hash chain link: the parentHash in the current entry
141
+ * must equal the hash of the previous entry's provenance.
142
+ */
143
+ async function verifyChainLink(current, previous) {
144
+ if (!current.provenance?.parentHash) {
145
+ return (0, shared_1.ok)(true); // No chain link to verify (root entry)
146
+ }
147
+ if (!previous.provenance) {
148
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('HASH_CHAIN_BREAK', `Previous entry "${previous.key}" has no provenance to hash`));
149
+ }
150
+ const expectedHash = await hashProvenance(previous.provenance);
151
+ const matches = constantTimeEqual(current.provenance.parentHash, expectedHash);
152
+ if (!matches) {
153
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('HASH_CHAIN_BREAK', `Parent hash mismatch for entry "${current.key}"`));
154
+ }
155
+ return (0, shared_1.ok)(true);
156
+ }
157
+ /* ── Internal helpers ── */
158
+ function canonicalValueString(value) {
159
+ if (value === null)
160
+ return 'null';
161
+ if (value instanceof Uint8Array)
162
+ return `bytes:${uint8ToBase64Url(value)}`;
163
+ if (typeof value === 'object')
164
+ return JSON.stringify(value, Object.keys(value).sort());
165
+ return String(value);
166
+ }
167
+ function uint8ToBase64Url(data) {
168
+ let binary = '';
169
+ for (let i = 0; i < data.length; i++) {
170
+ binary += String.fromCharCode(data[i]);
171
+ }
172
+ return btoa(binary).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
173
+ }
174
+ function base64UrlToUint8(b64url) {
175
+ const b64 = b64url.replace(/-/g, '+').replace(/_/g, '/');
176
+ const padded = b64 + '='.repeat((4 - (b64.length % 4)) % 4);
177
+ const binary = atob(padded);
178
+ const bytes = new Uint8Array(binary.length);
179
+ for (let i = 0; i < binary.length; i++) {
180
+ bytes[i] = binary.charCodeAt(i);
181
+ }
182
+ return bytes;
183
+ }
184
+ function constantTimeEqual(a, b) {
185
+ if (a.length !== b.length)
186
+ return false;
187
+ let diff = 0;
188
+ for (let i = 0; i < a.length; i++) {
189
+ diff |= a[i] ^ b[i];
190
+ }
191
+ return diff === 0;
192
+ }
@@ -0,0 +1,322 @@
1
+ "use strict";
2
+ /**
3
+ * @private.me/xcontinuity — Ratification (TrustStore)
4
+ *
5
+ * Central store for trust-annotated memory entries.
6
+ * Integrates provenance (signing), trust tiers, chronicle (history),
7
+ * adjudicator (conflict resolution), and event hooks.
8
+ *
9
+ * Key flows:
10
+ * write() — store an entry with automatic trust tier assignment
11
+ * ratify() — sign an entry, promoting it to 'ratified' tier
12
+ * restore() — rebuild trust state from a chronicle
13
+ * hypothesisMode() — sandbox for speculative writes
14
+ */
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.Hypothesis = exports.TrustStore = void 0;
17
+ const shared_1 = require("@private.me/shared");
18
+ const errors_js_1 = require("./errors.js");
19
+ const trust_js_1 = require("./trust.js");
20
+ const provenance_js_1 = require("./provenance.js");
21
+ const chronicle_js_1 = require("./chronicle.js");
22
+ const adjudicator_js_1 = require("./adjudicator.js");
23
+ class TrustStore {
24
+ entries = new Map();
25
+ chronicle = new chronicle_js_1.Chronicle();
26
+ adjudicator;
27
+ defaultMaxAge;
28
+ listeners = new Map();
29
+ sequenceCounter = 0;
30
+ constructor(config = {}) {
31
+ this.adjudicator = config.adjudicator ?? new adjudicator_js_1.PolicyAdjudicator();
32
+ this.defaultMaxAge = config.defaultMaxAge;
33
+ }
34
+ /**
35
+ * Write an entry to the trust store.
36
+ *
37
+ * Assigns trust tier based on provenance, checks for contradictions
38
+ * against the chronicle, and emits events.
39
+ */
40
+ write(key, value, provenance, signatureVerified = false) {
41
+ const tier = (0, trust_js_1.baselineTier)(provenance, signatureVerified);
42
+ const oldEntry = this.entries.get(key);
43
+ // Check for contradictions
44
+ const contradictions = [];
45
+ if (oldEntry !== undefined) {
46
+ if ((0, chronicle_js_1.detectContradiction)(value, oldEntry.value)) {
47
+ contradictions.push(oldEntry.key);
48
+ this.emit('contradiction', {
49
+ key,
50
+ existing: oldEntry,
51
+ incoming: { key, value, provenance, tier, contradictions, maxAge: this.defaultMaxAge },
52
+ });
53
+ }
54
+ }
55
+ const entry = {
56
+ key,
57
+ value,
58
+ provenance,
59
+ tier,
60
+ contradictions,
61
+ maxAge: this.defaultMaxAge,
62
+ ratifiedAt: tier === 'ratified' ? Date.now() : undefined,
63
+ };
64
+ this.entries.set(key, entry);
65
+ // Record in chronicle
66
+ const stateId = `entry-${this.sequenceCounter}`;
67
+ this.chronicle.appendWithContradictionCheck({
68
+ stateId,
69
+ sessionId: 'trust-store',
70
+ sequence: this.sequenceCounter++,
71
+ timestamp: Date.now(),
72
+ tags: [tier],
73
+ parentStateId: oldEntry ? `entry-${this.sequenceCounter - 2}` : undefined,
74
+ }, key, value, oldEntry?.value);
75
+ // Emit change event
76
+ this.emit('change', { key, oldEntry, newEntry: entry });
77
+ // Emit tier change if applicable
78
+ if (oldEntry && oldEntry.tier !== tier) {
79
+ this.emit('tierChange', {
80
+ key,
81
+ oldTier: oldEntry.tier,
82
+ newTier: tier,
83
+ reason: provenance ? 'provenance changed' : 'new write',
84
+ });
85
+ }
86
+ return (0, shared_1.ok)(entry);
87
+ }
88
+ /**
89
+ * Ratify an entry by signing it with Ed25519, promoting to 'ratified' tier.
90
+ */
91
+ async ratify(key, privateKey, publicKey) {
92
+ const existing = this.entries.get(key);
93
+ if (!existing) {
94
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('ENTRY_NOT_FOUND', `No entry for key "${key}"`));
95
+ }
96
+ // Sign the entry
97
+ const signResult = await (0, provenance_js_1.signEntry)(key, existing.value, privateKey, publicKey, existing.provenance?.parentHash);
98
+ if (!signResult.ok)
99
+ return signResult;
100
+ const oldTier = existing.tier;
101
+ const ratified = {
102
+ ...existing,
103
+ provenance: signResult.value,
104
+ tier: 'ratified',
105
+ ratifiedAt: Date.now(),
106
+ };
107
+ this.entries.set(key, ratified);
108
+ if (oldTier !== 'ratified') {
109
+ this.emit('tierChange', {
110
+ key,
111
+ oldTier,
112
+ newTier: 'ratified',
113
+ reason: 'ratified by signing',
114
+ });
115
+ }
116
+ this.emit('change', { key, oldEntry: existing, newEntry: ratified });
117
+ return (0, shared_1.ok)(ratified);
118
+ }
119
+ /**
120
+ * Read an entry, applying effective tier (decay + contradictions).
121
+ */
122
+ read(key) {
123
+ const entry = this.entries.get(key);
124
+ if (!entry)
125
+ return undefined;
126
+ const effective = (0, trust_js_1.effectiveTier)(entry);
127
+ if (effective !== entry.tier) {
128
+ // Update stored tier to reflect decay/contradictions
129
+ const updated = { ...entry, tier: effective };
130
+ this.entries.set(key, updated);
131
+ this.emit('tierChange', {
132
+ key,
133
+ oldTier: entry.tier,
134
+ newTier: effective,
135
+ reason: effective === 'inherited' ? 'TTL decay' : 'contradiction downgrade',
136
+ });
137
+ return updated;
138
+ }
139
+ return entry;
140
+ }
141
+ /**
142
+ * Read an entry only if it meets a minimum trust tier.
143
+ */
144
+ readAtTier(key, minTier) {
145
+ const entry = this.read(key);
146
+ if (!entry)
147
+ return undefined;
148
+ const tierRank = { ratified: 3, inherited: 2, quarantined: 1 };
149
+ if (tierRank[entry.tier] < tierRank[minTier])
150
+ return undefined;
151
+ return entry;
152
+ }
153
+ /** Check if a key exists in the store. */
154
+ has(key) {
155
+ return this.entries.has(key);
156
+ }
157
+ /** Delete an entry. */
158
+ delete(key) {
159
+ const existing = this.entries.get(key);
160
+ if (!existing)
161
+ return false;
162
+ this.entries.delete(key);
163
+ return true;
164
+ }
165
+ /** Get all keys. */
166
+ keys() {
167
+ return Array.from(this.entries.keys());
168
+ }
169
+ /** Get all entries. */
170
+ all() {
171
+ return Array.from(this.entries.values());
172
+ }
173
+ /** Get the chronicle (read-only access). */
174
+ getChronicle() {
175
+ return this.chronicle;
176
+ }
177
+ /** Get the entry count. */
178
+ get size() {
179
+ return this.entries.size;
180
+ }
181
+ /**
182
+ * Restore trust state from an array of memory entries.
183
+ * Used to rebuild state after loading from persistence.
184
+ */
185
+ restore(entries) {
186
+ this.entries.clear();
187
+ this.chronicle.clear();
188
+ this.sequenceCounter = 0;
189
+ for (const entry of entries) {
190
+ this.entries.set(entry.key, entry);
191
+ this.chronicle.append({
192
+ stateId: `entry-${this.sequenceCounter}`,
193
+ sessionId: 'trust-store',
194
+ sequence: this.sequenceCounter++,
195
+ timestamp: entry.provenance?.timestamp ?? Date.now(),
196
+ tags: [entry.tier],
197
+ });
198
+ }
199
+ }
200
+ /**
201
+ * Create a hypothesis (sandbox) that can be committed or discarded.
202
+ *
203
+ * All writes in the hypothesis are isolated from the main store.
204
+ * Call commit() to merge into the main store, or discard() to abandon.
205
+ */
206
+ hypothesisMode() {
207
+ // Snapshot current entries
208
+ const snapshot = new Map();
209
+ for (const [key, entry] of this.entries) {
210
+ snapshot.set(key, entry);
211
+ }
212
+ return new Hypothesis(this, snapshot);
213
+ }
214
+ /* ── Event System ── */
215
+ /** Subscribe to trust store events. */
216
+ on(event, listener) {
217
+ let set = this.listeners.get(event);
218
+ if (!set) {
219
+ set = new Set();
220
+ this.listeners.set(event, set);
221
+ }
222
+ set.add(listener);
223
+ }
224
+ /** Unsubscribe from trust store events. */
225
+ off(event, listener) {
226
+ const set = this.listeners.get(event);
227
+ if (set) {
228
+ set.delete(listener);
229
+ }
230
+ }
231
+ /** Remove all event listeners. */
232
+ removeAllListeners() {
233
+ this.listeners.clear();
234
+ }
235
+ /** Emit an event to all registered listeners. */
236
+ emit(event, payload) {
237
+ const set = this.listeners.get(event);
238
+ if (set) {
239
+ for (const listener of set) {
240
+ listener(payload);
241
+ }
242
+ }
243
+ }
244
+ /* ── Internal: used by Hypothesis ── */
245
+ /** @internal Merge entries from a hypothesis into the main store. */
246
+ _mergeHypothesis(entries) {
247
+ for (const [key, entry] of entries) {
248
+ const existing = this.entries.get(key);
249
+ this.entries.set(key, entry);
250
+ this.emit('change', { key, oldEntry: existing, newEntry: entry });
251
+ if (existing && existing.tier !== entry.tier) {
252
+ this.emit('tierChange', {
253
+ key,
254
+ oldTier: existing.tier,
255
+ newTier: entry.tier,
256
+ reason: 'hypothesis committed',
257
+ });
258
+ }
259
+ }
260
+ }
261
+ }
262
+ exports.TrustStore = TrustStore;
263
+ /**
264
+ * A sandboxed hypothesis for speculative writes.
265
+ *
266
+ * Changes are isolated until commit() is called.
267
+ * Memory is bounded — the hypothesis shares the parent's maxAge config.
268
+ */
269
+ class Hypothesis {
270
+ parent;
271
+ baseSnapshot;
272
+ changes = new Map();
273
+ committed = false;
274
+ discarded = false;
275
+ constructor(parent, baseSnapshot) {
276
+ this.parent = parent;
277
+ this.baseSnapshot = baseSnapshot;
278
+ }
279
+ /** Write a speculative entry (isolated from main store). */
280
+ write(key, value, tier = 'inherited') {
281
+ if (this.committed || this.discarded) {
282
+ throw new Error('Hypothesis already finalized');
283
+ }
284
+ const entry = {
285
+ key,
286
+ value,
287
+ tier,
288
+ contradictions: [],
289
+ };
290
+ this.changes.set(key, entry);
291
+ return entry;
292
+ }
293
+ /** Read from hypothesis (falls back to base snapshot). */
294
+ read(key) {
295
+ return this.changes.get(key) ?? this.baseSnapshot.get(key);
296
+ }
297
+ /** Commit all hypothesis changes into the main store. */
298
+ commit() {
299
+ if (this.committed || this.discarded) {
300
+ throw new Error('Hypothesis already finalized');
301
+ }
302
+ this.committed = true;
303
+ this.parent._mergeHypothesis(this.changes);
304
+ }
305
+ /** Discard all hypothesis changes. */
306
+ discard() {
307
+ if (this.committed || this.discarded) {
308
+ throw new Error('Hypothesis already finalized');
309
+ }
310
+ this.discarded = true;
311
+ this.changes.clear();
312
+ }
313
+ /** Check if the hypothesis has been finalized. */
314
+ get isFinalized() {
315
+ return this.committed || this.discarded;
316
+ }
317
+ /** Get the number of speculative changes. */
318
+ get changeCount() {
319
+ return this.changes.size;
320
+ }
321
+ }
322
+ exports.Hypothesis = Hypothesis;