@cynodia/axiom-runtime 0.8.2-alpha.1 → 0.9.0-alpha.2

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/dist/dom.d.ts CHANGED
@@ -86,5 +86,15 @@ export interface HostEnvironment {
86
86
  queryIntegration?(operationId: string, args: Record<string, unknown>, options: {
87
87
  timeoutMs?: number;
88
88
  }): Promise<IntegrationQueryOutcome>;
89
+ /**
90
+ * Reads a stored object's metadata by key and returns it as a `BlobRef` record.
91
+ *
92
+ * The same shape as `queryIntegration`, and for the same reason: a `blob-metadata`
93
+ * operation is a finite question resolved before the transaction opens. It returns the
94
+ * reference, never the bytes — nothing in the runtime ever holds an object's contents.
95
+ * Only the authoritative runtime calls this; object stores, like integrations, are
96
+ * server-only.
97
+ */
98
+ readBlobMetadata?(storageId: string, key: string): Promise<IntegrationQueryOutcome>;
89
99
  }
90
100
  //# sourceMappingURL=dom.d.ts.map
package/dist/runtime.d.ts CHANGED
@@ -44,6 +44,10 @@ export declare const RUNTIME_DIAGNOSTIC_CODES: {
44
44
  readonly INTEGRATION_RESULT_INVALID: "INTEGRATION_RESULT_INVALID";
45
45
  /** An integration query failed. Never carries a provider secret. */
46
46
  readonly INTEGRATION_QUERY_FAILED: "INTEGRATION_QUERY_FAILED";
47
+ /** No host capable of reaching an object store is configured. */
48
+ readonly BLOB_STORAGE_UNAVAILABLE: "BLOB_STORAGE_UNAVAILABLE";
49
+ /** A `blob-metadata` lookup failed: no such key, a staged object, or the store refused. */
50
+ readonly BLOB_METADATA_FAILED: "BLOB_METADATA_FAILED";
47
51
  };
48
52
  export type RuntimeDiagnosticCode = (typeof RUNTIME_DIAGNOSTIC_CODES)[keyof typeof RUNTIME_DIAGNOSTIC_CODES];
49
53
  /**
@@ -81,6 +85,19 @@ export interface EffectIntentRecord {
81
85
  succeededEventId?: NodeId;
82
86
  failedEventId?: NodeId;
83
87
  outcome?: 'committed' | 'rolled-back';
88
+ /**
89
+ * Set instead of an integration operation when the intent is a storage effect — a
90
+ * `blob-commit` or a `blob-delete`. It rides the same outbox for the same reason: an
91
+ * object store cannot join an Axiom transaction, so the intent commits with the state
92
+ * that references the object and dispatches only afterwards. `operationId` then carries
93
+ * the store's id, so one log and one dispatcher serve both kinds of effect rather than
94
+ * a second, parallel durability system (spec 0.9 §57).
95
+ */
96
+ storage?: {
97
+ storageId: NodeId;
98
+ operation: 'commit' | 'delete';
99
+ key: string;
100
+ };
84
101
  }
85
102
  export interface ActionResult {
86
103
  ok: boolean;
package/dist/runtime.js CHANGED
@@ -48,6 +48,10 @@ export const RUNTIME_DIAGNOSTIC_CODES = {
48
48
  INTEGRATION_RESULT_INVALID: 'INTEGRATION_RESULT_INVALID',
49
49
  /** An integration query failed. Never carries a provider secret. */
50
50
  INTEGRATION_QUERY_FAILED: 'INTEGRATION_QUERY_FAILED',
51
+ /** No host capable of reaching an object store is configured. */
52
+ BLOB_STORAGE_UNAVAILABLE: 'BLOB_STORAGE_UNAVAILABLE',
53
+ /** A `blob-metadata` lookup failed: no such key, a staged object, or the store refused. */
54
+ BLOB_METADATA_FAILED: 'BLOB_METADATA_FAILED',
51
55
  };
52
56
  const MISSING = Symbol('missing');
53
57
  function unwrapType(type) {
@@ -1083,6 +1087,7 @@ export function createAxiomRuntime(options) {
1083
1087
  return;
1084
1088
  }
1085
1089
  case 'integration-query':
1090
+ case 'blob-metadata':
1086
1091
  // The result was already resolved and bound into the action's scope before this
1087
1092
  // transaction opened — see `runActionAsync`. Nothing to do here; this case exists
1088
1093
  // only so the kind is recognized rather than falling to `default`.
@@ -1109,6 +1114,27 @@ export function createAxiomRuntime(options) {
1109
1114
  });
1110
1115
  return;
1111
1116
  }
1117
+ case 'blob-commit':
1118
+ case 'blob-delete': {
1119
+ // Identical discipline to `integration-effect`: no store is called here. Reaching
1120
+ // the operation records intent in the same log, discarded on rollback the same way,
1121
+ // and dispatched only once the surrounding transaction has committed.
1122
+ effectIntentLog.push({
1123
+ id: host.uuid(),
1124
+ transactionId: context.transactionId,
1125
+ ...(context.sourceNodeId ? { actionId: context.sourceNodeId } : {}),
1126
+ operationId: operation.storageId,
1127
+ arguments: {},
1128
+ storage: {
1129
+ storageId: operation.storageId,
1130
+ operation: operation.kind === 'blob-commit' ? 'commit' : 'delete',
1131
+ key: toText(evaluate(operation.blobKey, scope)),
1132
+ },
1133
+ ...(operation.succeededEventId ? { succeededEventId: operation.succeededEventId } : {}),
1134
+ ...(operation.failedEventId ? { failedEventId: operation.failedEventId } : {}),
1135
+ });
1136
+ return;
1137
+ }
1112
1138
  default:
