@longarc/mdash 3.1.1 → 3.1.3
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/README.md +86 -23
- package/SECURITY.md +254 -0
- package/dist/accountability/engine.d.ts +27 -0
- package/dist/accountability/engine.d.ts.map +1 -0
- package/dist/accountability/engine.js +148 -0
- package/dist/accountability/engine.js.map +1 -0
- package/dist/accountability/types.d.ts +46 -0
- package/dist/accountability/types.d.ts.map +1 -0
- package/dist/accountability/types.js +8 -0
- package/dist/accountability/types.js.map +1 -0
- package/dist/checkpoint/engine.d.ts +2 -2
- package/dist/checkpoint/engine.d.ts.map +1 -1
- package/dist/checkpoint/engine.js +5 -1
- package/dist/checkpoint/engine.js.map +1 -1
- package/dist/context/compose.d.ts +62 -0
- package/dist/context/compose.d.ts.map +1 -0
- package/dist/context/compose.js +286 -0
- package/dist/context/compose.js.map +1 -0
- package/dist/context/crypto/hash.d.ts +100 -0
- package/dist/context/crypto/hash.d.ts.map +1 -0
- package/dist/context/crypto/hash.js +248 -0
- package/dist/context/crypto/hash.js.map +1 -0
- package/dist/context/crypto/hmac.d.ts +80 -0
- package/dist/context/crypto/hmac.d.ts.map +1 -0
- package/dist/context/crypto/hmac.js +192 -0
- package/dist/context/crypto/hmac.js.map +1 -0
- package/dist/context/crypto/index.d.ts +7 -0
- package/dist/context/crypto/index.d.ts.map +1 -0
- package/dist/context/crypto/index.js +7 -0
- package/dist/context/crypto/index.js.map +1 -0
- package/dist/context/engine-v3.0-backup.d.ts +197 -0
- package/dist/context/engine-v3.0-backup.d.ts.map +1 -0
- package/dist/context/engine-v3.0-backup.js +392 -0
- package/dist/context/engine-v3.0-backup.js.map +1 -0
- package/dist/context/engine.d.ts +2 -2
- package/dist/context/engine.d.ts.map +1 -1
- package/dist/context/engine.js +2 -2
- package/dist/context/engine.js.map +1 -1
- package/dist/context/fragment.d.ts +99 -0
- package/dist/context/fragment.d.ts.map +1 -0
- package/dist/context/fragment.js +316 -0
- package/dist/context/fragment.js.map +1 -0
- package/dist/context/index.d.ts +99 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +180 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/provenance.d.ts +80 -0
- package/dist/context/provenance.d.ts.map +1 -0
- package/dist/context/provenance.js +294 -0
- package/dist/context/provenance.js.map +1 -0
- package/dist/context/resolve.d.ts +106 -0
- package/dist/context/resolve.d.ts.map +1 -0
- package/dist/context/resolve.js +440 -0
- package/dist/context/resolve.js.map +1 -0
- package/dist/context/store.d.ts +156 -0
- package/dist/context/store.d.ts.map +1 -0
- package/dist/context/store.js +396 -0
- package/dist/context/store.js.map +1 -0
- package/dist/context/types.d.ts +463 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +94 -0
- package/dist/context/types.js.map +1 -0
- package/dist/context/utils/atomic.d.ts +76 -0
- package/dist/context/utils/atomic.d.ts.map +1 -0
- package/dist/context/utils/atomic.js +159 -0
- package/dist/context/utils/atomic.js.map +1 -0
- package/dist/context/utils/credit.d.ts +65 -0
- package/dist/context/utils/credit.d.ts.map +1 -0
- package/dist/context/utils/credit.js +164 -0
- package/dist/context/utils/credit.js.map +1 -0
- package/dist/context/utils/index.d.ts +13 -0
- package/dist/context/utils/index.d.ts.map +1 -0
- package/dist/context/utils/index.js +13 -0
- package/dist/context/utils/index.js.map +1 -0
- package/dist/context/utils/utility.d.ts +63 -0
- package/dist/context/utils/utility.d.ts.map +1 -0
- package/dist/context/utils/utility.js +141 -0
- package/dist/context/utils/utility.js.map +1 -0
- package/dist/core/commitment.d.ts +26 -3
- package/dist/core/commitment.d.ts.map +1 -1
- package/dist/core/commitment.js +45 -7
- package/dist/core/commitment.js.map +1 -1
- package/dist/core/crypto.d.ts +2 -0
- package/dist/core/crypto.d.ts.map +1 -1
- package/dist/core/crypto.js +12 -0
- package/dist/core/crypto.js.map +1 -1
- package/dist/index.d.ts +11 -6
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +35 -10
- package/dist/index.js.map +1 -1
- package/dist/mcca/engine.d.ts.map +1 -1
- package/dist/mcca/engine.js +5 -4
- package/dist/mcca/engine.js.map +1 -1
- package/dist/physics/engine.d.ts +3 -2
- package/dist/physics/engine.d.ts.map +1 -1
- package/dist/physics/engine.js +37 -3
- package/dist/physics/engine.js.map +1 -1
- package/dist/provenance/api-handler.d.ts +45 -0
- package/dist/provenance/api-handler.d.ts.map +1 -0
- package/dist/provenance/api-handler.js +223 -0
- package/dist/provenance/api-handler.js.map +1 -0
- package/dist/provenance/api-types.d.ts +108 -0
- package/dist/provenance/api-types.d.ts.map +1 -0
- package/dist/provenance/api-types.js +9 -0
- package/dist/provenance/api-types.js.map +1 -0
- package/dist/provenance/index.d.ts +6 -0
- package/dist/provenance/index.d.ts.map +1 -0
- package/dist/provenance/index.js +3 -0
- package/dist/provenance/index.js.map +1 -0
- package/dist/provenance/provenance-engine.d.ts +63 -0
- package/dist/provenance/provenance-engine.d.ts.map +1 -0
- package/dist/provenance/provenance-engine.js +311 -0
- package/dist/provenance/provenance-engine.js.map +1 -0
- package/dist/provenance/types.d.ts +193 -0
- package/dist/provenance/types.d.ts.map +1 -0
- package/dist/provenance/types.js +9 -0
- package/dist/provenance/types.js.map +1 -0
- package/dist/tee/engine.d.ts.map +1 -1
- package/dist/tee/engine.js +14 -0
- package/dist/tee/engine.js.map +1 -1
- package/dist/warrant/engine.d.ts +24 -1
- package/dist/warrant/engine.d.ts.map +1 -1
- package/dist/warrant/engine.js +76 -1
- package/dist/warrant/engine.js.map +1 -1
- package/dist/zk/engine.d.ts.map +1 -1
- package/dist/zk/engine.js +7 -4
- package/dist/zk/engine.js.map +1 -1
- package/docs/SECURITY-PATCHES.md +170 -0
- package/package.json +17 -5
- package/src/__tests__/accountability.test.ts +308 -0
- package/src/__tests__/l1-verification-modes.test.ts +424 -0
- package/src/__tests__/phase1.benchmark.test.ts +94 -0
- package/src/__tests__/phase1.test.ts +0 -77
- package/src/__tests__/phase2-4.benchmark.test.ts +60 -0
- package/src/__tests__/phase2-4.test.ts +1 -52
- package/src/__tests__/provenance/api-handler.test.ts +356 -0
- package/src/__tests__/provenance/provenance-engine.test.ts +628 -0
- package/src/__tests__/sa-2026-008.test.ts +45 -0
- package/src/__tests__/sa-2026-009.test.ts +86 -0
- package/src/__tests__/sa-2026-010.test.ts +72 -0
- package/src/__tests__/sa-2026-012.test.ts +65 -0
- package/src/__tests__/sa-2026-nfc.test.ts +40 -0
- package/src/__tests__/security.test.ts +786 -0
- package/src/accountability/engine.ts +230 -0
- package/src/accountability/types.ts +58 -0
- package/src/checkpoint/engine.ts +6 -2
- package/src/context/__tests__/caret-v0.2.0.test.ts +860 -0
- package/src/context/__tests__/integration.test.ts +356 -0
- package/src/context/compose.ts +388 -0
- package/src/context/crypto/hash.ts +277 -0
- package/src/context/crypto/hmac.ts +253 -0
- package/src/context/crypto/index.ts +29 -0
- package/src/context/engine-v3.0-backup.ts +598 -0
- package/src/context/engine.ts +2 -2
- package/src/context/fragment.ts +454 -0
- package/src/context/index.ts +427 -0
- package/src/context/provenance.ts +380 -0
- package/src/context/resolve.ts +581 -0
- package/src/context/store.ts +503 -0
- package/src/context/types.ts +679 -0
- package/src/context/utils/atomic.ts +207 -0
- package/src/context/utils/credit.ts +224 -0
- package/src/context/utils/index.ts +13 -0
- package/src/context/utils/utility.ts +200 -0
- package/src/core/commitment.ts +130 -68
- package/src/core/crypto.ts +13 -0
- package/src/index.ts +62 -10
- package/src/mcca/engine.ts +5 -4
- package/src/physics/engine.ts +42 -5
- package/src/provenance/api-handler.ts +248 -0
- package/src/provenance/api-types.ts +112 -0
- package/src/provenance/index.ts +19 -0
- package/src/provenance/provenance-engine.ts +387 -0
- package/src/provenance/types.ts +211 -0
- package/src/tee/engine.ts +16 -0
- package/src/warrant/engine.ts +89 -1
- package/src/zk/engine.ts +8 -4
- package/tsconfig.json +1 -1
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caret — Atomic Encoding Utilities
|
|
3
|
+
* @module @longarcstudios/caret/utils/atomic
|
|
4
|
+
*
|
|
5
|
+
* Coreference resolution for atomic attestation encoding.
|
|
6
|
+
* Transforms ambiguous text into explicit references.
|
|
7
|
+
*
|
|
8
|
+
* Rules:
|
|
9
|
+
* - Pronouns → Agent IDs
|
|
10
|
+
* - Relative time → ISO 8601 timestamps
|
|
11
|
+
* - "it/this/that" → Explicit entity IDs
|
|
12
|
+
* - Ambiguous verbs → MdashActionCode
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { CoreferenceContext, MdashActionCode } from '../types.js';
|
|
16
|
+
|
|
17
|
+
// ============================================================================
|
|
18
|
+
// COREFERENCE PATTERNS
|
|
19
|
+
// ============================================================================
|
|
20
|
+
|
|
21
|
+
interface CoreferencePattern {
|
|
22
|
+
pattern: RegExp;
|
|
23
|
+
resolver: (match: string, context: CoreferenceContext) => string;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const COREFERENCE_PATTERNS: CoreferencePattern[] = [
|
|
27
|
+
// Pronouns → Agent IDs
|
|
28
|
+
{
|
|
29
|
+
pattern: /\b(he|she|it|they)\b/gi,
|
|
30
|
+
resolver: (_, ctx) => ctx.lastAgentId ?? 'Agent_unknown',
|
|
31
|
+
},
|
|
32
|
+
// Relative time → ISO 8601
|
|
33
|
+
{
|
|
34
|
+
pattern: /\byesterday\b/gi,
|
|
35
|
+
resolver: (_, ctx) => {
|
|
36
|
+
const d = new Date(ctx.referenceTime);
|
|
37
|
+
d.setDate(d.getDate() - 1);
|
|
38
|
+
return d.toISOString();
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
{
|
|
42
|
+
pattern: /\btomorrow\b/gi,
|
|
43
|
+
resolver: (_, ctx) => {
|
|
44
|
+
const d = new Date(ctx.referenceTime);
|
|
45
|
+
d.setDate(d.getDate() + 1);
|
|
46
|
+
return d.toISOString();
|
|
47
|
+
},
|
|
48
|
+
},
|
|
49
|
+
{
|
|
50
|
+
pattern: /\bnow\b/gi,
|
|
51
|
+
resolver: (_, ctx) => new Date(ctx.referenceTime).toISOString(),
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
pattern: /\btoday\b/gi,
|
|
55
|
+
resolver: (_, ctx) => {
|
|
56
|
+
const d = new Date(ctx.referenceTime);
|
|
57
|
+
d.setHours(0, 0, 0, 0);
|
|
58
|
+
return d.toISOString();
|
|
59
|
+
},
|
|
60
|
+
},
|
|
61
|
+
// Implicit references → Explicit IDs
|
|
62
|
+
{
|
|
63
|
+
pattern: /\bthe warrant\b/gi,
|
|
64
|
+
resolver: (_, ctx) => ctx.lastWarrantId ?? 'Warrant_unknown',
|
|
65
|
+
},
|
|
66
|
+
{
|
|
67
|
+
pattern: /\bthe checkpoint\b/gi,
|
|
68
|
+
resolver: (_, ctx) => ctx.lastCheckpointId ?? 'Checkpoint_unknown',
|
|
69
|
+
},
|
|
70
|
+
{
|
|
71
|
+
pattern: /\bthe agent\b/gi,
|
|
72
|
+
resolver: (_, ctx) => ctx.lastAgentId ?? 'Agent_unknown',
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
|
|
76
|
+
// ============================================================================
|
|
77
|
+
// MAIN FUNCTIONS
|
|
78
|
+
// ============================================================================
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Resolve coreferences in a text string.
|
|
82
|
+
* Transforms ambiguous language into atomic references.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```ts
|
|
86
|
+
* const result = resolveCoreferences(
|
|
87
|
+
* "He approved it yesterday",
|
|
88
|
+
* {
|
|
89
|
+
* referenceTime: '2026-01-18T12:00:00Z',
|
|
90
|
+
* lastAgentId: 'Agent_0x7f3a',
|
|
91
|
+
* lastWarrantId: 'Warrant_0x8b2c'
|
|
92
|
+
* }
|
|
93
|
+
* );
|
|
94
|
+
* // "Agent_0x7f3a approved Warrant_0x8b2c 2026-01-17T12:00:00.000Z"
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export function resolveCoreferences(
|
|
98
|
+
text: string,
|
|
99
|
+
context: CoreferenceContext
|
|
100
|
+
): string {
|
|
101
|
+
let resolved = text;
|
|
102
|
+
|
|
103
|
+
for (const { pattern, resolver } of COREFERENCE_PATTERNS) {
|
|
104
|
+
resolved = resolved.replace(pattern, (match) => resolver(match, context));
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
return resolved;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Create a default coreference context with current time.
|
|
112
|
+
*/
|
|
113
|
+
export function createDefaultContext(
|
|
114
|
+
overrides?: Partial<CoreferenceContext>
|
|
115
|
+
): CoreferenceContext {
|
|
116
|
+
return {
|
|
117
|
+
referenceTime: new Date().toISOString(),
|
|
118
|
+
...overrides,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Check if text contains unresolved coreferences.
|
|
124
|
+
* Useful for validation before sealing.
|
|
125
|
+
*/
|
|
126
|
+
export function hasUnresolvedCoreferences(text: string): boolean {
|
|
127
|
+
const patterns = [
|
|
128
|
+
/\b(he|she|it|they)\b/i,
|
|
129
|
+
/\b(yesterday|tomorrow|now|today)\b/i,
|
|
130
|
+
/\bthe (warrant|checkpoint|agent)\b/i,
|
|
131
|
+
];
|
|
132
|
+
|
|
133
|
+
return patterns.some(p => p.test(text));
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Extract entity IDs from resolved text.
|
|
138
|
+
* Returns all Agent_, Warrant_, Checkpoint_, User_ prefixed IDs.
|
|
139
|
+
*/
|
|
140
|
+
export function extractEntityIds(text: string): string[] {
|
|
141
|
+
const pattern = /\b(Agent|Warrant|Checkpoint|User)_[a-f0-9]+\b/gi;
|
|
142
|
+
const matches = text.match(pattern);
|
|
143
|
+
return matches ? [...new Set(matches)] : [];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ============================================================================
|
|
147
|
+
// CANONICAL ENCODING
|
|
148
|
+
// ============================================================================
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Atomic attestation structure (pre-hash).
|
|
152
|
+
*/
|
|
153
|
+
export interface AtomicAttestationInput {
|
|
154
|
+
subject: string;
|
|
155
|
+
action: MdashActionCode;
|
|
156
|
+
object?: string;
|
|
157
|
+
timestamp: string;
|
|
158
|
+
context?: string;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
/**
|
|
162
|
+
* Create canonical encoding for commitment hash.
|
|
163
|
+
* Deterministic string representation suitable for hashing.
|
|
164
|
+
*/
|
|
165
|
+
export function canonicalEncode(attestation: AtomicAttestationInput): string {
|
|
166
|
+
const parts = [
|
|
167
|
+
attestation.subject,
|
|
168
|
+
attestation.action,
|
|
169
|
+
attestation.object ?? '',
|
|
170
|
+
attestation.timestamp,
|
|
171
|
+
attestation.context ?? '',
|
|
172
|
+
];
|
|
173
|
+
|
|
174
|
+
return parts.join('|');
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Compute SHA-256 hash of canonical encoding.
|
|
179
|
+
* Uses Web Crypto API for cross-platform compatibility.
|
|
180
|
+
*/
|
|
181
|
+
export async function computeCommitmentHash(
|
|
182
|
+
attestation: AtomicAttestationInput
|
|
183
|
+
): Promise<string> {
|
|
184
|
+
const canonical = canonicalEncode(attestation);
|
|
185
|
+
const encoder = new TextEncoder();
|
|
186
|
+
const data = encoder.encode(canonical);
|
|
187
|
+
|
|
188
|
+
const hashBuffer = await crypto.subtle.digest('SHA-256', data);
|
|
189
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
190
|
+
|
|
191
|
+
return hashArray.map(b => b.toString(16).padStart(2, '0')).join('');
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Validate atomic attestation subject format.
|
|
196
|
+
* Must be Entity_hexid pattern.
|
|
197
|
+
*/
|
|
198
|
+
export function isValidSubject(subject: string): boolean {
|
|
199
|
+
return /^(Agent|Warrant|Checkpoint|User)_[a-f0-9]+$/i.test(subject);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Validate ISO 8601 timestamp format.
|
|
204
|
+
*/
|
|
205
|
+
export function isValidTimestamp(timestamp: string): boolean {
|
|
206
|
+
return /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(\.\d+)?Z$/.test(timestamp);
|
|
207
|
+
}
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caret — Credit Assignment Utilities
|
|
3
|
+
* @module @longarcstudios/caret/utils/credit
|
|
4
|
+
*
|
|
5
|
+
* Step-wise credit assignment for execution traces.
|
|
6
|
+
* Distributes terminal reward proportionally to influence consumption.
|
|
7
|
+
*
|
|
8
|
+
* Properties:
|
|
9
|
+
* - Credits sum to terminal reward (conservation)
|
|
10
|
+
* - Higher influence steps receive higher credit
|
|
11
|
+
* - Works for arbitrarily long chains
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import type {
|
|
15
|
+
ExecutionTrace,
|
|
16
|
+
CreditAssignment,
|
|
17
|
+
StepCredit,
|
|
18
|
+
LiabilityShare,
|
|
19
|
+
InfluenceBudget,
|
|
20
|
+
Timestamp,
|
|
21
|
+
} from '../types.js';
|
|
22
|
+
import { sumInfluence } from '../types.js';
|
|
23
|
+
|
|
24
|
+
// ============================================================================
|
|
25
|
+
// CREDIT ASSIGNMENT
|
|
26
|
+
// ============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Assign credit to execution steps proportional to influence consumption.
|
|
30
|
+
* Terminal reward is distributed to all steps based on their contribution.
|
|
31
|
+
*
|
|
32
|
+
* @param trace - Execution trace with steps
|
|
33
|
+
* @param terminalReward - Final outcome reward to distribute
|
|
34
|
+
* @returns Credit assignment for all steps
|
|
35
|
+
*
|
|
36
|
+
* @example
|
|
37
|
+
* ```ts
|
|
38
|
+
* const credits = assignCredit(trace, 1.0);
|
|
39
|
+
* // Sum of credits.step_credits[].credit === 1.0
|
|
40
|
+
* ```
|
|
41
|
+
*/
|
|
42
|
+
export function assignCredit(
|
|
43
|
+
trace: ExecutionTrace,
|
|
44
|
+
terminalReward: number
|
|
45
|
+
): CreditAssignment {
|
|
46
|
+
// Compute total influence
|
|
47
|
+
const totalInfluence = trace.steps.reduce(
|
|
48
|
+
(sum, step) => sum + sumInfluence(step.influence_consumed),
|
|
49
|
+
0
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
// Distribute credit proportionally
|
|
53
|
+
const stepCredits: StepCredit[] = trace.steps.map((step) => {
|
|
54
|
+
const influence = sumInfluence(step.influence_consumed);
|
|
55
|
+
const influenceFraction = totalInfluence > 0 ? influence / totalInfluence : 0;
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
step_id: step.step_id,
|
|
59
|
+
credit: influenceFraction * terminalReward,
|
|
60
|
+
influence,
|
|
61
|
+
influence_fraction: influenceFraction,
|
|
62
|
+
};
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
trace_id: trace.trace_id,
|
|
67
|
+
terminal_reward: terminalReward,
|
|
68
|
+
step_credits: stepCredits,
|
|
69
|
+
assigned_at: new Date().toISOString() as Timestamp,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Validate that credit assignment conserves terminal reward.
|
|
75
|
+
* Credits should sum to terminal reward within tolerance.
|
|
76
|
+
*/
|
|
77
|
+
export function validateCreditAssignment(
|
|
78
|
+
assignment: CreditAssignment,
|
|
79
|
+
tolerance: number = 0.001
|
|
80
|
+
): boolean {
|
|
81
|
+
const creditSum = assignment.step_credits.reduce(
|
|
82
|
+
(acc, step) => acc + step.credit,
|
|
83
|
+
0
|
|
84
|
+
);
|
|
85
|
+
return Math.abs(creditSum - assignment.terminal_reward) < tolerance;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// ============================================================================
|
|
89
|
+
// LIABILITY ATTRIBUTION
|
|
90
|
+
// ============================================================================
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Extract agent ID from step ID.
|
|
94
|
+
* Convention: step IDs prefixed with agent identifier.
|
|
95
|
+
*/
|
|
96
|
+
function extractAgentId(stepId: string): string {
|
|
97
|
+
const match = stepId.match(/^(Agent_[a-f0-9]+)/i);
|
|
98
|
+
return match && match[1] ? match[1] : 'Agent_unknown';
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Attribute liability to contributing agents based on influence consumption.
|
|
103
|
+
*
|
|
104
|
+
* @param trace - Execution trace
|
|
105
|
+
* @returns Liability shares summing to 1.0
|
|
106
|
+
*/
|
|
107
|
+
export function attributeLiability(trace: ExecutionTrace): LiabilityShare[] {
|
|
108
|
+
// Compute total influence
|
|
109
|
+
const totalInfluence = trace.steps.reduce(
|
|
110
|
+
(sum, step) => sum + sumInfluence(step.influence_consumed),
|
|
111
|
+
0
|
|
112
|
+
);
|
|
113
|
+
|
|
114
|
+
if (totalInfluence === 0) {
|
|
115
|
+
return []; // No liability if no influence consumed
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Group steps by agent
|
|
119
|
+
const agentContributions = new Map<string, {
|
|
120
|
+
influence: number;
|
|
121
|
+
steps: string[];
|
|
122
|
+
}>();
|
|
123
|
+
|
|
124
|
+
for (const step of trace.steps) {
|
|
125
|
+
const agentId = extractAgentId(step.step_id);
|
|
126
|
+
const stepInfluence = sumInfluence(step.influence_consumed);
|
|
127
|
+
|
|
128
|
+
const existing = agentContributions.get(agentId) ?? { influence: 0, steps: [] };
|
|
129
|
+
existing.influence += stepInfluence;
|
|
130
|
+
existing.steps.push(step.step_id);
|
|
131
|
+
agentContributions.set(agentId, existing);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Convert to liability shares
|
|
135
|
+
return Array.from(agentContributions.entries()).map(([agentId, data]) => ({
|
|
136
|
+
agent_id: agentId,
|
|
137
|
+
contribution: data.influence / totalInfluence,
|
|
138
|
+
evidence: data.steps,
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Validate that liability shares sum to 1.0 within tolerance.
|
|
144
|
+
*/
|
|
145
|
+
export function validateLiabilityShares(
|
|
146
|
+
shares: LiabilityShare[],
|
|
147
|
+
tolerance: number = 0.001
|
|
148
|
+
): boolean {
|
|
149
|
+
if (shares.length === 0) return true; // Empty is valid
|
|
150
|
+
|
|
151
|
+
const sum = shares.reduce((acc, share) => acc + share.contribution, 0);
|
|
152
|
+
return Math.abs(sum - 1.0) < tolerance;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// ============================================================================
|
|
156
|
+
// INFLUENCE UTILITIES
|
|
157
|
+
// ============================================================================
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Create MCCA-compliant default influence budget.
|
|
161
|
+
*/
|
|
162
|
+
export function createDefaultInfluenceBudget(): InfluenceBudget {
|
|
163
|
+
return {
|
|
164
|
+
system: 0.45,
|
|
165
|
+
user: 0.30,
|
|
166
|
+
environment: 0.15,
|
|
167
|
+
assistant: 0.10,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Check if influence budget is MCCA-compliant.
|
|
173
|
+
*/
|
|
174
|
+
export function isMCCACompliant(budget: InfluenceBudget): boolean {
|
|
175
|
+
return (
|
|
176
|
+
budget.system >= 0.40 &&
|
|
177
|
+
budget.user <= 0.35 &&
|
|
178
|
+
budget.environment <= 0.20 &&
|
|
179
|
+
budget.assistant <= 0.25
|
|
180
|
+
);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
/**
|
|
184
|
+
* Normalize influence budget to sum to 1.0.
|
|
185
|
+
*/
|
|
186
|
+
export function normalizeInfluenceBudget(budget: InfluenceBudget): InfluenceBudget {
|
|
187
|
+
const total = sumInfluence(budget);
|
|
188
|
+
|
|
189
|
+
if (total === 0) {
|
|
190
|
+
return createDefaultInfluenceBudget();
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
return {
|
|
194
|
+
system: budget.system / total,
|
|
195
|
+
user: budget.user / total,
|
|
196
|
+
environment: budget.environment / total,
|
|
197
|
+
assistant: budget.assistant / total,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Add two influence budgets.
|
|
203
|
+
*/
|
|
204
|
+
export function addInfluenceBudgets(
|
|
205
|
+
a: InfluenceBudget,
|
|
206
|
+
b: InfluenceBudget
|
|
207
|
+
): InfluenceBudget {
|
|
208
|
+
return {
|
|
209
|
+
system: a.system + b.system,
|
|
210
|
+
user: a.user + b.user,
|
|
211
|
+
environment: a.environment + b.environment,
|
|
212
|
+
assistant: a.assistant + b.assistant,
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Compute total influence consumed in a trace.
|
|
218
|
+
*/
|
|
219
|
+
export function computeTraceInfluence(trace: ExecutionTrace): InfluenceBudget {
|
|
220
|
+
return trace.steps.reduce(
|
|
221
|
+
(total, step) => addInfluenceBudgets(total, step.influence_consumed),
|
|
222
|
+
{ system: 0, user: 0, environment: 0, assistant: 0 }
|
|
223
|
+
);
|
|
224
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caret — Utility Functions
|
|
3
|
+
* @module @longarcstudios/caret/utils
|
|
4
|
+
*
|
|
5
|
+
* Research pattern utilities for Caret v0.2.0:
|
|
6
|
+
* - Atomic encoding (coreference resolution)
|
|
7
|
+
* - Utility learning (incremental score updates)
|
|
8
|
+
* - Credit assignment (step-wise reward distribution)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export * from './atomic.js';
|
|
12
|
+
export * from './utility.js';
|
|
13
|
+
export * from './credit.js';
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Caret — Utility Learning Functions
|
|
3
|
+
* @module @longarcstudios/caret/utils/utility
|
|
4
|
+
*
|
|
5
|
+
* Incremental utility score learning.
|
|
6
|
+
* Implements: score ← score + α(reward - score)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type {
|
|
10
|
+
Outcome,
|
|
11
|
+
UtilityScore,
|
|
12
|
+
UtilityFeedback,
|
|
13
|
+
} from '../types.js';
|
|
14
|
+
|
|
15
|
+
// ============================================================================
|
|
16
|
+
// CONSTANTS
|
|
17
|
+
// ============================================================================
|
|
18
|
+
|
|
19
|
+
/** Default learning rate for utility updates */
|
|
20
|
+
const DEFAULT_ALPHA = 0.1;
|
|
21
|
+
|
|
22
|
+
/** Confidence saturation constant (updates needed for ~90% confidence) */
|
|
23
|
+
const CONFIDENCE_TAU = 10;
|
|
24
|
+
|
|
25
|
+
// ============================================================================
|
|
26
|
+
// UTILITY UPDATE
|
|
27
|
+
// ============================================================================
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Update utility score based on feedback.
|
|
31
|
+
* Implements incremental learning: score ← score + α(reward - score)
|
|
32
|
+
*
|
|
33
|
+
* @param current - Current utility score
|
|
34
|
+
* @param feedback - Feedback to incorporate
|
|
35
|
+
* @param alpha - Learning rate (default 0.1)
|
|
36
|
+
* @returns Updated utility score (immutable)
|
|
37
|
+
*
|
|
38
|
+
* @example
|
|
39
|
+
* ```ts
|
|
40
|
+
* const updated = updateUtilityScore(
|
|
41
|
+
* { score: 0.5, confidence: 0.3, update_count: 3 },
|
|
42
|
+
* { reward: 0.8, source: 'human', timestamp: '2026-01-18T12:00:00Z' }
|
|
43
|
+
* );
|
|
44
|
+
* // { score: 0.53, confidence: 0.33, update_count: 4 }
|
|
45
|
+
* ```
|
|
46
|
+
*/
|
|
47
|
+
export function updateUtilityScore(
|
|
48
|
+
current: UtilityScore,
|
|
49
|
+
feedback: UtilityFeedback,
|
|
50
|
+
alpha: number = DEFAULT_ALPHA
|
|
51
|
+
): UtilityScore {
|
|
52
|
+
// Clamp feedback reward to [-1, 1]
|
|
53
|
+
const reward = Math.max(-1, Math.min(1, feedback.reward));
|
|
54
|
+
|
|
55
|
+
// Incremental update
|
|
56
|
+
const newScore = Math.max(-1, Math.min(1,
|
|
57
|
+
current.score + alpha * (reward - current.score)
|
|
58
|
+
));
|
|
59
|
+
|
|
60
|
+
// Update confidence based on count
|
|
61
|
+
const newCount = current.update_count + 1;
|
|
62
|
+
const newConfidence = Math.min(0.99, 1 - Math.exp(-newCount / CONFIDENCE_TAU));
|
|
63
|
+
|
|
64
|
+
return {
|
|
65
|
+
score: newScore,
|
|
66
|
+
confidence: newConfidence,
|
|
67
|
+
update_count: newCount,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Update an Outcome's utility score based on feedback.
|
|
73
|
+
* Returns a new Outcome with updated utility (immutable).
|
|
74
|
+
*/
|
|
75
|
+
export function updateOutcomeUtility<T>(
|
|
76
|
+
outcome: Outcome<T>,
|
|
77
|
+
feedback: UtilityFeedback,
|
|
78
|
+
alpha: number = DEFAULT_ALPHA
|
|
79
|
+
): Outcome<T> {
|
|
80
|
+
return {
|
|
81
|
+
...outcome,
|
|
82
|
+
utility: updateUtilityScore(outcome.utility, feedback, alpha),
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ============================================================================
|
|
87
|
+
// UTILITY INITIALIZATION
|
|
88
|
+
// ============================================================================
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Create a fresh utility score (no observations yet).
|
|
92
|
+
*/
|
|
93
|
+
export function createInitialUtility(priorScore: number = 0): UtilityScore {
|
|
94
|
+
return {
|
|
95
|
+
score: Math.max(-1, Math.min(1, priorScore)),
|
|
96
|
+
confidence: 0,
|
|
97
|
+
update_count: 0,
|
|
98
|
+
};
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Create utility score from historical observations.
|
|
103
|
+
*/
|
|
104
|
+
export function createUtilityFromHistory(
|
|
105
|
+
rewards: number[],
|
|
106
|
+
alpha: number = DEFAULT_ALPHA
|
|
107
|
+
): UtilityScore {
|
|
108
|
+
let utility = createInitialUtility();
|
|
109
|
+
|
|
110
|
+
for (const reward of rewards) {
|
|
111
|
+
utility = updateUtilityScore(
|
|
112
|
+
utility,
|
|
113
|
+
{ reward, source: 'system', timestamp: new Date().toISOString() as any },
|
|
114
|
+
alpha
|
|
115
|
+
);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return utility;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// ============================================================================
|
|
122
|
+
// UTILITY PREDICTION
|
|
123
|
+
// ============================================================================
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Predict success probability from historical outcomes.
|
|
127
|
+
* Uses exponential moving average with recency bias.
|
|
128
|
+
*
|
|
129
|
+
* @param historicalScores - Array of past utility scores
|
|
130
|
+
* @param decayFactor - Weight decay per position (default 0.9)
|
|
131
|
+
* @returns Predicted success probability [0, 1]
|
|
132
|
+
*/
|
|
133
|
+
export function predictSuccess(
|
|
134
|
+
historicalScores: UtilityScore[],
|
|
135
|
+
decayFactor: number = 0.9
|
|
136
|
+
): number {
|
|
137
|
+
if (historicalScores.length === 0) {
|
|
138
|
+
return 0.5; // Uninformative prior
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Sort by update count (proxy for recency if no timestamps)
|
|
142
|
+
const sorted = [...historicalScores].sort(
|
|
143
|
+
(a, b) => b.update_count - a.update_count
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
let weightedSum = 0;
|
|
147
|
+
let weightSum = 0;
|
|
148
|
+
|
|
149
|
+
for (let i = 0; i < sorted.length; i++) {
|
|
150
|
+
const score = sorted[i];
|
|
151
|
+
if (!score) continue;
|
|
152
|
+
|
|
153
|
+
const weight = Math.pow(decayFactor, i);
|
|
154
|
+
// Map score from [-1, 1] to [0, 1] for probability
|
|
155
|
+
const prob = (score.score + 1) / 2;
|
|
156
|
+
|
|
157
|
+
weightedSum += weight * prob;
|
|
158
|
+
weightSum += weight;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return weightedSum / weightSum;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Compute expected utility given success probability and reward magnitudes.
|
|
166
|
+
*/
|
|
167
|
+
export function expectedUtility(
|
|
168
|
+
successProb: number,
|
|
169
|
+
successReward: number,
|
|
170
|
+
failureReward: number
|
|
171
|
+
): number {
|
|
172
|
+
return successProb * successReward + (1 - successProb) * failureReward;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ============================================================================
|
|
176
|
+
// UTILITY COMPARISON
|
|
177
|
+
// ============================================================================
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Compare two utility scores, accounting for confidence.
|
|
181
|
+
* Returns positive if a > b, negative if a < b, zero if equivalent.
|
|
182
|
+
*/
|
|
183
|
+
export function compareUtility(a: UtilityScore, b: UtilityScore): number {
|
|
184
|
+
// Weight by confidence
|
|
185
|
+
const aWeighted = a.score * a.confidence;
|
|
186
|
+
const bWeighted = b.score * b.confidence;
|
|
187
|
+
|
|
188
|
+
return aWeighted - bWeighted;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Select the best outcome from a list by utility.
|
|
193
|
+
*/
|
|
194
|
+
export function selectBestOutcome<T>(outcomes: Outcome<T>[]): Outcome<T> | null {
|
|
195
|
+
if (outcomes.length === 0) return null;
|
|
196
|
+
|
|
197
|
+
return outcomes.reduce((best, current) =>
|
|
198
|
+
compareUtility(current.utility, best.utility) > 0 ? current : best
|
|
199
|
+
);
|
|
200
|
+
}
|