@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,76 @@
1
+ /**
2
+ * @private.me/xcontinuity — Chronicle (State History)
3
+ *
4
+ * Append-only ordered state history with navigation.
5
+ * Secondary index (Map stateId->index) for O(1) lookup.
6
+ * Query with filters: sessionId, time range, tags, limit/offset pagination.
7
+ */
8
+ import type { Result } from '@private.me/shared';
9
+ import type { ChronicleEntry, ChronicleQuery, StateValue } from './types.js';
10
+ import type { ContinuityError } from './errors.js';
11
+ /** Extended chronicle entry with contradiction and revision tracking. */
12
+ export interface ChronicleContradiction {
13
+ /** The stateId of the new (incoming) entry. */
14
+ readonly incomingStateId: string;
15
+ /** The stateId of the existing (contradicted) entry. */
16
+ readonly existingStateId: string;
17
+ /** The key on which the contradiction was detected. */
18
+ readonly key: string;
19
+ /** Timestamp when the contradiction was detected. */
20
+ readonly detectedAt: number;
21
+ }
22
+ export declare class Chronicle {
23
+ private readonly entries;
24
+ private readonly indexByStateId;
25
+ /** Track the latest stateId for each key (for contradiction detection). */
26
+ private readonly latestByKey;
27
+ /** Detected contradictions. */
28
+ private readonly contradictions;
29
+ /** Append a new entry to the chronicle. */
30
+ append(entry: ChronicleEntry): void;
31
+ /**
32
+ * Append an entry and check for contradictions against the latest
33
+ * value for the same key. Returns detected contradictions (if any).
34
+ *
35
+ * @param entry - The chronicle entry
36
+ * @param key - The state key this entry represents
37
+ * @param value - The new value for the key
38
+ * @param existingValue - The most recent known value for the key (if any)
39
+ * @returns Array of contradictions detected (empty if none)
40
+ */
41
+ appendWithContradictionCheck(entry: ChronicleEntry, key: string, value: StateValue, existingValue?: StateValue): ChronicleContradiction[];
42
+ /** Get all detected contradictions. */
43
+ getContradictions(): readonly ChronicleContradiction[];
44
+ /** Get contradictions for a specific key. */
45
+ getContradictionsForKey(key: string): readonly ChronicleContradiction[];
46
+ /** Get the latest stateId recorded for a given key. */
47
+ getLatestForKey(key: string): string | undefined;
48
+ /** Get an entry by stateId. O(1) lookup. */
49
+ get(stateId: string): Result<ChronicleEntry, ContinuityError>;
50
+ /** Get the latest entry, or null if empty. */
51
+ latest(): ChronicleEntry | null;
52
+ /** Get entry at a specific sequence position. */
53
+ atSequence(sequence: number): Result<ChronicleEntry, ContinuityError>;
54
+ /** Get the parent entry (if parentStateId is set). */
55
+ parent(stateId: string): Result<ChronicleEntry, ContinuityError>;
56
+ /**
57
+ * Query chronicle entries with filters.
58
+ *
59
+ * @param query - Filter criteria (sessionId, time range, tags, limit/offset)
60
+ * @returns Matching entries ordered by timestamp
61
+ */
62
+ query(query?: ChronicleQuery): ChronicleEntry[];
63
+ /** Total number of entries. */
64
+ get length(): number;
65
+ /** Get all entries (readonly copy). */
66
+ all(): readonly ChronicleEntry[];
67
+ /** Clear all entries. */
68
+ clear(): void;
69
+ }
70
+ /**
71
+ * Detect whether two values for the same key contradict each other.
72
+ *
73
+ * Contradiction = values are structurally different.
74
+ * Identical values are not contradictions (they're confirmations).
75
+ */
76
+ export declare function detectContradiction(newValue: StateValue, existingValue: StateValue): boolean;
@@ -0,0 +1,173 @@
1
+ /**
2
+ * @private.me/xcontinuity — Chronicle (State History)
3
+ *
4
+ * Append-only ordered state history with navigation.
5
+ * Secondary index (Map stateId->index) for O(1) lookup.
6
+ * Query with filters: sessionId, time range, tags, limit/offset pagination.
7
+ */
8
+ import { ok, err } from '@private.me/shared';
9
+ import { continuityError } from './errors.js';
10
+ export class Chronicle {
11
+ entries = [];
12
+ indexByStateId = new Map();
13
+ /** Track the latest stateId for each key (for contradiction detection). */
14
+ latestByKey = new Map();
15
+ /** Detected contradictions. */
16
+ contradictions = [];
17
+ /** Append a new entry to the chronicle. */
18
+ append(entry) {
19
+ const idx = this.entries.length;
20
+ this.entries.push(entry);
21
+ this.indexByStateId.set(entry.stateId, idx);
22
+ }
23
+ /**
24
+ * Append an entry and check for contradictions against the latest
25
+ * value for the same key. Returns detected contradictions (if any).
26
+ *
27
+ * @param entry - The chronicle entry
28
+ * @param key - The state key this entry represents
29
+ * @param value - The new value for the key
30
+ * @param existingValue - The most recent known value for the key (if any)
31
+ * @returns Array of contradictions detected (empty if none)
32
+ */
33
+ appendWithContradictionCheck(entry, key, value, existingValue) {
34
+ const detected = [];
35
+ const previousStateId = this.latestByKey.get(key);
36
+ if (previousStateId !== undefined && existingValue !== undefined) {
37
+ if (detectContradiction(value, existingValue)) {
38
+ const contradiction = {
39
+ incomingStateId: entry.stateId,
40
+ existingStateId: previousStateId,
41
+ key,
42
+ detectedAt: Date.now(),
43
+ };
44
+ detected.push(contradiction);
45
+ this.contradictions.push(contradiction);
46
+ }
47
+ }
48
+ this.latestByKey.set(key, entry.stateId);
49
+ this.append(entry);
50
+ return detected;
51
+ }
52
+ /** Get all detected contradictions. */
53
+ getContradictions() {
54
+ return this.contradictions.slice();
55
+ }
56
+ /** Get contradictions for a specific key. */
57
+ getContradictionsForKey(key) {
58
+ return this.contradictions.filter(c => c.key === key);
59
+ }
60
+ /** Get the latest stateId recorded for a given key. */
61
+ getLatestForKey(key) {
62
+ return this.latestByKey.get(key);
63
+ }
64
+ /** Get an entry by stateId. O(1) lookup. */
65
+ get(stateId) {
66
+ const idx = this.indexByStateId.get(stateId);
67
+ if (idx === undefined) {
68
+ return err(continuityError('ENTRY_NOT_FOUND', `Chronicle entry ${stateId} not found`));
69
+ }
70
+ return ok(this.entries[idx]);
71
+ }
72
+ /** Get the latest entry, or null if empty. */
73
+ latest() {
74
+ if (this.entries.length === 0)
75
+ return null;
76
+ return this.entries[this.entries.length - 1];
77
+ }
78
+ /** Get entry at a specific sequence position. */
79
+ atSequence(sequence) {
80
+ const entry = this.entries.find(e => e.sequence === sequence);
81
+ if (!entry) {
82
+ return err(continuityError('ENTRY_NOT_FOUND', `No entry with sequence ${sequence}`));
83
+ }
84
+ return ok(entry);
85
+ }
86
+ /** Get the parent entry (if parentStateId is set). */
87
+ parent(stateId) {
88
+ const entryResult = this.get(stateId);
89
+ if (!entryResult.ok)
90
+ return entryResult;
91
+ const entry = entryResult.value;
92
+ if (!entry.parentStateId) {
93
+ return err(continuityError('ENTRY_NOT_FOUND', `Entry ${stateId} has no parent`));
94
+ }
95
+ return this.get(entry.parentStateId);
96
+ }
97
+ /**
98
+ * Query chronicle entries with filters.
99
+ *
100
+ * @param query - Filter criteria (sessionId, time range, tags, limit/offset)
101
+ * @returns Matching entries ordered by timestamp
102
+ */
103
+ query(query = {}) {
104
+ let results = this.entries.slice();
105
+ if (query.sessionId) {
106
+ results = results.filter(e => e.sessionId === query.sessionId);
107
+ }
108
+ if (query.after !== undefined) {
109
+ results = results.filter(e => e.timestamp >= query.after);
110
+ }
111
+ if (query.before !== undefined) {
112
+ results = results.filter(e => e.timestamp <= query.before);
113
+ }
114
+ if (query.tags && query.tags.length > 0) {
115
+ const tagSet = new Set(query.tags);
116
+ results = results.filter(e => e.tags.some(t => tagSet.has(t)));
117
+ }
118
+ // Pagination
119
+ const offset = query.offset ?? 0;
120
+ const limit = query.limit ?? results.length;
121
+ return results.slice(offset, offset + limit);
122
+ }
123
+ /** Total number of entries. */
124
+ get length() {
125
+ return this.entries.length;
126
+ }
127
+ /** Get all entries (readonly copy). */
128
+ all() {
129
+ return this.entries.slice();
130
+ }
131
+ /** Clear all entries. */
132
+ clear() {
133
+ this.entries.length = 0;
134
+ this.indexByStateId.clear();
135
+ this.latestByKey.clear();
136
+ this.contradictions.length = 0;
137
+ }
138
+ }
139
+ /**
140
+ * Detect whether two values for the same key contradict each other.
141
+ *
142
+ * Contradiction = values are structurally different.
143
+ * Identical values are not contradictions (they're confirmations).
144
+ */
145
+ export function detectContradiction(newValue, existingValue) {
146
+ // Null checks
147
+ if (newValue === null && existingValue === null)
148
+ return false;
149
+ if (newValue === null || existingValue === null)
150
+ return true;
151
+ // Uint8Array comparison
152
+ if (newValue instanceof Uint8Array && existingValue instanceof Uint8Array) {
153
+ if (newValue.length !== existingValue.length)
154
+ return true;
155
+ for (let i = 0; i < newValue.length; i++) {
156
+ if (newValue[i] !== existingValue[i])
157
+ return true;
158
+ }
159
+ return false;
160
+ }
161
+ // Type mismatch
162
+ if (typeof newValue !== typeof existingValue)
163
+ return true;
164
+ if (newValue instanceof Uint8Array || existingValue instanceof Uint8Array)
165
+ return true;
166
+ // Object comparison (deterministic JSON)
167
+ if (typeof newValue === 'object' && typeof existingValue === 'object') {
168
+ return JSON.stringify(newValue, Object.keys(newValue).sort())
169
+ !== JSON.stringify(existingValue, Object.keys(existingValue).sort());
170
+ }
171
+ // Primitive comparison
172
+ return newValue !== existingValue;
173
+ }
@@ -0,0 +1,189 @@
1
+ "use strict";
2
+ /**
3
+ * @private.me/xcontinuity — Adjudicator (Conflict Resolution)
4
+ *
5
+ * Resolves conflicts when multiple entries exist for the same key.
6
+ *
7
+ * Two implementations:
8
+ * PolicyAdjudicator — deterministic local resolution (default)
9
+ * ConsensusAdjudicator — multi-agent IXorIDA-backed resolution
10
+ *
11
+ * Deterministic tiebreaker (total ordering):
12
+ * 1. Highest trust tier
13
+ * 2. Newest timestamp
14
+ * 3. Lexicographically lowest author DID
15
+ *
16
+ * This guarantees two agents merging the same conflicting entries
17
+ * always pick the same winner — convergent merge without human input.
18
+ */
19
+ Object.defineProperty(exports, "__esModule", { value: true });
20
+ exports.ConsensusAdjudicator = exports.PolicyAdjudicator = void 0;
21
+ const shared_1 = require("@private.me/shared");
22
+ const types_js_1 = require("./types.js");
23
+ const errors_js_1 = require("./errors.js");
24
+ const trust_js_1 = require("./trust.js");
25
+ /**
26
+ * Policy-based adjudicator (default).
27
+ *
28
+ * Deterministic resolution using total ordering:
29
+ * 1. Highest effective trust tier (with decay applied)
30
+ * 2. Newest timestamp (higher = more recent)
31
+ * 3. Lexicographically lowest author DID (deterministic tiebreaker)
32
+ */
33
+ class PolicyAdjudicator {
34
+ resolve(key, candidates) {
35
+ if (candidates.length === 0) {
36
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('NO_CANDIDATES', `No candidates for key "${key}"`));
37
+ }
38
+ if (candidates.length === 1) {
39
+ return (0, shared_1.ok)({ winner: candidates[0], reason: 'single candidate' });
40
+ }
41
+ let winner = candidates[0];
42
+ let winnerTier = (0, trust_js_1.effectiveTier)(winner);
43
+ for (let i = 1; i < candidates.length; i++) {
44
+ const candidate = candidates[i];
45
+ const candidateTier = (0, trust_js_1.effectiveTier)(candidate);
46
+ const comparison = compareCandidates(winner, winnerTier, candidate, candidateTier);
47
+ if (comparison < 0) {
48
+ winner = candidate;
49
+ winnerTier = candidateTier;
50
+ }
51
+ }
52
+ const reason = buildReason(winner, winnerTier, candidates.length);
53
+ return (0, shared_1.ok)({ winner, reason });
54
+ }
55
+ }
56
+ exports.PolicyAdjudicator = PolicyAdjudicator;
57
+ /**
58
+ * Consensus-based adjudicator for multi-agent scenarios.
59
+ *
60
+ * Gathers views from participating agents via ViewProvider,
61
+ * then applies majority voting with policy fallback.
62
+ * Requires a quorum (> 50% of views must agree).
63
+ */
64
+ class ConsensusAdjudicator {
65
+ viewProvider;
66
+ policyFallback = new PolicyAdjudicator();
67
+ constructor(viewProvider) {
68
+ this.viewProvider = viewProvider;
69
+ }
70
+ resolve(key, candidates) {
71
+ // Synchronous interface — consensus requires async gatherViews.
72
+ // For synchronous resolve(), fall back to policy adjudication.
73
+ // Use resolveAsync() for full consensus.
74
+ return this.policyFallback.resolve(key, candidates);
75
+ }
76
+ /**
77
+ * Async resolution with full multi-agent consensus.
78
+ *
79
+ * @param key - The contested key
80
+ * @param candidates - Local candidates
81
+ * @returns Consensus-resolved winner
82
+ */
83
+ async resolveAsync(key, candidates) {
84
+ try {
85
+ const views = await this.viewProvider.gatherViews(key);
86
+ if (views.length === 0) {
87
+ return this.policyFallback.resolve(key, candidates);
88
+ }
89
+ // Combine local candidates with remote views
90
+ const allEntries = [
91
+ ...candidates,
92
+ ...views.map(v => v.entry),
93
+ ];
94
+ if (allEntries.length === 0) {
95
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('NO_CANDIDATES', `No candidates for key "${key}"`));
96
+ }
97
+ // Count votes by value (group entries with same effective value)
98
+ const voteGroups = groupByValue(allEntries);
99
+ const totalVotes = allEntries.length;
100
+ const quorum = Math.floor(totalVotes / 2) + 1;
101
+ // Find group with most votes
102
+ let bestGroup = voteGroups[0];
103
+ for (const group of voteGroups) {
104
+ if (group.count > bestGroup.count) {
105
+ bestGroup = group;
106
+ }
107
+ }
108
+ if (bestGroup.count >= quorum) {
109
+ // Quorum reached — use the best entry from the winning group
110
+ const groupResult = this.policyFallback.resolve(key, bestGroup.entries);
111
+ if (!groupResult.ok)
112
+ return groupResult;
113
+ return (0, shared_1.ok)({
114
+ winner: groupResult.value.winner,
115
+ reason: `consensus: ${bestGroup.count}/${totalVotes} votes (quorum=${quorum})`,
116
+ });
117
+ }
118
+ // No quorum — fall back to policy
119
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('QUORUM_NOT_MET', `Best group has ${bestGroup.count}/${totalVotes} votes, need ${quorum}`));
120
+ }
121
+ catch (e) {
122
+ return (0, shared_1.err)((0, errors_js_1.continuityError)('CONSENSUS_FAILED', `Consensus failed: ${e instanceof Error ? e.message : String(e)}`));
123
+ }
124
+ }
125
+ }
126
+ exports.ConsensusAdjudicator = ConsensusAdjudicator;
127
+ /* ── Internal helpers ── */
128
+ /**
129
+ * Compare two candidates using total ordering.
130
+ * Returns > 0 if a wins, < 0 if b wins, 0 if tied (shouldn't happen with DID tiebreaker).
131
+ */
132
+ function compareCandidates(a, aTier, b, bTier) {
133
+ // 1. Higher tier wins
134
+ const tierDiff = types_js_1.TRUST_TIER_RANK[aTier] - types_js_1.TRUST_TIER_RANK[bTier];
135
+ if (tierDiff !== 0)
136
+ return tierDiff;
137
+ // 2. Newer timestamp wins
138
+ const aTime = a.provenance?.timestamp ?? 0;
139
+ const bTime = b.provenance?.timestamp ?? 0;
140
+ if (aTime !== bTime)
141
+ return aTime - bTime;
142
+ // 3. Lexicographically lowest author DID wins (deterministic tiebreaker)
143
+ const aAuthor = a.provenance?.author ?? '';
144
+ const bAuthor = b.provenance?.author ?? '';
145
+ if (aAuthor !== bAuthor) {
146
+ // Lower DID wins — so if a < b, a wins (return > 0)
147
+ return aAuthor < bAuthor ? 1 : -1;
148
+ }
149
+ return 0;
150
+ }
151
+ function buildReason(winner, tier, candidateCount) {
152
+ const parts = [`tier=${tier}`];
153
+ if (winner.provenance) {
154
+ parts.push(`author=${winner.provenance.author.slice(0, 8)}...`);
155
+ parts.push(`ts=${winner.provenance.timestamp}`);
156
+ }
157
+ return `policy: selected from ${candidateCount} candidates (${parts.join(', ')})`;
158
+ }
159
+ function groupByValue(entries) {
160
+ const groups = new Map();
161
+ for (const entry of entries) {
162
+ const valueKey = canonicalValueKey(entry.value);
163
+ const group = groups.get(valueKey);
164
+ if (group) {
165
+ group.push(entry);
166
+ }
167
+ else {
168
+ groups.set(valueKey, [entry]);
169
+ }
170
+ }
171
+ return Array.from(groups.values()).map(entries => ({
172
+ entries,
173
+ count: entries.length,
174
+ }));
175
+ }
176
+ function canonicalValueKey(value) {
177
+ if (value === null)
178
+ return 'null';
179
+ if (value instanceof Uint8Array) {
180
+ let s = 'bytes:';
181
+ for (let i = 0; i < value.length; i++)
182
+ s += String.fromCharCode(value[i]);
183
+ return s;
184
+ }
185
+ if (typeof value === 'object') {
186
+ return JSON.stringify(value, Object.keys(value).sort());
187
+ }
188
+ return `${typeof value}:${String(value)}`;
189
+ }