1113
1139
  result.push({
1114
1140
  code: RUNTIME_DIAGNOSTIC_CODES.UNSUPPORTED_OPERATION,
@@ -1267,8 +1293,8 @@ export function createAxiomRuntime(options) {
1267
1293
  return result;
1268
1294
  });
1269
1295
  }
1270
- function actionHasIntegrationQuery(action) {
1271
- return (action.operations ?? []).some((operation) => operation.kind === 'integration-query');
1296
+ function actionHasAsyncQuery(action) {
1297
+ return (action.operations ?? []).some((operation) => operation.kind === 'integration-query' || operation.kind === 'blob-metadata');
1272
1298
  }
1273
1299
  /**
1274
1300
  * Resolves every top-level `integration-query` operation before the transaction opens,
@@ -1282,7 +1308,7 @@ export function createAxiomRuntime(options) {
1282
1308
  */
1283
1309
  async function runActionAsync(actionId, args = {}) {
1284
1310
  const action = ir.actions[actionId];
1285
- if (!action || !actionHasIntegrationQuery(action) || remoteActionIds.has(actionId)) {
1311
+ if (!action || !actionHasAsyncQuery(action) || remoteActionIds.has(actionId)) {
1286
1312
  return runAction(actionId, args);
1287
1313
  }
1288
1314
  const scope = rootScope();
@@ -1290,7 +1316,55 @@ export function createAxiomRuntime(options) {
1290
1316
  scope.values.set(parameter.id, args[parameter.id] ?? null);
1291
1317
  }
1292
1318
  const presetBindings = new Map();
1319
+ const failWith = (diagnostic) => {
1320
+ report(diagnostic);
1321
+ recordOutcome(actionId, 'failed', [diagnostic]);
1322
+ renderApplication();
1323
+ return { ok: false, diagnostics: [diagnostic] };
1324
+ };
1293
1325
  for (const operation of action.operations ?? []) {
1326
+ if (operation.kind === 'blob-metadata') {
1327
+ // A metadata lookup is the storage half of the same pre-transaction phase: a finite
1328
+ // question, answered before anything is mutated, whose result later operations read.
1329
+ const key = toText(evaluate(operation.blobKey, scope));
1330
+ let blobOutcome;
1331
+ try {
1332
+ blobOutcome = await host.readBlobMetadata?.(String(operation.storageId), key);
1333
+ }
1334
+ catch (error) {
1335
+ blobOutcome = {
1336
+ ok: false,
1337
+ code: RUNTIME_DIAGNOSTIC_CODES.BLOB_METADATA_FAILED,
1338
+ message: error instanceof Error ? error.message : String(error),
1339
+ };
1340
+ }
1341
+ if (!blobOutcome) {
1342
+ return failWith({
1343
+ code: RUNTIME_DIAGNOSTIC_CODES.BLOB_STORAGE_UNAVAILABLE,
1344
+ message: `${action.name ?? action.id} reads object storage, but this host provides none`,
1345
+ severity: 'error',
1346
+ nodeId: action.id,
1347
+ actionId: action.id,
1348
+ details: { storageId: String(operation.storageId) },
1349
+ });
1350
+ }
1351
+ if (!blobOutcome.ok) {
1352
+ // The store's own code travels in `details.code` rather than replacing the
1353
+ // diagnostic code: a provider's vocabulary is not Axiom's, and a caller matching
1354
+ // on `code` must see a code this runtime actually declares.
1355
+ return failWith({
1356
+ code: RUNTIME_DIAGNOSTIC_CODES.BLOB_METADATA_FAILED,
1357
+ message: blobOutcome.message,
1358
+ severity: 'error',
1359
+ nodeId: action.id,
1360
+ actionId: action.id,
1361
+ details: { storageId: String(operation.storageId), code: blobOutcome.code },
1362
+ });
1363
+ }
1364
+ scope.values.set(operation.bindAs, blobOutcome.value);
1365
+ presetBindings.set(operation.bindAs, blobOutcome.value);
1366
+ continue;
1367
+ }
1294
1368
  if (operation.kind !== 'integration-query') {
1295
1369
  continue;
1296
1370
  }
@@ -2455,7 +2529,7 @@ export function createAxiomRuntime(options) {
2455
2529
  },
2456
2530
  async invokeActionAsync(id, args = {}) {
2457
2531
  const action = ir.actions[id];
2458
- if (action && actionHasIntegrationQuery(action) && !remoteActionIds.has(id)) {
2532
+ if (action && actionHasAsyncQuery(action) && !remoteActionIds.has(id)) {
2459
2533
  return runActionAsync(id, args);
2460
2534
  }
2461
2535
  const result = runAction(id, args);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cynodia/axiom-runtime",
3
- "version": "0.8.2-alpha.1",
3
+ "version": "0.9.0-alpha.2",
4
4
  "description": "Domain-independent runtime that executes an Axiom application graph.",
5
5
  "license": "MIT",
6
6
  "author": "AskTech AS",
@@ -31,7 +31,7 @@
31
31
  }
32
32
  },
33
33
  "dependencies": {
34
- "@cynodia/axiom-core": "0.8.2-alpha.1"
34
+ "@cynodia/axiom-core": "0.9.0-alpha.2"
35
35
  },
36
36
  "scripts": {
37
37
  "build": "tsc -b tsconfig.json tsconfig.test.json",