@buildaureon/sdk 0.1.1 → 0.1.7
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 +655 -640
- package/dist/index.d.ts +713 -89
- package/dist/index.js +1754 -190
- package/dist/index.js.map +1 -1
- package/docs/architecture.md +206 -206
- package/docs/auth.md +174 -174
- package/docs/client-api.md +782 -605
- package/docs/data-contracts.md +710 -635
- package/docs/error-model.md +217 -217
- package/docs/integration-guide.md +397 -257
- package/docs/receipt-validation.md +63 -0
- package/docs/security.md +120 -120
- package/docs/transport.md +142 -142
- package/examples/ai-to-objective-to-portfolio/main.ts +127 -0
- package/examples/audit-trail/main.ts +53 -0
- package/examples/drift-detect-restore/main.ts +96 -0
- package/examples/full-aureon-loop/main.ts +83 -0
- package/examples/green-vs-plan/main.ts +139 -0
- package/examples/market-event/main.ts +67 -65
- package/examples/portfolio-watch/main.ts +84 -0
- package/examples/quickstart/main.ts +72 -72
- package/examples/receipt-verification/main.ts +87 -0
- package/examples/registry-register/main.ts +44 -0
- package/package.json +8 -1
package/dist/index.js
CHANGED
|
@@ -14,7 +14,7 @@ function userAgentHeader(version) {
|
|
|
14
14
|
var DEFAULT_API_BASE_URL = "https://api.aureonlabs.network";
|
|
15
15
|
var LOCAL_API_BASE_URL = "http://127.0.0.1:8787";
|
|
16
16
|
var DEFAULT_TIMEOUT_MS = 3e4;
|
|
17
|
-
var SDK_VERSION = "0.1.
|
|
17
|
+
var SDK_VERSION = "0.1.7";
|
|
18
18
|
var SDK_NAME = "@buildaureon/sdk";
|
|
19
19
|
var PRODUCT_NAME = "AUREON";
|
|
20
20
|
var PRODUCT_TAGLINE = "Financial Compass for Robinhood Chain";
|
|
@@ -44,7 +44,9 @@ var ENDPOINTS = {
|
|
|
44
44
|
authLogout: "/auth/logout",
|
|
45
45
|
authDevLogin: "/auth/dev-login",
|
|
46
46
|
authMe: "/auth/me",
|
|
47
|
-
developerApiKeys: "/developer/api-keys"
|
|
47
|
+
developerApiKeys: "/developer/api-keys",
|
|
48
|
+
registryStatus: "/registry/status",
|
|
49
|
+
settlements: "/settlements"
|
|
48
50
|
};
|
|
49
51
|
function objectivePath(id) {
|
|
50
52
|
return `${ENDPOINTS.objectives}/${encodeURIComponent(id)}`;
|
|
@@ -64,6 +66,21 @@ function objectiveRestorePath(id) {
|
|
|
64
66
|
function developerApiKeyPath(id) {
|
|
65
67
|
return `${ENDPOINTS.developerApiKeys}/${encodeURIComponent(id)}`;
|
|
66
68
|
}
|
|
69
|
+
function registryObjectivePath(id) {
|
|
70
|
+
return `/registry/objectives/${encodeURIComponent(id)}`;
|
|
71
|
+
}
|
|
72
|
+
function registryPreparePath(id) {
|
|
73
|
+
return `${registryObjectivePath(id)}/prepare-register`;
|
|
74
|
+
}
|
|
75
|
+
function registryConfirmPath(id) {
|
|
76
|
+
return `${registryObjectivePath(id)}/confirm`;
|
|
77
|
+
}
|
|
78
|
+
function executionSettlementPath(id) {
|
|
79
|
+
return `/executions/${encodeURIComponent(id)}/settlement`;
|
|
80
|
+
}
|
|
81
|
+
function executionConfirmSettlementPath(id) {
|
|
82
|
+
return `/executions/${encodeURIComponent(id)}/confirm-settlement`;
|
|
83
|
+
}
|
|
67
84
|
|
|
68
85
|
// src/errors/codes.ts
|
|
69
86
|
var RETRYABLE_CODES = [
|
|
@@ -158,22 +175,1110 @@ var AureonTimeoutError = class extends AureonError {
|
|
|
158
175
|
}
|
|
159
176
|
};
|
|
160
177
|
|
|
161
|
-
// src/
|
|
178
|
+
// src/formatting/allocation.ts
|
|
179
|
+
var ACTIVE_STATUSES = /* @__PURE__ */ new Set(["active", "validated"]);
|
|
180
|
+
function isOffPlan(state) {
|
|
181
|
+
return state === "warning" || state === "violation";
|
|
182
|
+
}
|
|
183
|
+
function buildAllocationComparison(objectives, health) {
|
|
184
|
+
const healthById = new Map(health.map((h) => [h.objectiveId, h]));
|
|
185
|
+
return objectives.filter((o) => ACTIVE_STATUSES.has(o.status)).map((objective) => {
|
|
186
|
+
const record = healthById.get(objective.id);
|
|
187
|
+
const targetWeight = objective.policy?.targetWeight ?? record?.targetMetric ?? 0;
|
|
188
|
+
const currentMetric = record?.currentMetric ?? 0;
|
|
189
|
+
return {
|
|
190
|
+
objectiveId: objective.id,
|
|
191
|
+
name: objective.name,
|
|
192
|
+
kind: objective.kind,
|
|
193
|
+
targetSymbol: objective.policy?.targetSymbol,
|
|
194
|
+
targetWeight,
|
|
195
|
+
currentMetric,
|
|
196
|
+
deviation: record?.deviation ?? currentMetric - targetWeight,
|
|
197
|
+
state: record?.state ?? "paused"
|
|
198
|
+
};
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
function detectPlanParadox(overview, health) {
|
|
202
|
+
const offPlan = health.filter((h) => isOffPlan(h.state));
|
|
203
|
+
const offPlanCount = offPlan.length;
|
|
204
|
+
const bookUp = overview.change24hPct != null && !overview.change24hBaselineOnly ? overview.change24hPct >= 0 : overview.attentionCount > 0 && overview.totalNotionalUsd > 0;
|
|
205
|
+
const detected = bookUp && offPlanCount > 0;
|
|
206
|
+
let message = "Portfolio and objectives are aligned.";
|
|
207
|
+
if (detected) {
|
|
208
|
+
const pct = overview.change24hPct != null && !overview.change24hBaselineOnly ? `${(overview.change24hPct * 100).toFixed(1)}%` : "recent activity";
|
|
209
|
+
message = `Book is up (${pct}), but ${offPlanCount} objective${offPlanCount === 1 ? "" : "s"} are off-plan.`;
|
|
210
|
+
} else if (offPlanCount > 0) {
|
|
211
|
+
message = `${offPlanCount} objective${offPlanCount === 1 ? "" : "s"} need attention.`;
|
|
212
|
+
}
|
|
213
|
+
return { detected, bookUp, offPlanCount, message };
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/types/objective.ts
|
|
217
|
+
var OBJECTIVE_KINDS = [
|
|
218
|
+
"stable_allocation",
|
|
219
|
+
"balanced_portfolio",
|
|
220
|
+
"risk_ceiling",
|
|
221
|
+
"reward_reinvestment"
|
|
222
|
+
];
|
|
223
|
+
var OBJECTIVE_PRIORITIES = [
|
|
224
|
+
"low",
|
|
225
|
+
"medium",
|
|
226
|
+
"high",
|
|
227
|
+
"critical"
|
|
228
|
+
];
|
|
229
|
+
function isObjectiveKind(value) {
|
|
230
|
+
return OBJECTIVE_KINDS.includes(value);
|
|
231
|
+
}
|
|
232
|
+
function isObjectivePriority(value) {
|
|
233
|
+
return OBJECTIVE_PRIORITIES.includes(value);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// src/validation/objective-input.ts
|
|
237
|
+
function buildPolicySummary(kind, targetWeight, tolerance) {
|
|
238
|
+
const targetPct = (targetWeight * 100).toFixed(1);
|
|
239
|
+
const tolPct = (tolerance * 100).toFixed(1);
|
|
240
|
+
switch (kind) {
|
|
241
|
+
case "stable_allocation":
|
|
242
|
+
return `Maintain ${targetPct}% stable allocation within \xB1${tolPct}%`;
|
|
243
|
+
case "balanced_portfolio":
|
|
244
|
+
return `Hold balanced weights near ${targetPct}% primary sleeve within \xB1${tolPct}%`;
|
|
245
|
+
case "risk_ceiling":
|
|
246
|
+
return `Keep portfolio risk at or below configured ceiling with ${tolPct}% buffer`;
|
|
247
|
+
case "reward_reinvestment":
|
|
248
|
+
return `Reinvest available rewards toward ${targetPct}% target sleeve`;
|
|
249
|
+
default:
|
|
250
|
+
return `Objective policy target ${targetPct}% \xB1${tolPct}%`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
function normalizeCreateObjectiveInput(input) {
|
|
254
|
+
const name = input.name?.trim();
|
|
255
|
+
if (!name || name.length < 3) {
|
|
256
|
+
throw new AureonValidationError("Objective name must be at least 3 characters");
|
|
257
|
+
}
|
|
258
|
+
if (!isObjectiveKind(input.kind)) {
|
|
259
|
+
throw new AureonValidationError(`Unsupported objective kind: ${input.kind}`);
|
|
260
|
+
}
|
|
261
|
+
if (input.targetWeight < 0 || input.targetWeight > 1) {
|
|
262
|
+
throw new AureonValidationError("targetWeight must be between 0 and 1");
|
|
263
|
+
}
|
|
264
|
+
if (input.tolerance < 0 || input.tolerance > 0.5) {
|
|
265
|
+
throw new AureonValidationError("tolerance must be between 0 and 0.5");
|
|
266
|
+
}
|
|
267
|
+
const priority = input.priority ?? "high";
|
|
268
|
+
if (!isObjectivePriority(priority)) {
|
|
269
|
+
throw new AureonValidationError(`Unsupported priority: ${priority}`);
|
|
270
|
+
}
|
|
271
|
+
if (input.kind === "balanced_portfolio") {
|
|
272
|
+
const symbol = input.targetSymbol?.trim().toUpperCase();
|
|
273
|
+
if (!symbol) {
|
|
274
|
+
throw new AureonValidationError(
|
|
275
|
+
"balanced_portfolio requires targetSymbol"
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
...input,
|
|
280
|
+
name,
|
|
281
|
+
priority,
|
|
282
|
+
targetSymbol: symbol,
|
|
283
|
+
// SDK / agent path defaults to Automatic. Explicit "manual" is reserved
|
|
284
|
+
// for the operator utility Approve UX; not recommended for integrations.
|
|
285
|
+
automationMode: input.automationMode === "manual" ? "manual" : "auto"
|
|
286
|
+
};
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
...input,
|
|
290
|
+
name,
|
|
291
|
+
priority,
|
|
292
|
+
targetSymbol: null,
|
|
293
|
+
automationMode: input.automationMode === "manual" ? "manual" : "auto"
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
function normalizeUpdateObjectiveInput(input) {
|
|
297
|
+
const next = { ...input };
|
|
298
|
+
if (next.name !== void 0) {
|
|
299
|
+
const name = next.name.trim();
|
|
300
|
+
if (name.length < 3) {
|
|
301
|
+
throw new AureonValidationError("Objective name must be at least 3 characters");
|
|
302
|
+
}
|
|
303
|
+
next.name = name;
|
|
304
|
+
}
|
|
305
|
+
if (next.targetWeight !== void 0) {
|
|
306
|
+
if (next.targetWeight < 0 || next.targetWeight > 1) {
|
|
307
|
+
throw new AureonValidationError("targetWeight must be between 0 and 1");
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
if (next.tolerance !== void 0) {
|
|
311
|
+
if (next.tolerance < 0 || next.tolerance > 0.5) {
|
|
312
|
+
throw new AureonValidationError("tolerance must be between 0 and 0.5");
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
if (next.priority !== void 0 && !isObjectivePriority(next.priority)) {
|
|
316
|
+
throw new AureonValidationError(`Unsupported priority: ${next.priority}`);
|
|
317
|
+
}
|
|
318
|
+
if (next.automationMode !== void 0) {
|
|
319
|
+
throw new AureonValidationError(
|
|
320
|
+
"automationMode cannot be changed after create: recreate the objective instead"
|
|
321
|
+
);
|
|
322
|
+
}
|
|
323
|
+
if (input.targetSymbol !== void 0) {
|
|
324
|
+
throw new AureonValidationError(
|
|
325
|
+
"targetSymbol cannot be changed after create: recreate the objective instead"
|
|
326
|
+
);
|
|
327
|
+
}
|
|
328
|
+
return next;
|
|
329
|
+
}
|
|
330
|
+
function assertId(value, label) {
|
|
331
|
+
if (!value || typeof value !== "string" || value.trim().length < 8) {
|
|
332
|
+
throw new AureonValidationError(`Invalid ${label}`);
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// src/formatting/drift-restore.ts
|
|
337
|
+
function isOffPlan2(state) {
|
|
338
|
+
return state === "warning" || state === "violation";
|
|
339
|
+
}
|
|
340
|
+
function inferDriftPhase(health) {
|
|
341
|
+
if (health.state === "healthy") return "aligned";
|
|
342
|
+
if (isOffPlan2(health.state)) return "drift_detected";
|
|
343
|
+
return "aligned";
|
|
344
|
+
}
|
|
345
|
+
function buildDriftRestoreFlow(input) {
|
|
346
|
+
const targetWeight = input.objective.policy?.targetWeight ?? input.alignedHealth.targetMetric ?? 0;
|
|
347
|
+
const tolerance = input.objective.policy?.tolerance ?? 0.02;
|
|
348
|
+
const summary = input.objective.policy?.summary ?? buildPolicySummary(input.objective.kind, targetWeight, tolerance);
|
|
349
|
+
const restored = input.restoredHealth ? {
|
|
350
|
+
health: input.restoredHealth,
|
|
351
|
+
receipt: input.receipt,
|
|
352
|
+
settlement: input.receipt?.settlement
|
|
353
|
+
} : void 0;
|
|
354
|
+
const currentPhase = restored ? restored.health.state === "healthy" || !isOffPlan2(restored.health.state) ? "restored" : "drift_detected" : inferDriftPhase(input.driftHealth);
|
|
355
|
+
let message = "Rule set and portfolio aligned. Ready to detect drift when marks move.";
|
|
356
|
+
if (currentPhase === "drift_detected") {
|
|
357
|
+
message = "We broke the rule on purpose. AUREON detected drift \u2014 allocation moved off policy.";
|
|
358
|
+
} else if (currentPhase === "restored") {
|
|
359
|
+
const settlement = input.receipt?.settlement ?? "staged";
|
|
360
|
+
message = `Drift detected and restore completed (${settlement} settlement). Policy is back within tolerance.`;
|
|
361
|
+
}
|
|
362
|
+
return {
|
|
363
|
+
objectiveId: input.objective.id,
|
|
364
|
+
rule: { summary, targetWeight, tolerance },
|
|
365
|
+
phases: {
|
|
366
|
+
aligned: {
|
|
367
|
+
health: input.alignedHealth,
|
|
368
|
+
allocationRow: input.alignedRow
|
|
369
|
+
},
|
|
370
|
+
drift: {
|
|
371
|
+
health: input.driftHealth,
|
|
372
|
+
allocationRow: input.driftRow,
|
|
373
|
+
restorePlan: input.driftPlan
|
|
374
|
+
},
|
|
375
|
+
restored
|
|
376
|
+
},
|
|
377
|
+
currentPhase,
|
|
378
|
+
message
|
|
379
|
+
};
|
|
380
|
+
}
|
|
381
|
+
function buildDriftRestoreFlowFromSnapshot(input) {
|
|
382
|
+
const targetWeight = input.objective.policy?.targetWeight ?? input.health.targetMetric ?? 0;
|
|
383
|
+
const offPlan = isOffPlan2(input.health.state);
|
|
384
|
+
const hasRestore = input.latestReceipt && input.latestReceipt.objectiveId === input.objective.id;
|
|
385
|
+
if (!offPlan && !hasRestore) {
|
|
386
|
+
return buildDriftRestoreFlow({
|
|
387
|
+
objective: input.objective,
|
|
388
|
+
alignedHealth: input.health,
|
|
389
|
+
driftHealth: input.health,
|
|
390
|
+
alignedRow: input.allocationRow,
|
|
391
|
+
driftRow: input.allocationRow
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
if (offPlan) {
|
|
395
|
+
return buildDriftRestoreFlow({
|
|
396
|
+
objective: input.objective,
|
|
397
|
+
alignedHealth: {
|
|
398
|
+
...input.health,
|
|
399
|
+
state: "healthy",
|
|
400
|
+
currentMetric: targetWeight,
|
|
401
|
+
deviation: 0,
|
|
402
|
+
message: "On track \u2014 still inside your target range."
|
|
403
|
+
},
|
|
404
|
+
driftHealth: input.health,
|
|
405
|
+
driftPlan: input.restorePlan,
|
|
406
|
+
alignedRow: input.allocationRow,
|
|
407
|
+
driftRow: input.allocationRow
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
return buildDriftRestoreFlow({
|
|
411
|
+
objective: input.objective,
|
|
412
|
+
alignedHealth: {
|
|
413
|
+
...input.health,
|
|
414
|
+
state: "healthy",
|
|
415
|
+
currentMetric: targetWeight,
|
|
416
|
+
deviation: 0,
|
|
417
|
+
message: "On track \u2014 still inside your target range."
|
|
418
|
+
},
|
|
419
|
+
driftHealth: {
|
|
420
|
+
...input.health,
|
|
421
|
+
state: "warning",
|
|
422
|
+
message: "Prior drift detected."
|
|
423
|
+
},
|
|
424
|
+
restoredHealth: input.health,
|
|
425
|
+
receipt: input.latestReceipt,
|
|
426
|
+
alignedRow: input.allocationRow,
|
|
427
|
+
driftRow: input.allocationRow
|
|
428
|
+
});
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
// src/types/execution.ts
|
|
432
|
+
function shortTransactionHash(hash, head = 10, tail = 6) {
|
|
433
|
+
if (hash.length <= head + tail + 1) return hash;
|
|
434
|
+
return `${hash.slice(0, head)}\u2026${hash.slice(-tail)}`;
|
|
435
|
+
}
|
|
436
|
+
function sortExecutionsNewestFirst(receipts) {
|
|
437
|
+
return [...receipts].sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
|
|
438
|
+
}
|
|
439
|
+
function isVaultSettlement(receipt) {
|
|
440
|
+
return receipt.settlement === "vault";
|
|
441
|
+
}
|
|
442
|
+
function isChainVerifiedReceipt(receipt) {
|
|
443
|
+
return receipt.verifiedOnChain === true;
|
|
444
|
+
}
|
|
445
|
+
function formatReceiptSummary(receipt) {
|
|
446
|
+
const settlementLabel = receipt.verifiedOnChain ? "vault settlement (chain-verified)" : receipt.settlement === "vault" ? "vault settlement (unverified on-chain)" : "staged settlement (capital book)";
|
|
447
|
+
const parts = [receipt.action, settlementLabel, receipt.status];
|
|
448
|
+
if (receipt.explorerUrl) parts.push(receipt.explorerUrl);
|
|
449
|
+
if (receipt.registryRef) {
|
|
450
|
+
parts.push(
|
|
451
|
+
`registry ${shortTransactionHash(receipt.registryRef.objectiveKey, 8, 4)}`
|
|
452
|
+
);
|
|
453
|
+
}
|
|
454
|
+
return parts.join(" \xB7 ");
|
|
455
|
+
}
|
|
456
|
+
function findTimelineEventsForReceipt(events, receipt) {
|
|
457
|
+
return events.filter((event) => {
|
|
458
|
+
if (event.type !== "execution_started" && event.type !== "execution_completed") {
|
|
459
|
+
return false;
|
|
460
|
+
}
|
|
461
|
+
const executionId = event.payload?.executionId;
|
|
462
|
+
return typeof executionId === "string" && executionId === receipt.id;
|
|
463
|
+
});
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
// src/validation/receipt-validator.ts
|
|
467
|
+
var EXECUTION_STATUSES = /* @__PURE__ */ new Set([
|
|
468
|
+
"pending",
|
|
469
|
+
"submitted",
|
|
470
|
+
"confirmed",
|
|
471
|
+
"failed"
|
|
472
|
+
]);
|
|
473
|
+
var SETTLEMENTS = /* @__PURE__ */ new Set(["vault", "staged"]);
|
|
474
|
+
var TX_HASH_0X = /^0x[a-fA-F0-9]{64}$/;
|
|
475
|
+
var ADDRESS_0X = /^0x[a-fA-F0-9]{40}$/;
|
|
476
|
+
var BYTES32_0X = /^0x[a-fA-F0-9]{64}$/;
|
|
477
|
+
var ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T/;
|
|
478
|
+
function issue(code, message, path) {
|
|
479
|
+
return path ? { code, message, path } : { code, message };
|
|
480
|
+
}
|
|
162
481
|
function isRecord(value) {
|
|
163
482
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
164
483
|
}
|
|
484
|
+
function isNonEmptyString(value) {
|
|
485
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
486
|
+
}
|
|
487
|
+
function isVaultTransactionHash(hash) {
|
|
488
|
+
return hash.startsWith("pending_vault_") || TX_HASH_0X.test(hash);
|
|
489
|
+
}
|
|
490
|
+
function isRealVaultTxHash(hash) {
|
|
491
|
+
return TX_HASH_0X.test(hash);
|
|
492
|
+
}
|
|
493
|
+
function isStagedTransactionHash(hash) {
|
|
494
|
+
return !isVaultTransactionHash(hash);
|
|
495
|
+
}
|
|
496
|
+
function validateSettlementRecord(input, options = {}) {
|
|
497
|
+
const issues = [];
|
|
498
|
+
if (!isRecord(input)) {
|
|
499
|
+
return {
|
|
500
|
+
valid: false,
|
|
501
|
+
issues: [issue("INVALID_INPUT", "Settlement record must be an object")]
|
|
502
|
+
};
|
|
503
|
+
}
|
|
504
|
+
const requiredStrings = [
|
|
505
|
+
["id", "id"],
|
|
506
|
+
["walletAddress", "walletAddress"],
|
|
507
|
+
["transactionHash", "transactionHash"],
|
|
508
|
+
["vaultAddress", "vaultAddress"],
|
|
509
|
+
["tokenSell", "tokenSell"],
|
|
510
|
+
["tokenBuy", "tokenBuy"],
|
|
511
|
+
["amountIn", "amountIn"],
|
|
512
|
+
["amountOut", "amountOut"],
|
|
513
|
+
["explorerUrl", "explorerUrl"],
|
|
514
|
+
["verifiedAt", "verifiedAt"],
|
|
515
|
+
["status", "status"]
|
|
516
|
+
];
|
|
517
|
+
for (const [key, path] of requiredStrings) {
|
|
518
|
+
if (!isNonEmptyString(input[key])) {
|
|
519
|
+
issues.push(issue("MISSING_FIELD", `Missing ${key}`, path));
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
if (input.settlement !== "vault") {
|
|
523
|
+
issues.push(
|
|
524
|
+
issue(
|
|
525
|
+
"INVALID_SETTLEMENT_RECORD",
|
|
526
|
+
'settlement must be "vault" on chain records',
|
|
527
|
+
"settlement"
|
|
528
|
+
)
|
|
529
|
+
);
|
|
530
|
+
}
|
|
531
|
+
if (typeof input.blockNumber !== "number" || !Number.isFinite(input.blockNumber) || input.blockNumber < 0) {
|
|
532
|
+
issues.push(
|
|
533
|
+
issue("INVALID_SETTLEMENT_RECORD", "blockNumber must be a non-negative number", "blockNumber")
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
if (typeof input.logIndex !== "number" || !Number.isInteger(input.logIndex) || input.logIndex < 0) {
|
|
537
|
+
issues.push(
|
|
538
|
+
issue("INVALID_SETTLEMENT_RECORD", "logIndex must be a non-negative integer", "logIndex")
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
const tx = String(input.transactionHash ?? "");
|
|
542
|
+
if (tx && !TX_HASH_0X.test(tx)) {
|
|
543
|
+
issues.push(
|
|
544
|
+
issue("INVALID_SETTLEMENT_RECORD", "transactionHash must be a 32-byte hex hash", "transactionHash")
|
|
545
|
+
);
|
|
546
|
+
}
|
|
547
|
+
if (input.status !== "confirmed" && input.status !== "orphan") {
|
|
548
|
+
issues.push(
|
|
549
|
+
issue(
|
|
550
|
+
"INVALID_SETTLEMENT_RECORD",
|
|
551
|
+
'status must be "confirmed" or "orphan"',
|
|
552
|
+
"status"
|
|
553
|
+
)
|
|
554
|
+
);
|
|
555
|
+
}
|
|
556
|
+
if (options.executionId && input.executionId != null && input.executionId !== options.executionId) {
|
|
557
|
+
issues.push(
|
|
558
|
+
issue(
|
|
559
|
+
"VERIFIED_RECORD_MISMATCH",
|
|
560
|
+
"settlementRecord.executionId must match receipt.id",
|
|
561
|
+
"executionId"
|
|
562
|
+
)
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
if (isRecord(input.registryRef)) {
|
|
566
|
+
const refIssues = validateRegistryRef(input.registryRef, "registryRef");
|
|
567
|
+
issues.push(...refIssues);
|
|
568
|
+
}
|
|
569
|
+
return { valid: issues.length === 0, issues };
|
|
570
|
+
}
|
|
571
|
+
function validateRegistryRef(ref, basePath) {
|
|
572
|
+
const issues = [];
|
|
573
|
+
const objectiveKey = ref.objectiveKey;
|
|
574
|
+
const contractAddress = ref.contractAddress;
|
|
575
|
+
if (!isNonEmptyString(objectiveKey) || !BYTES32_0X.test(objectiveKey)) {
|
|
576
|
+
issues.push(
|
|
577
|
+
issue(
|
|
578
|
+
"INVALID_REGISTRY_REF",
|
|
579
|
+
"objectiveKey must be a 32-byte hex string",
|
|
580
|
+
`${basePath}.objectiveKey`
|
|
581
|
+
)
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
if (!isNonEmptyString(contractAddress) || !ADDRESS_0X.test(contractAddress)) {
|
|
585
|
+
issues.push(
|
|
586
|
+
issue(
|
|
587
|
+
"INVALID_REGISTRY_REF",
|
|
588
|
+
"contractAddress must be a 20-byte hex address",
|
|
589
|
+
`${basePath}.contractAddress`
|
|
590
|
+
)
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
return issues;
|
|
594
|
+
}
|
|
595
|
+
function validateExecutionReceipt(input) {
|
|
596
|
+
const issues = [];
|
|
597
|
+
if (!isRecord(input)) {
|
|
598
|
+
return {
|
|
599
|
+
valid: false,
|
|
600
|
+
issues: [issue("INVALID_INPUT", "Receipt must be an object")]
|
|
601
|
+
};
|
|
602
|
+
}
|
|
603
|
+
const required = [
|
|
604
|
+
["id", "id"],
|
|
605
|
+
["objectiveId", "objectiveId"],
|
|
606
|
+
["action", "action"],
|
|
607
|
+
["status", "status"],
|
|
608
|
+
["transactionHash", "transactionHash"],
|
|
609
|
+
["result", "result"],
|
|
610
|
+
["createdAt", "createdAt"],
|
|
611
|
+
["settlement", "settlement"]
|
|
612
|
+
];
|
|
613
|
+
for (const [key, path] of required) {
|
|
614
|
+
if (!isNonEmptyString(input[key])) {
|
|
615
|
+
issues.push(issue("MISSING_FIELD", `Missing ${key}`, path));
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
const settlement = input.settlement;
|
|
619
|
+
if (settlement != null && !SETTLEMENTS.has(String(settlement))) {
|
|
620
|
+
issues.push(
|
|
621
|
+
issue(
|
|
622
|
+
"INVALID_SETTLEMENT",
|
|
623
|
+
'settlement must be "vault" or "staged"',
|
|
624
|
+
"settlement"
|
|
625
|
+
)
|
|
626
|
+
);
|
|
627
|
+
}
|
|
628
|
+
const status = input.status;
|
|
629
|
+
if (status != null && !EXECUTION_STATUSES.has(String(status))) {
|
|
630
|
+
issues.push(
|
|
631
|
+
issue(
|
|
632
|
+
"INVALID_STATUS",
|
|
633
|
+
"status must be pending, submitted, confirmed, or failed",
|
|
634
|
+
"status"
|
|
635
|
+
)
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
if (status === "confirmed" && !isNonEmptyString(input.confirmedAt)) {
|
|
639
|
+
issues.push(
|
|
640
|
+
issue("MISSING_CONFIRMED_AT", "confirmed status requires confirmedAt", "confirmedAt")
|
|
641
|
+
);
|
|
642
|
+
}
|
|
643
|
+
if (isNonEmptyString(input.createdAt) && !ISO_TIMESTAMP.test(input.createdAt)) {
|
|
644
|
+
issues.push(
|
|
645
|
+
issue("MISSING_FIELD", "createdAt must be an ISO-8601 timestamp", "createdAt")
|
|
646
|
+
);
|
|
647
|
+
}
|
|
648
|
+
const txHash = String(input.transactionHash ?? "");
|
|
649
|
+
const settlementStr = String(settlement ?? "");
|
|
650
|
+
if (settlementStr === "staged") {
|
|
651
|
+
if (input.explorerUrl != null && input.explorerUrl !== "") {
|
|
652
|
+
issues.push(
|
|
653
|
+
issue(
|
|
654
|
+
"STAGED_WITH_EXPLORER",
|
|
655
|
+
"staged receipts must not include explorerUrl",
|
|
656
|
+
"explorerUrl"
|
|
657
|
+
)
|
|
658
|
+
);
|
|
659
|
+
}
|
|
660
|
+
if (input.verifiedOnChain === true) {
|
|
661
|
+
issues.push(
|
|
662
|
+
issue(
|
|
663
|
+
"STAGED_VERIFIED_ON_CHAIN",
|
|
664
|
+
"staged receipts cannot be verifiedOnChain",
|
|
665
|
+
"verifiedOnChain"
|
|
666
|
+
)
|
|
667
|
+
);
|
|
668
|
+
}
|
|
669
|
+
if (input.settlementRecord != null) {
|
|
670
|
+
issues.push(
|
|
671
|
+
issue(
|
|
672
|
+
"STAGED_WITH_SETTLEMENT_RECORD",
|
|
673
|
+
"staged receipts must not include settlementRecord",
|
|
674
|
+
"settlementRecord"
|
|
675
|
+
)
|
|
676
|
+
);
|
|
677
|
+
}
|
|
678
|
+
if (txHash && !isStagedTransactionHash(txHash)) {
|
|
679
|
+
issues.push(
|
|
680
|
+
issue(
|
|
681
|
+
"INVALID_VAULT_HASH",
|
|
682
|
+
"staged settlement must not use a vault transaction hash",
|
|
683
|
+
"transactionHash"
|
|
684
|
+
)
|
|
685
|
+
);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
if (settlementStr === "vault") {
|
|
689
|
+
if (txHash && !isVaultTransactionHash(txHash)) {
|
|
690
|
+
issues.push(
|
|
691
|
+
issue(
|
|
692
|
+
"INVALID_VAULT_HASH",
|
|
693
|
+
"vault settlement requires 0x\u2026 hash or pending_vault_* prefix",
|
|
694
|
+
"transactionHash"
|
|
695
|
+
)
|
|
696
|
+
);
|
|
697
|
+
}
|
|
698
|
+
if (isRealVaultTxHash(txHash)) {
|
|
699
|
+
if (input.explorerUrl == null || input.explorerUrl === "") {
|
|
700
|
+
issues.push(
|
|
701
|
+
issue(
|
|
702
|
+
"VAULT_MISSING_EXPLORER",
|
|
703
|
+
"confirmed vault tx must include explorerUrl",
|
|
704
|
+
"explorerUrl"
|
|
705
|
+
)
|
|
706
|
+
);
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
if (input.verifiedOnChain === true) {
|
|
711
|
+
if (settlementStr !== "vault") {
|
|
712
|
+
issues.push(
|
|
713
|
+
issue(
|
|
714
|
+
"VERIFIED_WITHOUT_RECORD",
|
|
715
|
+
"verifiedOnChain requires vault settlement",
|
|
716
|
+
"verifiedOnChain"
|
|
717
|
+
)
|
|
718
|
+
);
|
|
719
|
+
}
|
|
720
|
+
if (input.settlementRecord == null) {
|
|
721
|
+
issues.push(
|
|
722
|
+
issue(
|
|
723
|
+
"VERIFIED_WITHOUT_RECORD",
|
|
724
|
+
"verifiedOnChain requires settlementRecord",
|
|
725
|
+
"verifiedOnChain"
|
|
726
|
+
)
|
|
727
|
+
);
|
|
728
|
+
} else {
|
|
729
|
+
const nested = validateSettlementRecord(input.settlementRecord, {
|
|
730
|
+
executionId: isNonEmptyString(input.id) ? input.id : void 0
|
|
731
|
+
});
|
|
732
|
+
for (const nestedIssue of nested.issues) {
|
|
733
|
+
issues.push({
|
|
734
|
+
...nestedIssue,
|
|
735
|
+
path: nestedIssue.path ? `settlementRecord.${nestedIssue.path}` : "settlementRecord"
|
|
736
|
+
});
|
|
737
|
+
}
|
|
738
|
+
}
|
|
739
|
+
} else if (input.settlementRecord != null && input.verifiedOnChain !== false) {
|
|
740
|
+
if (input.verifiedOnChain !== true) {
|
|
741
|
+
issues.push(
|
|
742
|
+
issue(
|
|
743
|
+
"VERIFIED_RECORD_MISMATCH",
|
|
744
|
+
"settlementRecord present but verifiedOnChain is not true",
|
|
745
|
+
"verifiedOnChain"
|
|
746
|
+
)
|
|
747
|
+
);
|
|
748
|
+
}
|
|
749
|
+
}
|
|
750
|
+
if (isRecord(input.registryRef)) {
|
|
751
|
+
issues.push(...validateRegistryRef(input.registryRef, "registryRef"));
|
|
752
|
+
}
|
|
753
|
+
return { valid: issues.length === 0, issues };
|
|
754
|
+
}
|
|
755
|
+
function isValidExecutionReceipt(input) {
|
|
756
|
+
return validateExecutionReceipt(input).valid;
|
|
757
|
+
}
|
|
758
|
+
function assertValidExecutionReceipt(receipt) {
|
|
759
|
+
const result = validateExecutionReceipt(receipt);
|
|
760
|
+
if (!result.valid) {
|
|
761
|
+
throw new AureonValidationError("Invalid execution receipt", {
|
|
762
|
+
issues: result.issues
|
|
763
|
+
});
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
|
|
767
|
+
// src/formatting/receipt-verification.ts
|
|
768
|
+
function inferProofTier(receipt, validation, settlement) {
|
|
769
|
+
if (!validation.valid) return "claim_only";
|
|
770
|
+
if (settlement?.verifiedOnChain === true || isChainVerifiedReceipt(receipt) || receipt.verifiedOnChain === true) {
|
|
771
|
+
return "chain_verified";
|
|
772
|
+
}
|
|
773
|
+
return "schema_valid";
|
|
774
|
+
}
|
|
775
|
+
function inferCurrentPhase(validation, proofTier) {
|
|
776
|
+
if (!validation.valid) return "validation_failed";
|
|
777
|
+
if (proofTier === "chain_verified") return "chain_verified";
|
|
778
|
+
return "validated";
|
|
779
|
+
}
|
|
780
|
+
function buildReceiptVerificationFlow(input) {
|
|
781
|
+
const validation = input.validation ?? validateExecutionReceipt(input.receipt);
|
|
782
|
+
const proofTier = inferProofTier(
|
|
783
|
+
input.receipt,
|
|
784
|
+
validation,
|
|
785
|
+
input.settlement
|
|
786
|
+
);
|
|
787
|
+
const currentPhase = inferCurrentPhase(validation, proofTier);
|
|
788
|
+
let message = "An AI saying 'transaction successful' is only a claim \u2014 validate the receipt before trusting it.";
|
|
789
|
+
if (currentPhase === "validation_failed") {
|
|
790
|
+
message = "Receipt failed validation \u2014 do not summarize as proof. Fix honesty issues before reporting success.";
|
|
791
|
+
} else if (currentPhase === "chain_verified") {
|
|
792
|
+
message = "Receipt passes validation and has independent on-chain settlement proof.";
|
|
793
|
+
} else if (proofTier === "schema_valid") {
|
|
794
|
+
const label = input.receipt.settlement === "staged" ? "staged (capital book)" : "vault (not yet chain-observed)";
|
|
795
|
+
message = `Receipt passes validation (${label}). Schema-valid does not mean chain-verified \u2014 check settlement lookup for vault proof.`;
|
|
796
|
+
}
|
|
797
|
+
return {
|
|
798
|
+
executionId: input.receipt.id,
|
|
799
|
+
receipt: input.receipt,
|
|
800
|
+
phases: {
|
|
801
|
+
claimed: {
|
|
802
|
+
summary: formatReceiptSummary(input.receipt),
|
|
803
|
+
status: input.receipt.status,
|
|
804
|
+
settlement: input.receipt.settlement,
|
|
805
|
+
result: input.receipt.result
|
|
806
|
+
},
|
|
807
|
+
validation,
|
|
808
|
+
settlement: input.settlement,
|
|
809
|
+
timelineEvents: input.timelineEvents
|
|
810
|
+
},
|
|
811
|
+
proofTier,
|
|
812
|
+
currentPhase,
|
|
813
|
+
message
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
// src/formatting/portfolio-watch.ts
|
|
818
|
+
var DEFAULT_PORTFOLIO_WATCH_BRIEF = "Watch my portfolio while I'm away \u2014 keep about 20% in stable assets.";
|
|
819
|
+
function isOffPlan3(state) {
|
|
820
|
+
return state === "warning" || state === "violation";
|
|
821
|
+
}
|
|
822
|
+
function inferPortfolioWatchPhase(input) {
|
|
823
|
+
if (input.whileAway) return "return_briefing";
|
|
824
|
+
if (isOffPlan3(input.registerHealth.state)) return "while_away";
|
|
825
|
+
return "watch_registered";
|
|
826
|
+
}
|
|
827
|
+
function buildPortfolioWatchBriefingLines(input) {
|
|
828
|
+
const hostLabel = input.host === "cursor" ? "Cursor" : input.host === "claude" ? "Claude" : "MCP agent";
|
|
829
|
+
const lines = [
|
|
830
|
+
`You asked ${hostLabel} to watch your portfolio while away.`,
|
|
831
|
+
`Policy registered: ${input.objective.name} (${input.objective.automationMode ?? "auto"} mode).`
|
|
832
|
+
];
|
|
833
|
+
if (input.whileAway) {
|
|
834
|
+
lines.push(
|
|
835
|
+
`While away: ${input.whileAway.symbol} moved ${(input.whileAway.priceChangeRatio * 100).toFixed(0)}% \u2014 health went from ${input.whileAway.healthBefore.state} to ${input.whileAway.healthAfter.state}.`
|
|
836
|
+
);
|
|
837
|
+
if (input.whileAway.autoRestored && input.whileAway.receipt) {
|
|
838
|
+
lines.push(
|
|
839
|
+
`Automatic restore ran \u2014 settlement ${input.whileAway.receipt.settlement}. Receipt id ${input.whileAway.receipt.id}.`
|
|
840
|
+
);
|
|
841
|
+
} else if (input.whileAway.autoRestored) {
|
|
842
|
+
lines.push("Automatic restore ran \u2014 check executions for receipt.");
|
|
843
|
+
} else {
|
|
844
|
+
lines.push("No automatic restore fired \u2014 review health and restore plan.");
|
|
845
|
+
}
|
|
846
|
+
} else if (isOffPlan3(input.briefingHealth.state)) {
|
|
847
|
+
lines.push(
|
|
848
|
+
`Portfolio is off-plan (${input.briefingHealth.state}) \u2014 agent should surface restore options.`
|
|
849
|
+
);
|
|
850
|
+
} else {
|
|
851
|
+
lines.push("Portfolio remains within policy \u2014 no action required.");
|
|
852
|
+
}
|
|
853
|
+
if (input.timelineEvents.length > 0) {
|
|
854
|
+
const types = [...new Set(input.timelineEvents.map((e) => e.type))].slice(
|
|
855
|
+
0,
|
|
856
|
+
4
|
|
857
|
+
);
|
|
858
|
+
lines.push(`Timeline: ${input.timelineEvents.length} recent event(s) \u2014 ${types.join(", ")}.`);
|
|
859
|
+
}
|
|
860
|
+
lines.push(`Current health: ${input.briefingHealth.state}.`);
|
|
861
|
+
return lines;
|
|
862
|
+
}
|
|
863
|
+
function buildPortfolioWatchFlow(input) {
|
|
864
|
+
const targetWeight = input.objective.policy?.targetWeight ?? input.registerHealth.targetMetric ?? 0;
|
|
865
|
+
const tolerance = input.objective.policy?.tolerance ?? 0.02;
|
|
866
|
+
const policySummary = input.objective.policy?.summary ?? buildPolicySummary(input.objective.kind, targetWeight, tolerance);
|
|
867
|
+
const whileAway = input.whileAway ? {
|
|
868
|
+
marketEventName: input.whileAway.marketEvent.name,
|
|
869
|
+
symbol: input.whileAway.marketEvent.symbol,
|
|
870
|
+
priceChangeRatio: input.whileAway.marketEvent.priceChangeRatio,
|
|
871
|
+
healthBefore: input.whileAway.healthBefore,
|
|
872
|
+
healthAfter: input.whileAway.healthAfter,
|
|
873
|
+
autoRestored: input.whileAway.autoRestored,
|
|
874
|
+
receipt: input.whileAway.receipt
|
|
875
|
+
} : void 0;
|
|
876
|
+
const summaryLines = buildPortfolioWatchBriefingLines({
|
|
877
|
+
userBrief: input.userBrief,
|
|
878
|
+
host: input.host,
|
|
879
|
+
objective: input.objective,
|
|
880
|
+
registerHealth: input.registerHealth,
|
|
881
|
+
briefingHealth: input.briefingHealth,
|
|
882
|
+
whileAway,
|
|
883
|
+
timelineEvents: input.timelineEvents
|
|
884
|
+
});
|
|
885
|
+
const currentPhase = inferPortfolioWatchPhase({
|
|
886
|
+
registerHealth: input.registerHealth,
|
|
887
|
+
whileAway
|
|
888
|
+
});
|
|
889
|
+
let message = "Tell your agent to watch the portfolio \u2014 intent becomes an Automatic objective, then read health and timeline when you return.";
|
|
890
|
+
if (currentPhase === "return_briefing" && whileAway?.autoRestored) {
|
|
891
|
+
message = "While you were away, the market moved and Automatic mode restored policy. Review the briefing \u2014 not a blank check, a registered rule.";
|
|
892
|
+
} else if (currentPhase === "while_away" || isOffPlan3(input.briefingHealth.state)) {
|
|
893
|
+
message = "Portfolio drifted off-plan. The agent should report health and restore options \u2014 not discretionary trades.";
|
|
894
|
+
}
|
|
895
|
+
return {
|
|
896
|
+
objectiveId: input.objective.id,
|
|
897
|
+
userBrief: input.userBrief,
|
|
898
|
+
host: input.host,
|
|
899
|
+
phases: {
|
|
900
|
+
register: {
|
|
901
|
+
objectiveName: input.objective.name,
|
|
902
|
+
automationMode: input.objective.automationMode ?? "auto",
|
|
903
|
+
policySummary,
|
|
904
|
+
health: input.registerHealth,
|
|
905
|
+
allocationRow: input.registerRow
|
|
906
|
+
},
|
|
907
|
+
whileAway,
|
|
908
|
+
briefing: {
|
|
909
|
+
health: input.briefingHealth,
|
|
910
|
+
timelineEventCount: input.timelineEvents.length,
|
|
911
|
+
timelineEvents: input.timelineEvents,
|
|
912
|
+
summaryLines
|
|
913
|
+
}
|
|
914
|
+
},
|
|
915
|
+
currentPhase,
|
|
916
|
+
message
|
|
917
|
+
};
|
|
918
|
+
}
|
|
919
|
+
function buildPortfolioWatchFlowFromSnapshot(input) {
|
|
920
|
+
return buildPortfolioWatchFlow({
|
|
921
|
+
userBrief: input.userBrief,
|
|
922
|
+
host: input.host,
|
|
923
|
+
objective: input.objective,
|
|
924
|
+
registerHealth: input.health,
|
|
925
|
+
registerRow: input.allocationRow,
|
|
926
|
+
briefingHealth: input.health,
|
|
927
|
+
timelineEvents: input.timelineEvents
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
// src/formatting/full-aureon-loop.ts
|
|
932
|
+
var DEFAULT_FULL_LOOP_BRIEF = "Keep about 20% in stable assets \u2014 grow the book without abandoning the plan.";
|
|
933
|
+
function inferFullAureonLoopPhase(input) {
|
|
934
|
+
if (input.hasRestore && input.verificationValid) return "verified";
|
|
935
|
+
if (input.hasRestore) return "restored";
|
|
936
|
+
return "plan_check";
|
|
937
|
+
}
|
|
938
|
+
function buildFullAureonLoopFlow(input) {
|
|
939
|
+
const targetWeight = input.objective.policy?.targetWeight ?? input.baselineHealth.targetMetric ?? 0;
|
|
940
|
+
const tolerance = input.objective.policy?.tolerance ?? 0.02;
|
|
941
|
+
const policySummary = input.objective.policy?.summary ?? buildPolicySummary(input.objective.kind, targetWeight, tolerance);
|
|
942
|
+
const verification = input.verification ?? buildReceiptVerificationFlow({ receipt: input.receipt });
|
|
943
|
+
const currentPhase = inferFullAureonLoopPhase({
|
|
944
|
+
hasRestore: true,
|
|
945
|
+
verificationValid: verification.phases.validation.valid
|
|
946
|
+
});
|
|
947
|
+
let message = "We're not building another portfolio tracker. AUREON registers intent, checks the plan, restores when off-plan, and verifies the receipt.";
|
|
948
|
+
if (!verification.phases.validation.valid) {
|
|
949
|
+
message = "Loop completed restore, but the receipt failed validation \u2014 do not treat success text as proof.";
|
|
950
|
+
} else if (verification.proofTier === "chain_verified") {
|
|
951
|
+
message = "Full loop complete: intent \u2192 plan check \u2192 restore \u2192 chain-verified receipt. Not a tracker \u2014 a Financial Compass.";
|
|
952
|
+
} else {
|
|
953
|
+
message = "Full loop complete: intent \u2192 plan check \u2192 restore \u2192 schema-valid receipt. We're not building another portfolio tracker.";
|
|
954
|
+
}
|
|
955
|
+
return {
|
|
956
|
+
objectiveId: input.objective.id,
|
|
957
|
+
userBrief: input.userBrief,
|
|
958
|
+
phases: {
|
|
959
|
+
intent: {
|
|
960
|
+
objectiveName: input.objective.name,
|
|
961
|
+
policySummary,
|
|
962
|
+
automationMode: input.objective.automationMode ?? "auto",
|
|
963
|
+
health: input.baselineHealth
|
|
964
|
+
},
|
|
965
|
+
planCheck: {
|
|
966
|
+
baselineAligned: input.baselineHealth.state === "healthy",
|
|
967
|
+
afterShock: {
|
|
968
|
+
health: input.afterShockHealth,
|
|
969
|
+
allocationRow: input.afterShockRow,
|
|
970
|
+
paradox: input.paradox
|
|
971
|
+
}
|
|
972
|
+
},
|
|
973
|
+
driftRestore: {
|
|
974
|
+
healthBefore: input.afterShockHealth,
|
|
975
|
+
healthAfter: input.restoredHealth,
|
|
976
|
+
receipt: input.receipt,
|
|
977
|
+
settlement: input.receipt.settlement
|
|
978
|
+
},
|
|
979
|
+
verification
|
|
980
|
+
},
|
|
981
|
+
currentPhase,
|
|
982
|
+
message
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
function buildFullAureonLoopFlowFromSnapshot(input) {
|
|
986
|
+
if (!input.latestReceipt) return null;
|
|
987
|
+
const verification = input.verification ?? buildReceiptVerificationFlow({ receipt: input.latestReceipt });
|
|
988
|
+
return buildFullAureonLoopFlow({
|
|
989
|
+
userBrief: input.userBrief,
|
|
990
|
+
objective: input.objective,
|
|
991
|
+
baselineHealth: input.health,
|
|
992
|
+
afterShockHealth: input.health,
|
|
993
|
+
afterShockRow: input.allocationRow,
|
|
994
|
+
paradox: input.paradox,
|
|
995
|
+
restoredHealth: input.health,
|
|
996
|
+
receipt: input.latestReceipt,
|
|
997
|
+
verification
|
|
998
|
+
});
|
|
999
|
+
}
|
|
1000
|
+
|
|
1001
|
+
// src/formatting/audit-trail.ts
|
|
1002
|
+
function buildFinancialAuditTrail(input) {
|
|
1003
|
+
const targetWeight = input.objective.policy?.targetWeight ?? 0;
|
|
1004
|
+
const tolerance = input.objective.policy?.tolerance ?? 0.02;
|
|
1005
|
+
const policySummary = input.objective.policy?.summary ?? buildPolicySummary(input.objective.kind, targetWeight, tolerance);
|
|
1006
|
+
const registered = input.registry?.registered === true ? input.registry.record : void 0;
|
|
1007
|
+
const receipts = input.receipts.map((receipt) => {
|
|
1008
|
+
const validation = validateExecutionReceipt(receipt);
|
|
1009
|
+
return {
|
|
1010
|
+
id: receipt.id,
|
|
1011
|
+
action: receipt.action,
|
|
1012
|
+
settlement: receipt.settlement,
|
|
1013
|
+
status: receipt.status,
|
|
1014
|
+
valid: validation.valid,
|
|
1015
|
+
verifiedOnChain: receipt.verifiedOnChain === true,
|
|
1016
|
+
explorerUrl: receipt.explorerUrl ?? null,
|
|
1017
|
+
summary: formatReceiptSummary(receipt),
|
|
1018
|
+
validation
|
|
1019
|
+
};
|
|
1020
|
+
});
|
|
1021
|
+
const timeline = input.timeline.map((event) => ({
|
|
1022
|
+
id: event.id,
|
|
1023
|
+
type: event.type,
|
|
1024
|
+
message: event.message,
|
|
1025
|
+
createdAt: event.createdAt,
|
|
1026
|
+
executionId: typeof event.payload?.executionId === "string" ? event.payload.executionId : null
|
|
1027
|
+
}));
|
|
1028
|
+
const gaps = [];
|
|
1029
|
+
if (input.registryLookupFailed || input.settlementsLookupFailed) {
|
|
1030
|
+
const parts = [
|
|
1031
|
+
input.registryLookupFailed ? "Registry lookup failed." : null,
|
|
1032
|
+
input.settlementsLookupFailed ? "Settlement lookup failed." : null
|
|
1033
|
+
].filter((part) => Boolean(part));
|
|
1034
|
+
gaps.push({
|
|
1035
|
+
code: "lookup_failed",
|
|
1036
|
+
message: `${parts.join(" ")} Do not treat this as a confirmed gap.`
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
if (!registered && !input.registryLookupFailed) {
|
|
1040
|
+
gaps.push({
|
|
1041
|
+
code: "not_registered",
|
|
1042
|
+
message: "Objective is not registered on ObjectiveRegistry."
|
|
1043
|
+
});
|
|
1044
|
+
}
|
|
1045
|
+
if (receipts.length === 0) {
|
|
1046
|
+
gaps.push({
|
|
1047
|
+
code: "no_executions",
|
|
1048
|
+
message: "No execution receipts for this objective."
|
|
1049
|
+
});
|
|
1050
|
+
} else {
|
|
1051
|
+
if (receipts.every((row) => row.settlement === "staged")) {
|
|
1052
|
+
gaps.push({
|
|
1053
|
+
code: "staged_only",
|
|
1054
|
+
message: "Every receipt is staged. None are on-chain."
|
|
1055
|
+
});
|
|
1056
|
+
}
|
|
1057
|
+
if (receipts.some((row) => row.settlement === "vault" && !row.verifiedOnChain)) {
|
|
1058
|
+
gaps.push({
|
|
1059
|
+
code: "vault_unverified",
|
|
1060
|
+
message: "At least one vault receipt has no independent settlement record yet."
|
|
1061
|
+
});
|
|
1062
|
+
}
|
|
1063
|
+
if (receipts.some((row) => !row.valid)) {
|
|
1064
|
+
gaps.push({
|
|
1065
|
+
code: "invalid_receipt",
|
|
1066
|
+
message: "At least one receipt failed local validation. Do not trust it."
|
|
1067
|
+
});
|
|
1068
|
+
}
|
|
1069
|
+
}
|
|
1070
|
+
const everyReceiptStaged = receipts.length > 0 && receipts.every((row) => row.settlement === "staged");
|
|
1071
|
+
if (input.settlements.length === 0 && !input.settlementsLookupFailed && !everyReceiptStaged) {
|
|
1072
|
+
gaps.push({
|
|
1073
|
+
code: "no_settlements",
|
|
1074
|
+
message: "No chain settlement records linked to this objective."
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
if (timeline.length === 0) {
|
|
1078
|
+
gaps.push({
|
|
1079
|
+
code: "no_timeline",
|
|
1080
|
+
message: "No timeline events for this objective."
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
return {
|
|
1084
|
+
objectiveId: input.objective.id,
|
|
1085
|
+
objectiveName: input.objective.name,
|
|
1086
|
+
policySummary,
|
|
1087
|
+
healthState: input.health?.state ?? null,
|
|
1088
|
+
generatedAt: input.generatedAt ?? (/* @__PURE__ */ new Date()).toISOString(),
|
|
1089
|
+
registry: registered ? { present: true, record: registered } : { present: false },
|
|
1090
|
+
receipts,
|
|
1091
|
+
settlements: input.settlements,
|
|
1092
|
+
timeline,
|
|
1093
|
+
gaps,
|
|
1094
|
+
message: auditTrailMessage({
|
|
1095
|
+
registered: Boolean(registered),
|
|
1096
|
+
receiptCount: receipts.length,
|
|
1097
|
+
settlementCount: input.settlements.length,
|
|
1098
|
+
invalid: receipts.some((row) => !row.valid),
|
|
1099
|
+
chainVerified: receipts.some((row) => row.verifiedOnChain)
|
|
1100
|
+
})
|
|
1101
|
+
};
|
|
1102
|
+
}
|
|
1103
|
+
function formatAuditTrailLines(trail) {
|
|
1104
|
+
const lines = [
|
|
1105
|
+
`Objective ${trail.objectiveName} (${trail.objectiveId})`,
|
|
1106
|
+
`Policy ${trail.policySummary}`,
|
|
1107
|
+
`Health ${trail.healthState ?? "unknown"}`,
|
|
1108
|
+
`Registry ${trail.registry.present ? "registered on-chain" : "not registered"}`,
|
|
1109
|
+
`Receipts ${trail.receipts.length}`,
|
|
1110
|
+
`Settlements ${trail.settlements.length}`,
|
|
1111
|
+
`Timeline ${trail.timeline.length}`
|
|
1112
|
+
];
|
|
1113
|
+
if (trail.gaps.length > 0) {
|
|
1114
|
+
lines.push("Gaps");
|
|
1115
|
+
for (const gap of trail.gaps) {
|
|
1116
|
+
lines.push(` - ${gap.message}`);
|
|
1117
|
+
}
|
|
1118
|
+
}
|
|
1119
|
+
lines.push(trail.message);
|
|
1120
|
+
return lines;
|
|
1121
|
+
}
|
|
1122
|
+
function auditTrailMessage(input) {
|
|
1123
|
+
if (input.invalid) {
|
|
1124
|
+
return "Audit trail assembled with dishonest or incomplete receipts. Do not treat success text as proof.";
|
|
1125
|
+
}
|
|
1126
|
+
if (input.receiptCount === 0) {
|
|
1127
|
+
return "Audit trail shows the objective only. No restore has been recorded yet.";
|
|
1128
|
+
}
|
|
1129
|
+
if (input.chainVerified && input.registered) {
|
|
1130
|
+
return "Audit trail complete on testnet: registered objective, receipts, and chain settlement. Not mainnet.";
|
|
1131
|
+
}
|
|
1132
|
+
if (input.chainVerified) {
|
|
1133
|
+
return "Receipts include chain settlement proof. Objective is not registered on-chain.";
|
|
1134
|
+
}
|
|
1135
|
+
if (input.settlementCount === 0) {
|
|
1136
|
+
return "Audit trail shows receipts without chain settlement records. Staged or unverified vault \u2014 not independent proof.";
|
|
1137
|
+
}
|
|
1138
|
+
return "Audit trail assembled from what exists. Gaps are labeled. Nothing missing was invented.";
|
|
1139
|
+
}
|
|
1140
|
+
|
|
1141
|
+
// src/formatting/intent.ts
|
|
1142
|
+
var DEFAULT_TOLERANCE = 0.02;
|
|
1143
|
+
function defaultName(intent) {
|
|
1144
|
+
if (intent.name?.trim()) return intent.name.trim();
|
|
1145
|
+
const pct = (intent.targetWeight * 100).toFixed(0);
|
|
1146
|
+
switch (intent.kind) {
|
|
1147
|
+
case "stable_allocation":
|
|
1148
|
+
return `Maintain ${pct}% Stable Assets`;
|
|
1149
|
+
case "balanced_portfolio":
|
|
1150
|
+
return `Maintain ${pct}% ${intent.targetSymbol ?? "Sleeve"}`;
|
|
1151
|
+
case "risk_ceiling":
|
|
1152
|
+
return `Risk ceiling policy`;
|
|
1153
|
+
case "reward_reinvestment":
|
|
1154
|
+
return `Reinvest rewards toward ${pct}%`;
|
|
1155
|
+
default:
|
|
1156
|
+
return intent.brief.slice(0, 64);
|
|
1157
|
+
}
|
|
1158
|
+
}
|
|
1159
|
+
function resolveObjectiveFromIntent(intent) {
|
|
1160
|
+
const brief = intent.brief?.trim();
|
|
1161
|
+
if (!brief || brief.length < 3) {
|
|
1162
|
+
throw new AureonValidationError("Intent brief must be at least 3 characters");
|
|
1163
|
+
}
|
|
1164
|
+
if (intent.targetWeight < 0 || intent.targetWeight > 1) {
|
|
1165
|
+
throw new AureonValidationError("targetWeight must be between 0 and 1");
|
|
1166
|
+
}
|
|
1167
|
+
const tolerance = intent.tolerance ?? DEFAULT_TOLERANCE;
|
|
1168
|
+
if (tolerance < 0 || tolerance > 0.5) {
|
|
1169
|
+
throw new AureonValidationError("tolerance must be between 0 and 0.5");
|
|
1170
|
+
}
|
|
1171
|
+
const base = {
|
|
1172
|
+
name: defaultName(intent),
|
|
1173
|
+
kind: intent.kind,
|
|
1174
|
+
targetWeight: intent.targetWeight,
|
|
1175
|
+
tolerance,
|
|
1176
|
+
priority: intent.priority ?? "high",
|
|
1177
|
+
automationMode: "auto"
|
|
1178
|
+
};
|
|
1179
|
+
if (intent.kind === "balanced_portfolio") {
|
|
1180
|
+
const symbol = intent.targetSymbol?.trim().toUpperCase();
|
|
1181
|
+
if (!symbol) {
|
|
1182
|
+
throw new AureonValidationError(
|
|
1183
|
+
"balanced_portfolio intent requires targetSymbol"
|
|
1184
|
+
);
|
|
1185
|
+
}
|
|
1186
|
+
return { ...base, targetSymbol: symbol };
|
|
1187
|
+
}
|
|
1188
|
+
return base;
|
|
1189
|
+
}
|
|
1190
|
+
function buildObjectivePortfolioFlow(intent, objective, health, portfolio) {
|
|
1191
|
+
const policySummary = objective.policy?.summary ?? buildPolicySummary(intent.kind, intent.targetWeight, intent.tolerance);
|
|
1192
|
+
const state = health?.state ?? "paused";
|
|
1193
|
+
const current = health?.currentMetric;
|
|
1194
|
+
const target = health?.targetMetric ?? intent.targetWeight;
|
|
1195
|
+
let message = "Intent registered as objective. Portfolio is now scored against that policy.";
|
|
1196
|
+
if (health && state === "healthy") {
|
|
1197
|
+
message = `Portfolio aligns with intent \u2014 ${policySummary}.`;
|
|
1198
|
+
} else if (health && (state === "warning" || state === "violation")) {
|
|
1199
|
+
message = `Objective is active but portfolio is off-plan (${state}). Current ${((current ?? 0) * 100).toFixed(1)}% vs target ${(target * 100).toFixed(1)}%.`;
|
|
1200
|
+
}
|
|
1201
|
+
return {
|
|
1202
|
+
intent: { brief: intent.brief.trim(), policySummary },
|
|
1203
|
+
objective,
|
|
1204
|
+
health,
|
|
1205
|
+
portfolio: {
|
|
1206
|
+
totalNotionalUsd: portfolio.totalNotionalUsd,
|
|
1207
|
+
stableWeight: portfolio.stableWeight,
|
|
1208
|
+
positions: portfolio.positions
|
|
1209
|
+
},
|
|
1210
|
+
message
|
|
1211
|
+
};
|
|
1212
|
+
}
|
|
1213
|
+
function parseFinancialIntent(brief) {
|
|
1214
|
+
const text = brief.trim();
|
|
1215
|
+
if (!text) {
|
|
1216
|
+
throw new AureonValidationError("Intent brief is required");
|
|
1217
|
+
}
|
|
1218
|
+
const stableMatch = text.match(
|
|
1219
|
+
/(\d+(?:\.\d+)?)\s*%?\s*(?:of\s+(?:my\s+)?portfolio\s+in\s+)?stable/i
|
|
1220
|
+
);
|
|
1221
|
+
if (stableMatch) {
|
|
1222
|
+
const pct = Number(stableMatch[1]) / 100;
|
|
1223
|
+
return {
|
|
1224
|
+
brief: text,
|
|
1225
|
+
kind: "stable_allocation",
|
|
1226
|
+
targetWeight: pct,
|
|
1227
|
+
tolerance: DEFAULT_TOLERANCE
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const holdMatch = text.match(
|
|
1231
|
+
/(?:hold|keep|maintain)\s+(?:about\s+)?(\d+(?:\.\d+)?)\s*%?\s*(?:in\s+)?([A-Z]{2,10})/i
|
|
1232
|
+
);
|
|
1233
|
+
if (holdMatch) {
|
|
1234
|
+
const pct = Number(holdMatch[1]) / 100;
|
|
1235
|
+
const symbol = holdMatch[2].toUpperCase();
|
|
1236
|
+
if (symbol === "STABLE" || symbol === "STABLES") {
|
|
1237
|
+
return {
|
|
1238
|
+
brief: text,
|
|
1239
|
+
kind: "stable_allocation",
|
|
1240
|
+
targetWeight: pct,
|
|
1241
|
+
tolerance: DEFAULT_TOLERANCE
|
|
1242
|
+
};
|
|
1243
|
+
}
|
|
1244
|
+
return {
|
|
1245
|
+
brief: text,
|
|
1246
|
+
kind: "balanced_portfolio",
|
|
1247
|
+
targetWeight: pct,
|
|
1248
|
+
tolerance: DEFAULT_TOLERANCE,
|
|
1249
|
+
targetSymbol: symbol
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
const pctOnly = text.match(/(\d+(?:\.\d+)?)\s*%/);
|
|
1253
|
+
if (pctOnly && /stable/i.test(text)) {
|
|
1254
|
+
return {
|
|
1255
|
+
brief: text,
|
|
1256
|
+
kind: "stable_allocation",
|
|
1257
|
+
targetWeight: Number(pctOnly[1]) / 100,
|
|
1258
|
+
tolerance: DEFAULT_TOLERANCE
|
|
1259
|
+
};
|
|
1260
|
+
}
|
|
1261
|
+
throw new AureonValidationError(
|
|
1262
|
+
"Could not parse intent from brief \u2014 supply structured FinancialIntent fields"
|
|
1263
|
+
);
|
|
1264
|
+
}
|
|
1265
|
+
|
|
1266
|
+
// src/errors/http.ts
|
|
1267
|
+
function isRecord2(value) {
|
|
1268
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
1269
|
+
}
|
|
165
1270
|
function extractMessage(body) {
|
|
166
|
-
if (!
|
|
1271
|
+
if (!isRecord2(body)) return null;
|
|
167
1272
|
if (typeof body.message === "string") return body.message;
|
|
168
1273
|
if (typeof body.error === "string") return body.error;
|
|
169
|
-
if (
|
|
1274
|
+
if (isRecord2(body.error) && typeof body.error.message === "string") {
|
|
170
1275
|
return body.error.message;
|
|
171
1276
|
}
|
|
172
1277
|
return null;
|
|
173
1278
|
}
|
|
174
1279
|
function errorFromHttpStatus(status, body) {
|
|
175
1280
|
const message = extractMessage(body) ?? defaultMessageForStatus(status);
|
|
176
|
-
const details =
|
|
1281
|
+
const details = isRecord2(body) ? body : { body };
|
|
177
1282
|
if (status === 400) return new AureonValidationError(message, details);
|
|
178
1283
|
if (status === 404) return new AureonNotFoundError(message, details);
|
|
179
1284
|
if (status === 409) return new AureonConflictError(message, details);
|
|
@@ -332,196 +1437,106 @@ async function requestJson(transport, path, options = {}) {
|
|
|
332
1437
|
);
|
|
333
1438
|
if (attempt <= maxRetries) {
|
|
334
1439
|
transport.logger?.warn("aureon.retry", {
|
|
335
|
-
path,
|
|
336
|
-
attempt,
|
|
337
|
-
reason: "network"
|
|
338
|
-
});
|
|
339
|
-
await sleep(retryDelayMs);
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
throw networkError;
|
|
343
|
-
} finally {
|
|
344
|
-
clearTimeout(timeout);
|
|
345
|
-
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// src/types/client-options.ts
|
|
351
|
-
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
352
|
-
function resolveTimeoutMs(options) {
|
|
353
|
-
const value = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
354
|
-
if (!Number.isFinite(value) || value <= 0) {
|
|
355
|
-
throw new Error("timeoutMs must be a positive finite number");
|
|
356
|
-
}
|
|
357
|
-
return value;
|
|
358
|
-
}
|
|
359
|
-
function resolveHeaders(options) {
|
|
360
|
-
const headers = { ...options.headers ?? {} };
|
|
361
|
-
return headers;
|
|
362
|
-
}
|
|
363
|
-
function resolveMaxRetries(options) {
|
|
364
|
-
const value = options.maxRetries ?? 0;
|
|
365
|
-
if (!Number.isInteger(value) || value < 0) {
|
|
366
|
-
throw new Error("maxRetries must be a non-negative integer");
|
|
367
|
-
}
|
|
368
|
-
return value;
|
|
369
|
-
}
|
|
370
|
-
function resolveRetryDelayMs(options) {
|
|
371
|
-
const value = options.retryDelayMs ?? 250;
|
|
372
|
-
if (!Number.isFinite(value) || value < 0) {
|
|
373
|
-
throw new Error("retryDelayMs must be a non-negative finite number");
|
|
374
|
-
}
|
|
375
|
-
return value;
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
// src/types/market.ts
|
|
379
|
-
function normalizeSymbol(symbol) {
|
|
380
|
-
return symbol.trim().toUpperCase();
|
|
381
|
-
}
|
|
382
|
-
|
|
383
|
-
// src/validation/market-input.ts
|
|
384
|
-
function normalizeApplyMarketEventInput(input) {
|
|
385
|
-
const symbol = normalizeSymbol(input.symbol ?? "");
|
|
386
|
-
if (!symbol) throw new AureonValidationError("symbol is required");
|
|
387
|
-
if (!Number.isFinite(input.priceChangeRatio)) {
|
|
388
|
-
throw new AureonValidationError("priceChangeRatio must be a finite number");
|
|
389
|
-
}
|
|
390
|
-
if (input.priceChangeRatio <= -0.95) {
|
|
391
|
-
throw new AureonValidationError(
|
|
392
|
-
"priceChangeRatio is too extreme for preview runtime"
|
|
393
|
-
);
|
|
394
|
-
}
|
|
395
|
-
return {
|
|
396
|
-
...input,
|
|
397
|
-
symbol,
|
|
398
|
-
autoRestore: input.autoRestore !== false,
|
|
399
|
-
name: input.name?.trim() || void 0,
|
|
400
|
-
description: input.description?.trim() || void 0
|
|
401
|
-
};
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// src/types/objective.ts
|
|
405
|
-
var OBJECTIVE_KINDS = [
|
|
406
|
-
"stable_allocation",
|
|
407
|
-
"balanced_portfolio",
|
|
408
|
-
"risk_ceiling",
|
|
409
|
-
"reward_reinvestment"
|
|
410
|
-
];
|
|
411
|
-
var OBJECTIVE_PRIORITIES = [
|
|
412
|
-
"low",
|
|
413
|
-
"medium",
|
|
414
|
-
"high",
|
|
415
|
-
"critical"
|
|
416
|
-
];
|
|
417
|
-
function isObjectiveKind(value) {
|
|
418
|
-
return OBJECTIVE_KINDS.includes(value);
|
|
419
|
-
}
|
|
420
|
-
function isObjectivePriority(value) {
|
|
421
|
-
return OBJECTIVE_PRIORITIES.includes(value);
|
|
422
|
-
}
|
|
423
|
-
|
|
424
|
-
// src/validation/objective-input.ts
|
|
425
|
-
function buildPolicySummary(kind, targetWeight, tolerance) {
|
|
426
|
-
const targetPct = (targetWeight * 100).toFixed(1);
|
|
427
|
-
const tolPct = (tolerance * 100).toFixed(1);
|
|
428
|
-
switch (kind) {
|
|
429
|
-
case "stable_allocation":
|
|
430
|
-
return `Maintain ${targetPct}% stable allocation within \xB1${tolPct}%`;
|
|
431
|
-
case "balanced_portfolio":
|
|
432
|
-
return `Hold balanced weights near ${targetPct}% primary sleeve within \xB1${tolPct}%`;
|
|
433
|
-
case "risk_ceiling":
|
|
434
|
-
return `Keep portfolio risk at or below configured ceiling with ${tolPct}% buffer`;
|
|
435
|
-
case "reward_reinvestment":
|
|
436
|
-
return `Reinvest available rewards toward ${targetPct}% target sleeve`;
|
|
437
|
-
default:
|
|
438
|
-
return `Objective policy target ${targetPct}% \xB1${tolPct}%`;
|
|
439
|
-
}
|
|
440
|
-
}
|
|
441
|
-
function normalizeCreateObjectiveInput(input) {
|
|
442
|
-
const name = input.name?.trim();
|
|
443
|
-
if (!name || name.length < 3) {
|
|
444
|
-
throw new AureonValidationError("Objective name must be at least 3 characters");
|
|
445
|
-
}
|
|
446
|
-
if (!isObjectiveKind(input.kind)) {
|
|
447
|
-
throw new AureonValidationError(`Unsupported objective kind: ${input.kind}`);
|
|
448
|
-
}
|
|
449
|
-
if (input.targetWeight < 0 || input.targetWeight > 1) {
|
|
450
|
-
throw new AureonValidationError("targetWeight must be between 0 and 1");
|
|
451
|
-
}
|
|
452
|
-
if (input.tolerance < 0 || input.tolerance > 0.5) {
|
|
453
|
-
throw new AureonValidationError("tolerance must be between 0 and 0.5");
|
|
454
|
-
}
|
|
455
|
-
const priority = input.priority ?? "high";
|
|
456
|
-
if (!isObjectivePriority(priority)) {
|
|
457
|
-
throw new AureonValidationError(`Unsupported priority: ${priority}`);
|
|
458
|
-
}
|
|
459
|
-
if (input.kind === "balanced_portfolio") {
|
|
460
|
-
const symbol = input.targetSymbol?.trim().toUpperCase();
|
|
461
|
-
if (!symbol) {
|
|
462
|
-
throw new AureonValidationError(
|
|
463
|
-
"balanced_portfolio requires targetSymbol"
|
|
464
|
-
);
|
|
1440
|
+
path,
|
|
1441
|
+
attempt,
|
|
1442
|
+
reason: "network"
|
|
1443
|
+
});
|
|
1444
|
+
await sleep(retryDelayMs);
|
|
1445
|
+
continue;
|
|
1446
|
+
}
|
|
1447
|
+
throw networkError;
|
|
1448
|
+
} finally {
|
|
1449
|
+
clearTimeout(timeout);
|
|
1450
|
+
if (options.signal) options.signal.removeEventListener("abort", onAbort);
|
|
465
1451
|
}
|
|
466
|
-
return {
|
|
467
|
-
...input,
|
|
468
|
-
name,
|
|
469
|
-
priority,
|
|
470
|
-
targetSymbol: symbol,
|
|
471
|
-
// SDK / agent path defaults to Automatic. Explicit "manual" is reserved
|
|
472
|
-
// for the operator utility Approve UX; not recommended for integrations.
|
|
473
|
-
automationMode: input.automationMode === "manual" ? "manual" : "auto"
|
|
474
|
-
};
|
|
475
1452
|
}
|
|
476
|
-
return {
|
|
477
|
-
...input,
|
|
478
|
-
name,
|
|
479
|
-
priority,
|
|
480
|
-
targetSymbol: null,
|
|
481
|
-
automationMode: input.automationMode === "manual" ? "manual" : "auto"
|
|
482
|
-
};
|
|
483
1453
|
}
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
next.name = name;
|
|
492
|
-
}
|
|
493
|
-
if (next.targetWeight !== void 0) {
|
|
494
|
-
if (next.targetWeight < 0 || next.targetWeight > 1) {
|
|
495
|
-
throw new AureonValidationError("targetWeight must be between 0 and 1");
|
|
496
|
-
}
|
|
1454
|
+
|
|
1455
|
+
// src/types/client-options.ts
|
|
1456
|
+
var DEFAULT_TIMEOUT_MS2 = 3e4;
|
|
1457
|
+
function resolveTimeoutMs(options) {
|
|
1458
|
+
const value = options.timeoutMs ?? DEFAULT_TIMEOUT_MS2;
|
|
1459
|
+
if (!Number.isFinite(value) || value <= 0) {
|
|
1460
|
+
throw new Error("timeoutMs must be a positive finite number");
|
|
497
1461
|
}
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
1462
|
+
return value;
|
|
1463
|
+
}
|
|
1464
|
+
function resolveHeaders(options) {
|
|
1465
|
+
const headers = { ...options.headers ?? {} };
|
|
1466
|
+
return headers;
|
|
1467
|
+
}
|
|
1468
|
+
function resolveMaxRetries(options) {
|
|
1469
|
+
const value = options.maxRetries ?? 0;
|
|
1470
|
+
if (!Number.isInteger(value) || value < 0) {
|
|
1471
|
+
throw new Error("maxRetries must be a non-negative integer");
|
|
502
1472
|
}
|
|
503
|
-
|
|
504
|
-
|
|
1473
|
+
return value;
|
|
1474
|
+
}
|
|
1475
|
+
function resolveRetryDelayMs(options) {
|
|
1476
|
+
const value = options.retryDelayMs ?? 250;
|
|
1477
|
+
if (!Number.isFinite(value) || value < 0) {
|
|
1478
|
+
throw new Error("retryDelayMs must be a non-negative finite number");
|
|
505
1479
|
}
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
1480
|
+
return value;
|
|
1481
|
+
}
|
|
1482
|
+
|
|
1483
|
+
// src/types/market.ts
|
|
1484
|
+
function normalizeSymbol(symbol) {
|
|
1485
|
+
return symbol.trim().toUpperCase();
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
// src/validation/market-input.ts
|
|
1489
|
+
function normalizeApplyMarketEventInput(input) {
|
|
1490
|
+
const symbol = normalizeSymbol(input.symbol ?? "");
|
|
1491
|
+
if (!symbol) throw new AureonValidationError("symbol is required");
|
|
1492
|
+
if (!Number.isFinite(input.priceChangeRatio)) {
|
|
1493
|
+
throw new AureonValidationError("priceChangeRatio must be a finite number");
|
|
510
1494
|
}
|
|
511
|
-
if (input.
|
|
1495
|
+
if (input.priceChangeRatio <= -0.95) {
|
|
512
1496
|
throw new AureonValidationError(
|
|
513
|
-
"
|
|
1497
|
+
"priceChangeRatio is too extreme for preview runtime"
|
|
514
1498
|
);
|
|
515
1499
|
}
|
|
516
|
-
return
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
1500
|
+
return {
|
|
1501
|
+
...input,
|
|
1502
|
+
symbol,
|
|
1503
|
+
autoRestore: input.autoRestore === true,
|
|
1504
|
+
name: input.name?.trim() || void 0,
|
|
1505
|
+
description: input.description?.trim() || void 0
|
|
1506
|
+
};
|
|
522
1507
|
}
|
|
523
1508
|
|
|
524
1509
|
// src/client/aureon-client.ts
|
|
1510
|
+
var DEMO_DRIFT_RESTORE_POSITIONS = [
|
|
1511
|
+
{
|
|
1512
|
+
symbol: "USDG",
|
|
1513
|
+
name: "Paxos USDG",
|
|
1514
|
+
category: "stable",
|
|
1515
|
+
quantity: 24e3,
|
|
1516
|
+
markPriceUsd: 1
|
|
1517
|
+
},
|
|
1518
|
+
{
|
|
1519
|
+
symbol: "NVDA",
|
|
1520
|
+
name: "NVIDIA Stock Token",
|
|
1521
|
+
category: "stock_token",
|
|
1522
|
+
quantity: 45,
|
|
1523
|
+
markPriceUsd: 920
|
|
1524
|
+
},
|
|
1525
|
+
{
|
|
1526
|
+
symbol: "AAPL",
|
|
1527
|
+
name: "Apple Stock Token",
|
|
1528
|
+
category: "stock_token",
|
|
1529
|
+
quantity: 80,
|
|
1530
|
+
markPriceUsd: 210
|
|
1531
|
+
},
|
|
1532
|
+
{
|
|
1533
|
+
symbol: "ETH",
|
|
1534
|
+
name: "Ether",
|
|
1535
|
+
category: "gas",
|
|
1536
|
+
quantity: 8.5,
|
|
1537
|
+
markPriceUsd: 3400
|
|
1538
|
+
}
|
|
1539
|
+
];
|
|
525
1540
|
var AureonClient = class {
|
|
526
1541
|
transport;
|
|
527
1542
|
constructor(options = {}) {
|
|
@@ -734,9 +1749,447 @@ var AureonClient = class {
|
|
|
734
1749
|
async getOverview() {
|
|
735
1750
|
return requestJson(this.transport, ENDPOINTS.overview);
|
|
736
1751
|
}
|
|
1752
|
+
/**
|
|
1753
|
+
* Objective vs actual portfolio — joins objectives, health, and overview
|
|
1754
|
+
* into comparison rows plus a green-book/off-plan paradox flag.
|
|
1755
|
+
* Auth required.
|
|
1756
|
+
*/
|
|
1757
|
+
async getAllocationVsTarget() {
|
|
1758
|
+
const [overview, objectives, health] = await Promise.all([
|
|
1759
|
+
this.getOverview(),
|
|
1760
|
+
this.listObjectives(),
|
|
1761
|
+
this.getHealth()
|
|
1762
|
+
]);
|
|
1763
|
+
const rows = buildAllocationComparison(objectives, health);
|
|
1764
|
+
const paradox = detectPlanParadox(overview, health);
|
|
1765
|
+
return { rows, paradox, overview };
|
|
1766
|
+
}
|
|
1767
|
+
/**
|
|
1768
|
+
* Registers agent/user intent as an Automatic objective and returns the
|
|
1769
|
+
* AI → objective → portfolio flow snapshot.
|
|
1770
|
+
* Auth required.
|
|
1771
|
+
*/
|
|
1772
|
+
async applyFinancialIntent(intent) {
|
|
1773
|
+
const objective = await this.createObjective(
|
|
1774
|
+
resolveObjectiveFromIntent(intent)
|
|
1775
|
+
);
|
|
1776
|
+
try {
|
|
1777
|
+
await this.refreshWatchdog();
|
|
1778
|
+
} catch {
|
|
1779
|
+
}
|
|
1780
|
+
const [healthRecords, portfolio] = await Promise.all([
|
|
1781
|
+
this.getHealth(objective.id),
|
|
1782
|
+
this.getPortfolio()
|
|
1783
|
+
]);
|
|
1784
|
+
return buildObjectivePortfolioFlow(
|
|
1785
|
+
intent,
|
|
1786
|
+
objective,
|
|
1787
|
+
healthRecords[0] ?? null,
|
|
1788
|
+
portfolio
|
|
1789
|
+
);
|
|
1790
|
+
}
|
|
1791
|
+
/**
|
|
1792
|
+
* Read-only AI → objective → portfolio flow for existing objectives.
|
|
1793
|
+
* Auth required.
|
|
1794
|
+
*/
|
|
1795
|
+
async getObjectivePortfolioFlow(objectiveId) {
|
|
1796
|
+
const [objectives, healthRecords, portfolio] = await Promise.all([
|
|
1797
|
+
objectiveId ? [await this.getObjective(objectiveId)] : this.listObjectives(),
|
|
1798
|
+
this.getHealth(objectiveId),
|
|
1799
|
+
this.getPortfolio()
|
|
1800
|
+
]);
|
|
1801
|
+
const active = objectives.filter(
|
|
1802
|
+
(o) => o.status === "active" || o.status === "validated"
|
|
1803
|
+
);
|
|
1804
|
+
const healthById = new Map(healthRecords.map((h) => [h.objectiveId, h]));
|
|
1805
|
+
return active.map((objective) => {
|
|
1806
|
+
const health = healthById.get(objective.id) ?? null;
|
|
1807
|
+
const intent = {
|
|
1808
|
+
brief: objective.name,
|
|
1809
|
+
kind: objective.kind,
|
|
1810
|
+
targetWeight: objective.policy?.targetWeight ?? 0,
|
|
1811
|
+
tolerance: objective.policy?.tolerance ?? 0.02,
|
|
1812
|
+
targetSymbol: objective.policy?.targetSymbol,
|
|
1813
|
+
name: objective.name,
|
|
1814
|
+
priority: objective.priority
|
|
1815
|
+
};
|
|
1816
|
+
return buildObjectivePortfolioFlow(
|
|
1817
|
+
intent,
|
|
1818
|
+
objective,
|
|
1819
|
+
health,
|
|
1820
|
+
portfolio
|
|
1821
|
+
);
|
|
1822
|
+
});
|
|
1823
|
+
}
|
|
1824
|
+
/**
|
|
1825
|
+
* Controlled drift → detection → restore demo
|
|
1826
|
+
* Seeds book, creates stable objective, applies NVDA rally with auto-restore
|
|
1827
|
+
* disabled, then runs manual restore and returns the three-beat flow.
|
|
1828
|
+
* Auth required.
|
|
1829
|
+
*/
|
|
1830
|
+
async runDriftRestoreDemo() {
|
|
1831
|
+
await this.setPortfolio(DEMO_DRIFT_RESTORE_POSITIONS);
|
|
1832
|
+
const objective = await this.createObjective({
|
|
1833
|
+
name: "Maintain 20% Stable Assets",
|
|
1834
|
+
kind: "stable_allocation",
|
|
1835
|
+
targetWeight: 0.2,
|
|
1836
|
+
tolerance: 0.02
|
|
1837
|
+
});
|
|
1838
|
+
try {
|
|
1839
|
+
await this.refreshWatchdog();
|
|
1840
|
+
} catch {
|
|
1841
|
+
}
|
|
1842
|
+
const [alignedHealthRecords, baselineRows] = await Promise.all([
|
|
1843
|
+
this.getHealth(objective.id),
|
|
1844
|
+
this.getAllocationVsTarget()
|
|
1845
|
+
]);
|
|
1846
|
+
const alignedHealth = alignedHealthRecords[0];
|
|
1847
|
+
if (!alignedHealth) {
|
|
1848
|
+
throw new AureonValidationError(
|
|
1849
|
+
"Baseline health missing after objective create"
|
|
1850
|
+
);
|
|
1851
|
+
}
|
|
1852
|
+
const alignedRow = baselineRows.rows.find(
|
|
1853
|
+
(r) => r.objectiveId === objective.id
|
|
1854
|
+
);
|
|
1855
|
+
await this.applyMarketEvent({
|
|
1856
|
+
name: "NVDA Stock Token Rally",
|
|
1857
|
+
description: "Controlled mark move \u2014 drift demo",
|
|
1858
|
+
symbol: "NVDA",
|
|
1859
|
+
priceChangeRatio: 0.45,
|
|
1860
|
+
autoRestore: false
|
|
1861
|
+
});
|
|
1862
|
+
const [driftHealthRecords, driftRows, restorePlan] = await Promise.all([
|
|
1863
|
+
this.getHealth(objective.id),
|
|
1864
|
+
this.getAllocationVsTarget(),
|
|
1865
|
+
this.getRestorePlan(objective.id)
|
|
1866
|
+
]);
|
|
1867
|
+
const driftHealth = driftHealthRecords[0];
|
|
1868
|
+
if (!driftHealth) {
|
|
1869
|
+
throw new AureonValidationError("Drift health missing after market event");
|
|
1870
|
+
}
|
|
1871
|
+
const driftRow = driftRows.rows.find((r) => r.objectiveId === objective.id);
|
|
1872
|
+
const receipt = await this.restoreObjective(objective.id);
|
|
1873
|
+
const [restoredHealthRecords] = await Promise.all([
|
|
1874
|
+
this.getHealth(objective.id)
|
|
1875
|
+
]);
|
|
1876
|
+
const restoredHealth = restoredHealthRecords[0];
|
|
1877
|
+
return buildDriftRestoreFlow({
|
|
1878
|
+
objective,
|
|
1879
|
+
alignedHealth,
|
|
1880
|
+
driftHealth,
|
|
1881
|
+
driftPlan: restorePlan,
|
|
1882
|
+
restoredHealth: restoredHealth ?? driftHealth,
|
|
1883
|
+
receipt,
|
|
1884
|
+
alignedRow,
|
|
1885
|
+
driftRow
|
|
1886
|
+
});
|
|
1887
|
+
}
|
|
1888
|
+
/**
|
|
1889
|
+
* Read-only drift → detection → restore flow for active objectives.
|
|
1890
|
+
* Auth required.
|
|
1891
|
+
*/
|
|
1892
|
+
async getDriftRestoreFlow(objectiveId) {
|
|
1893
|
+
const [objectives, healthRecords, allocation, executions] = await Promise.all([
|
|
1894
|
+
objectiveId ? [await this.getObjective(objectiveId)] : this.listObjectives(),
|
|
1895
|
+
this.getHealth(objectiveId),
|
|
1896
|
+
this.getAllocationVsTarget(),
|
|
1897
|
+
this.listExecutions(objectiveId)
|
|
1898
|
+
]);
|
|
1899
|
+
const active = objectives.filter(
|
|
1900
|
+
(o) => o.status === "active" || o.status === "validated"
|
|
1901
|
+
);
|
|
1902
|
+
const healthById = new Map(healthRecords.map((h) => [h.objectiveId, h]));
|
|
1903
|
+
const rowById = new Map(
|
|
1904
|
+
allocation.rows.map((r) => [r.objectiveId, r])
|
|
1905
|
+
);
|
|
1906
|
+
const receiptsByObjective = /* @__PURE__ */ new Map();
|
|
1907
|
+
for (const receipt of sortExecutionsNewestFirst(executions)) {
|
|
1908
|
+
if (!receiptsByObjective.has(receipt.objectiveId)) {
|
|
1909
|
+
receiptsByObjective.set(receipt.objectiveId, receipt);
|
|
1910
|
+
}
|
|
1911
|
+
}
|
|
1912
|
+
const flows = [];
|
|
1913
|
+
for (const objective of active) {
|
|
1914
|
+
const health = healthById.get(objective.id);
|
|
1915
|
+
if (!health) continue;
|
|
1916
|
+
let restorePlan;
|
|
1917
|
+
if (health.state === "warning" || health.state === "violation") {
|
|
1918
|
+
try {
|
|
1919
|
+
restorePlan = await this.getRestorePlan(objective.id);
|
|
1920
|
+
} catch {
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
flows.push(
|
|
1924
|
+
buildDriftRestoreFlowFromSnapshot({
|
|
1925
|
+
objective,
|
|
1926
|
+
health,
|
|
1927
|
+
allocationRow: rowById.get(objective.id),
|
|
1928
|
+
restorePlan,
|
|
1929
|
+
latestReceipt: receiptsByObjective.get(objective.id)
|
|
1930
|
+
})
|
|
1931
|
+
);
|
|
1932
|
+
}
|
|
1933
|
+
return flows;
|
|
1934
|
+
}
|
|
1935
|
+
async buildReceiptVerificationFlowForReceipt(receipt, timelineEvents) {
|
|
1936
|
+
const validation = validateExecutionReceipt(receipt);
|
|
1937
|
+
let settlement;
|
|
1938
|
+
if (receipt.settlement === "vault") {
|
|
1939
|
+
try {
|
|
1940
|
+
settlement = await this.getExecutionSettlement(receipt.id);
|
|
1941
|
+
} catch {
|
|
1942
|
+
}
|
|
1943
|
+
}
|
|
1944
|
+
const events = timelineEvents ?? findTimelineEventsForReceipt(
|
|
1945
|
+
await this.getTimeline(receipt.objectiveId),
|
|
1946
|
+
receipt
|
|
1947
|
+
);
|
|
1948
|
+
return buildReceiptVerificationFlow({
|
|
1949
|
+
receipt,
|
|
1950
|
+
validation,
|
|
1951
|
+
settlement,
|
|
1952
|
+
timelineEvents: events
|
|
1953
|
+
});
|
|
1954
|
+
}
|
|
1955
|
+
/**
|
|
1956
|
+
* Controlled receipt → verification demo.
|
|
1957
|
+
* Runs drift-restore, then validates receipt and looks up settlement.
|
|
1958
|
+
* Auth required.
|
|
1959
|
+
*/
|
|
1960
|
+
async runReceiptVerificationDemo() {
|
|
1961
|
+
const driftFlow = await this.runDriftRestoreDemo();
|
|
1962
|
+
const receipt = driftFlow.phases.restored?.receipt;
|
|
1963
|
+
if (!receipt) {
|
|
1964
|
+
throw new AureonValidationError(
|
|
1965
|
+
"Restore receipt missing after drift-restore demo"
|
|
1966
|
+
);
|
|
1967
|
+
}
|
|
1968
|
+
return this.buildReceiptVerificationFlowForReceipt(receipt);
|
|
1969
|
+
}
|
|
1970
|
+
/**
|
|
1971
|
+
* Read-only receipt → verification flow for execution receipts.
|
|
1972
|
+
* Auth required.
|
|
1973
|
+
*/
|
|
1974
|
+
async getReceiptVerificationFlow(executionId) {
|
|
1975
|
+
const executions = sortExecutionsNewestFirst(await this.listExecutions());
|
|
1976
|
+
const targets = executionId ? executions.filter((e) => e.id === executionId) : executions.slice(0, 5);
|
|
1977
|
+
if (targets.length === 0) {
|
|
1978
|
+
return [];
|
|
1979
|
+
}
|
|
1980
|
+
const timeline = await this.getTimeline();
|
|
1981
|
+
const flows = [];
|
|
1982
|
+
for (const receipt of targets) {
|
|
1983
|
+
flows.push(
|
|
1984
|
+
await this.buildReceiptVerificationFlowForReceipt(
|
|
1985
|
+
receipt,
|
|
1986
|
+
findTimelineEventsForReceipt(timeline, receipt)
|
|
1987
|
+
)
|
|
1988
|
+
);
|
|
1989
|
+
}
|
|
1990
|
+
return flows;
|
|
1991
|
+
}
|
|
1992
|
+
/**
|
|
1993
|
+
* Controlled portfolio watch demo.
|
|
1994
|
+
* User brief → Automatic objective → market move while away → auto restore → return briefing.
|
|
1995
|
+
* Auth required.
|
|
1996
|
+
*/
|
|
1997
|
+
async runPortfolioWatchDemo(input) {
|
|
1998
|
+
const userBrief = input?.brief?.trim() || DEFAULT_PORTFOLIO_WATCH_BRIEF;
|
|
1999
|
+
const host = input?.host ?? "cursor";
|
|
2000
|
+
await this.setPortfolio(DEMO_DRIFT_RESTORE_POSITIONS);
|
|
2001
|
+
const intent = {
|
|
2002
|
+
brief: userBrief,
|
|
2003
|
+
kind: "stable_allocation",
|
|
2004
|
+
targetWeight: 0.2,
|
|
2005
|
+
tolerance: 0.02
|
|
2006
|
+
};
|
|
2007
|
+
const setup = await this.applyFinancialIntent(intent);
|
|
2008
|
+
const objective = setup.objective;
|
|
2009
|
+
const registerHealth = setup.health;
|
|
2010
|
+
if (!registerHealth) {
|
|
2011
|
+
throw new AureonValidationError(
|
|
2012
|
+
"Register health missing after applyFinancialIntent"
|
|
2013
|
+
);
|
|
2014
|
+
}
|
|
2015
|
+
const baselineRows = await this.getAllocationVsTarget();
|
|
2016
|
+
const registerRow = baselineRows.rows.find(
|
|
2017
|
+
(r) => r.objectiveId === objective.id
|
|
2018
|
+
);
|
|
2019
|
+
const healthBeforeRecords = await this.getHealth(objective.id);
|
|
2020
|
+
const healthBefore = healthBeforeRecords[0];
|
|
2021
|
+
if (!healthBefore) {
|
|
2022
|
+
throw new AureonValidationError("Baseline health missing before market event");
|
|
2023
|
+
}
|
|
2024
|
+
const marketResult = await this.applyMarketEvent({
|
|
2025
|
+
name: "NVDA rally while you were away",
|
|
2026
|
+
description: "portfolio watch demo \u2014 auto restore on",
|
|
2027
|
+
symbol: "NVDA",
|
|
2028
|
+
priceChangeRatio: 0.45,
|
|
2029
|
+
autoRestore: true
|
|
2030
|
+
});
|
|
2031
|
+
const healthAfter = marketResult.health.find((h) => h.objectiveId === objective.id) ?? healthBefore;
|
|
2032
|
+
const receipt = marketResult.executions.find(
|
|
2033
|
+
(e) => e.objectiveId === objective.id
|
|
2034
|
+
);
|
|
2035
|
+
const timeline = await this.getTimeline(objective.id);
|
|
2036
|
+
return buildPortfolioWatchFlow({
|
|
2037
|
+
userBrief,
|
|
2038
|
+
host,
|
|
2039
|
+
objective,
|
|
2040
|
+
registerHealth,
|
|
2041
|
+
registerRow,
|
|
2042
|
+
whileAway: {
|
|
2043
|
+
marketEvent: marketResult.event,
|
|
2044
|
+
healthBefore,
|
|
2045
|
+
healthAfter,
|
|
2046
|
+
autoRestored: marketResult.executions.length > 0,
|
|
2047
|
+
receipt
|
|
2048
|
+
},
|
|
2049
|
+
briefingHealth: healthAfter,
|
|
2050
|
+
timelineEvents: timeline.slice(0, 10)
|
|
2051
|
+
});
|
|
2052
|
+
}
|
|
2053
|
+
/**
|
|
2054
|
+
* Read-only portfolio watch briefing for Automatic objectives.
|
|
2055
|
+
* Auth required.
|
|
2056
|
+
*/
|
|
2057
|
+
async getPortfolioWatchFlow(input) {
|
|
2058
|
+
const userBrief = input?.brief?.trim() || DEFAULT_PORTFOLIO_WATCH_BRIEF;
|
|
2059
|
+
const host = input?.host ?? "mcp";
|
|
2060
|
+
const [objectives, healthRecords, allocation, timeline] = await Promise.all([
|
|
2061
|
+
input?.objectiveId ? [await this.getObjective(input.objectiveId)] : this.listObjectives(),
|
|
2062
|
+
this.getHealth(input?.objectiveId),
|
|
2063
|
+
this.getAllocationVsTarget(),
|
|
2064
|
+
this.getTimeline(input?.objectiveId)
|
|
2065
|
+
]);
|
|
2066
|
+
const active = objectives.filter(
|
|
2067
|
+
(o) => (o.status === "active" || o.status === "validated") && (o.automationMode ?? "auto") === "auto"
|
|
2068
|
+
);
|
|
2069
|
+
const healthById = new Map(healthRecords.map((h) => [h.objectiveId, h]));
|
|
2070
|
+
const rowById = new Map(allocation.rows.map((r) => [r.objectiveId, r]));
|
|
2071
|
+
const flows = [];
|
|
2072
|
+
for (const objective of active) {
|
|
2073
|
+
const health = healthById.get(objective.id);
|
|
2074
|
+
if (!health) continue;
|
|
2075
|
+
const objectiveTimeline = timeline.filter(
|
|
2076
|
+
(e) => !e.objectiveId || e.objectiveId === objective.id
|
|
2077
|
+
);
|
|
2078
|
+
flows.push(
|
|
2079
|
+
buildPortfolioWatchFlowFromSnapshot({
|
|
2080
|
+
userBrief,
|
|
2081
|
+
host,
|
|
2082
|
+
objective,
|
|
2083
|
+
health,
|
|
2084
|
+
allocationRow: rowById.get(objective.id),
|
|
2085
|
+
timelineEvents: objectiveTimeline.slice(0, 10)
|
|
2086
|
+
})
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
return flows;
|
|
2090
|
+
}
|
|
2091
|
+
/**
|
|
2092
|
+
* Controlled full AUREON loop demo (Content Arc).
|
|
2093
|
+
* Intent → plan check (green vs plan with autoRestore false) → restore → receipt verification.
|
|
2094
|
+
* Auth required.
|
|
2095
|
+
*/
|
|
2096
|
+
async runFullAureonLoopDemo(input) {
|
|
2097
|
+
const userBrief = input?.brief?.trim() || DEFAULT_FULL_LOOP_BRIEF;
|
|
2098
|
+
await this.setPortfolio(DEMO_DRIFT_RESTORE_POSITIONS);
|
|
2099
|
+
const intent = {
|
|
2100
|
+
brief: userBrief,
|
|
2101
|
+
kind: "stable_allocation",
|
|
2102
|
+
targetWeight: 0.2,
|
|
2103
|
+
tolerance: 0.02
|
|
2104
|
+
};
|
|
2105
|
+
const setup = await this.applyFinancialIntent(intent);
|
|
2106
|
+
const objective = setup.objective;
|
|
2107
|
+
const baselineHealth = setup.health;
|
|
2108
|
+
if (!baselineHealth) {
|
|
2109
|
+
throw new AureonValidationError(
|
|
2110
|
+
"Baseline health missing after applyFinancialIntent"
|
|
2111
|
+
);
|
|
2112
|
+
}
|
|
2113
|
+
await this.applyMarketEvent({
|
|
2114
|
+
name: "NVDA rally \u2014 green book, off-plan sleeve",
|
|
2115
|
+
description: "Full loop \u2014 autoRestore false to expose plan paradox",
|
|
2116
|
+
symbol: "NVDA",
|
|
2117
|
+
priceChangeRatio: 0.45,
|
|
2118
|
+
autoRestore: false
|
|
2119
|
+
});
|
|
2120
|
+
const [afterShockHealthRecords, allocation] = await Promise.all([
|
|
2121
|
+
this.getHealth(objective.id),
|
|
2122
|
+
this.getAllocationVsTarget()
|
|
2123
|
+
]);
|
|
2124
|
+
const afterShockHealth = afterShockHealthRecords[0];
|
|
2125
|
+
if (!afterShockHealth) {
|
|
2126
|
+
throw new AureonValidationError("Health missing after market event");
|
|
2127
|
+
}
|
|
2128
|
+
const afterShockRow = allocation.rows.find(
|
|
2129
|
+
(r) => r.objectiveId === objective.id
|
|
2130
|
+
);
|
|
2131
|
+
const receipt = await this.restoreObjective(objective.id);
|
|
2132
|
+
const restoredHealthRecords = await this.getHealth(objective.id);
|
|
2133
|
+
const restoredHealth = restoredHealthRecords[0] ?? afterShockHealth;
|
|
2134
|
+
const verification = await this.buildReceiptVerificationFlowForReceipt(receipt);
|
|
2135
|
+
return buildFullAureonLoopFlow({
|
|
2136
|
+
userBrief,
|
|
2137
|
+
objective,
|
|
2138
|
+
baselineHealth,
|
|
2139
|
+
afterShockHealth,
|
|
2140
|
+
afterShockRow,
|
|
2141
|
+
paradox: allocation.paradox,
|
|
2142
|
+
restoredHealth,
|
|
2143
|
+
receipt,
|
|
2144
|
+
verification
|
|
2145
|
+
});
|
|
2146
|
+
}
|
|
2147
|
+
/**
|
|
2148
|
+
* Read-only full AUREON loop for active objectives with a latest receipt.
|
|
2149
|
+
* Auth required.
|
|
2150
|
+
*/
|
|
2151
|
+
async getFullAureonLoopFlow(input) {
|
|
2152
|
+
const userBrief = input?.brief?.trim() || DEFAULT_FULL_LOOP_BRIEF;
|
|
2153
|
+
const [objectives, healthRecords, allocation, executions] = await Promise.all([
|
|
2154
|
+
input?.objectiveId ? [await this.getObjective(input.objectiveId)] : this.listObjectives(),
|
|
2155
|
+
this.getHealth(input?.objectiveId),
|
|
2156
|
+
this.getAllocationVsTarget(),
|
|
2157
|
+
this.listExecutions(input?.objectiveId)
|
|
2158
|
+
]);
|
|
2159
|
+
const active = objectives.filter(
|
|
2160
|
+
(o) => o.status === "active" || o.status === "validated"
|
|
2161
|
+
);
|
|
2162
|
+
const healthById = new Map(healthRecords.map((h) => [h.objectiveId, h]));
|
|
2163
|
+
const rowById = new Map(allocation.rows.map((r) => [r.objectiveId, r]));
|
|
2164
|
+
const receiptsByObjective = /* @__PURE__ */ new Map();
|
|
2165
|
+
for (const receipt of sortExecutionsNewestFirst(executions)) {
|
|
2166
|
+
if (!receiptsByObjective.has(receipt.objectiveId)) {
|
|
2167
|
+
receiptsByObjective.set(receipt.objectiveId, receipt);
|
|
2168
|
+
}
|
|
2169
|
+
}
|
|
2170
|
+
const flows = [];
|
|
2171
|
+
for (const objective of active) {
|
|
2172
|
+
const health = healthById.get(objective.id);
|
|
2173
|
+
if (!health) continue;
|
|
2174
|
+
const latestReceipt = receiptsByObjective.get(objective.id);
|
|
2175
|
+
if (!latestReceipt) continue;
|
|
2176
|
+
const verification = await this.buildReceiptVerificationFlowForReceipt(latestReceipt);
|
|
2177
|
+
const flow = buildFullAureonLoopFlowFromSnapshot({
|
|
2178
|
+
userBrief,
|
|
2179
|
+
objective,
|
|
2180
|
+
health,
|
|
2181
|
+
allocationRow: rowById.get(objective.id),
|
|
2182
|
+
paradox: allocation.paradox,
|
|
2183
|
+
latestReceipt,
|
|
2184
|
+
verification
|
|
2185
|
+
});
|
|
2186
|
+
if (flow) flows.push(flow);
|
|
2187
|
+
}
|
|
2188
|
+
return flows;
|
|
2189
|
+
}
|
|
737
2190
|
/**
|
|
738
2191
|
* Applies a controlled market event to portfolio marks.
|
|
739
|
-
* When autoRestore is true, the API
|
|
2192
|
+
* When autoRestore is true, the API may run restore. Omit or false = drift only, no restore. Automatic still 409s if the vault cannot execute.
|
|
740
2193
|
* Auth required.
|
|
741
2194
|
*/
|
|
742
2195
|
async applyMarketEvent(input) {
|
|
@@ -780,7 +2233,8 @@ var AureonClient = class {
|
|
|
780
2233
|
});
|
|
781
2234
|
}
|
|
782
2235
|
/**
|
|
783
|
-
* Runs
|
|
2236
|
+
* Runs restorative execution for an objective outside policy.
|
|
2237
|
+
* Receipt.settlement may be vault or staged. Only verifiedOnChain is proof.
|
|
784
2238
|
* Auth required.
|
|
785
2239
|
*/
|
|
786
2240
|
async restoreObjective(objectiveId) {
|
|
@@ -798,6 +2252,69 @@ var AureonClient = class {
|
|
|
798
2252
|
);
|
|
799
2253
|
return result.executions;
|
|
800
2254
|
}
|
|
2255
|
+
/** Returns chain-verified settlement record for an execution when present. Auth required. */
|
|
2256
|
+
async getExecutionSettlement(executionId) {
|
|
2257
|
+
assertId(executionId, "execution id");
|
|
2258
|
+
return requestJson(this.transport, executionSettlementPath(executionId));
|
|
2259
|
+
}
|
|
2260
|
+
/** Lists chain-verified settlement records for the authenticated wallet. Auth required. */
|
|
2261
|
+
async listSettlements(objectiveId) {
|
|
2262
|
+
const path = withQuery(ENDPOINTS.settlements, { objectiveId });
|
|
2263
|
+
const result = await requestJson(
|
|
2264
|
+
this.transport,
|
|
2265
|
+
path
|
|
2266
|
+
);
|
|
2267
|
+
return result.settlements;
|
|
2268
|
+
}
|
|
2269
|
+
/**
|
|
2270
|
+
* Manual backfill: verify a vault tx on-chain and attach settlement proof.
|
|
2271
|
+
* Auth required.
|
|
2272
|
+
*/
|
|
2273
|
+
async confirmExecutionSettlement(executionId, transactionHash) {
|
|
2274
|
+
assertId(executionId, "execution id");
|
|
2275
|
+
const hash = transactionHash.trim();
|
|
2276
|
+
if (!hash) {
|
|
2277
|
+
throw new AureonValidationError("transactionHash is required");
|
|
2278
|
+
}
|
|
2279
|
+
return requestJson(this.transport, executionConfirmSettlementPath(executionId), {
|
|
2280
|
+
method: "POST",
|
|
2281
|
+
body: { transactionHash: hash }
|
|
2282
|
+
});
|
|
2283
|
+
}
|
|
2284
|
+
/** Returns Phase 2 ObjectiveRegistry deployment status. Auth required. */
|
|
2285
|
+
async getRegistryStatus() {
|
|
2286
|
+
return requestJson(this.transport, ENDPOINTS.registryStatus);
|
|
2287
|
+
}
|
|
2288
|
+
/** Returns on-chain registry record for an objective when registered. Auth required. */
|
|
2289
|
+
async getObjectiveRegistry(objectiveId) {
|
|
2290
|
+
assertId(objectiveId, "objective id");
|
|
2291
|
+
return requestJson(this.transport, registryObjectivePath(objectiveId));
|
|
2292
|
+
}
|
|
2293
|
+
/**
|
|
2294
|
+
* Prepares wallet-signed calldata to register an objective on ObjectiveRegistry.
|
|
2295
|
+
* Auth required.
|
|
2296
|
+
*/
|
|
2297
|
+
async prepareObjectiveRegistry(objectiveId) {
|
|
2298
|
+
assertId(objectiveId, "objective id");
|
|
2299
|
+
return requestJson(this.transport, registryPreparePath(objectiveId), {
|
|
2300
|
+
method: "POST"
|
|
2301
|
+
});
|
|
2302
|
+
}
|
|
2303
|
+
/**
|
|
2304
|
+
* Confirms an on-chain registration after the wallet broadcast tx.
|
|
2305
|
+
* Auth required.
|
|
2306
|
+
*/
|
|
2307
|
+
async confirmObjectiveRegistry(objectiveId, transactionHash) {
|
|
2308
|
+
assertId(objectiveId, "objective id");
|
|
2309
|
+
const hash = transactionHash.trim();
|
|
2310
|
+
if (!hash) {
|
|
2311
|
+
throw new AureonValidationError("transactionHash is required");
|
|
2312
|
+
}
|
|
2313
|
+
return requestJson(this.transport, registryConfirmPath(objectiveId), {
|
|
2314
|
+
method: "POST",
|
|
2315
|
+
body: { transactionHash: hash }
|
|
2316
|
+
});
|
|
2317
|
+
}
|
|
801
2318
|
/** Returns the vault overview for the authenticated wallet. Auth required. */
|
|
802
2319
|
async getVault() {
|
|
803
2320
|
return requestJson(this.transport, ENDPOINTS.vault);
|
|
@@ -884,6 +2401,43 @@ var AureonClient = class {
|
|
|
884
2401
|
method: "POST"
|
|
885
2402
|
});
|
|
886
2403
|
}
|
|
2404
|
+
/**
|
|
2405
|
+
* Joins objective → registry → receipts → settlements → timeline.
|
|
2406
|
+
* Missing proof is labeled as a gap. Nothing is invented. Auth required.
|
|
2407
|
+
*/
|
|
2408
|
+
async getAuditTrail(objectiveId) {
|
|
2409
|
+
assertId(objectiveId, "objective id");
|
|
2410
|
+
const [objective, healthRows, receipts, settlementsResult, timeline] = await Promise.all([
|
|
2411
|
+
this.getObjective(objectiveId),
|
|
2412
|
+
this.getHealth(objectiveId),
|
|
2413
|
+
this.listExecutions(objectiveId),
|
|
2414
|
+
this.listSettlements(objectiveId).then(
|
|
2415
|
+
(rows) => ({ ok: true, rows }),
|
|
2416
|
+
() => ({ ok: false, rows: [] })
|
|
2417
|
+
),
|
|
2418
|
+
this.getTimeline(objectiveId)
|
|
2419
|
+
]);
|
|
2420
|
+
let registry = {
|
|
2421
|
+
registered: false,
|
|
2422
|
+
objectiveId
|
|
2423
|
+
};
|
|
2424
|
+
let registryLookupFailed = false;
|
|
2425
|
+
try {
|
|
2426
|
+
registry = await this.getObjectiveRegistry(objectiveId);
|
|
2427
|
+
} catch {
|
|
2428
|
+
registryLookupFailed = true;
|
|
2429
|
+
}
|
|
2430
|
+
return buildFinancialAuditTrail({
|
|
2431
|
+
objective,
|
|
2432
|
+
health: healthRows[0],
|
|
2433
|
+
registry,
|
|
2434
|
+
receipts: sortExecutionsNewestFirst(receipts),
|
|
2435
|
+
settlements: settlementsResult.rows,
|
|
2436
|
+
timeline,
|
|
2437
|
+
registryLookupFailed,
|
|
2438
|
+
settlementsLookupFailed: !settlementsResult.ok
|
|
2439
|
+
});
|
|
2440
|
+
}
|
|
887
2441
|
};
|
|
888
2442
|
|
|
889
2443
|
// src/client/factory.ts
|
|
@@ -994,15 +2548,25 @@ var TIMELINE_EVENT_TYPES = [
|
|
|
994
2548
|
"objective_restored",
|
|
995
2549
|
"capital_provisioned",
|
|
996
2550
|
"capital_cleared",
|
|
997
|
-
"capital_synced"
|
|
2551
|
+
"capital_synced",
|
|
2552
|
+
"registry_registered",
|
|
2553
|
+
"settlement_recorded"
|
|
998
2554
|
];
|
|
999
2555
|
function isTimelineEventType(value) {
|
|
1000
2556
|
return TIMELINE_EVENT_TYPES.includes(value);
|
|
1001
2557
|
}
|
|
1002
2558
|
|
|
1003
|
-
// src/types/
|
|
1004
|
-
function
|
|
1005
|
-
|
|
2559
|
+
// src/types/settlement.ts
|
|
2560
|
+
function formatSettlementSummary(record) {
|
|
2561
|
+
const parts = [
|
|
2562
|
+
"vault settlement (chain-verified)",
|
|
2563
|
+
`block ${record.blockNumber}`,
|
|
2564
|
+
record.explorerUrl
|
|
2565
|
+
];
|
|
2566
|
+
if (record.registryRef) {
|
|
2567
|
+
parts.push(`registry ${record.registryRef.objectiveKey.slice(0, 10)}\u2026`);
|
|
2568
|
+
}
|
|
2569
|
+
return parts.join(" \xB7 ");
|
|
1006
2570
|
}
|
|
1007
2571
|
|
|
1008
2572
|
// src/adapters/logging-adapter.ts
|
|
@@ -1031,6 +2595,6 @@ function createConsoleLogger(prefix = "aureon-sdk") {
|
|
|
1031
2595
|
};
|
|
1032
2596
|
}
|
|
1033
2597
|
|
|
1034
|
-
export { API_KEY_HEADER, AureonClient, AureonConflictError, AureonError, AureonNetworkError, AureonNotFoundError, AureonTimeoutError, AureonValidationError, DEFAULT_API_BASE_URL, DEFAULT_TIMEOUT_MS, ENDPOINTS, LOCAL_API_BASE_URL, OBJECTIVE_KINDS, OBJECTIVE_PRIORITIES, PRODUCT_NAME, PRODUCT_TAGLINE, SDK_NAME, SDK_VERSION, TIMELINE_EVENT_TYPES, assertBaseUrl, buildPolicySummary, createAureonClient, createConsoleLogger, createLocalAureonClient, createSessionTokenProvider, errorFromHttpStatus, formatIsoTime, formatSignedPercent, formatUsd, formatWeight, healthTone, isAureonError, isHealthState, isObjectiveKind, isObjectivePriority, isTimelineEventType, isVaultSettlement, joinUrl, normalizeCreateObjectiveInput, normalizeUpdateObjectiveInput, pickWorstHealth, requestJson, resolveFetch, silentLogger, withQuery };
|
|
2598
|
+
export { API_KEY_HEADER, AureonClient, AureonConflictError, AureonError, AureonNetworkError, AureonNotFoundError, AureonTimeoutError, AureonValidationError, DEFAULT_API_BASE_URL, DEFAULT_FULL_LOOP_BRIEF, DEFAULT_PORTFOLIO_WATCH_BRIEF, DEFAULT_TIMEOUT_MS, ENDPOINTS, LOCAL_API_BASE_URL, OBJECTIVE_KINDS, OBJECTIVE_PRIORITIES, PRODUCT_NAME, PRODUCT_TAGLINE, SDK_NAME, SDK_VERSION, TIMELINE_EVENT_TYPES, assertBaseUrl, assertValidExecutionReceipt, buildAllocationComparison, buildDriftRestoreFlow, buildDriftRestoreFlowFromSnapshot, buildFinancialAuditTrail, buildFullAureonLoopFlow, buildFullAureonLoopFlowFromSnapshot, buildObjectivePortfolioFlow, buildPolicySummary, buildPortfolioWatchBriefingLines, buildPortfolioWatchFlow, buildPortfolioWatchFlowFromSnapshot, buildReceiptVerificationFlow, createAureonClient, createConsoleLogger, createLocalAureonClient, createSessionTokenProvider, detectPlanParadox, errorFromHttpStatus, findTimelineEventsForReceipt, formatAuditTrailLines, formatIsoTime, formatReceiptSummary, formatSettlementSummary, formatSignedPercent, formatUsd, formatWeight, healthTone, inferDriftPhase, inferFullAureonLoopPhase, inferPortfolioWatchPhase, inferProofTier, isAureonError, isChainVerifiedReceipt, isHealthState, isObjectiveKind, isObjectivePriority, isTimelineEventType, isValidExecutionReceipt, isVaultSettlement, joinUrl, normalizeCreateObjectiveInput, normalizeUpdateObjectiveInput, parseFinancialIntent, pickWorstHealth, requestJson, resolveFetch, resolveObjectiveFromIntent, silentLogger, validateExecutionReceipt, validateSettlementRecord, withQuery };
|
|
1035
2599
|
//# sourceMappingURL=index.js.map
|
|
1036
2600
|
//# sourceMappingURL=index.js.map
|