@happyvertical/smrt-agents 0.49.2 → 0.49.4

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/server.js CHANGED
@@ -1,7 +1,10 @@
1
1
  import { n as executeAsPrincipal, r as AgentConfig } from "./chunks/execute-as-principal-DIyBp1oE.js";
2
2
  import { i as loadManifestsFromPackages, n as extractAgentPackagesFromConfig, r as loadManifestsFromConfig, t as extractAgentManifest } from "./chunks/manifest-utils-CtMyFQDx.js";
3
+ import { ObjectRegistry, SmrtObject, field, smrt } from "@happyvertical/smrt-core";
3
4
  import { sanitizeConfig } from "@happyvertical/smrt-config";
4
- import { createHash, randomBytes } from "node:crypto";
5
+ import { TenantScoped, getTenantId, tenantId } from "@happyvertical/smrt-tenancy";
6
+ import { createHash, randomBytes, randomUUID } from "node:crypto";
7
+ import { SmrtJobCollection, backgroundEligible, createHmacDurableJobPayloadSigner, getActiveJobExecutionContext, isRunnerExecutionContext } from "@happyvertical/smrt-jobs";
5
8
  //#region src/server/api-routes.ts
6
9
  function buildRouteMap(manifests) {
7
10
  const routes = /* @__PURE__ */ new Map();
@@ -92,6 +95,16 @@ var InMemoryDataSurfaceActionStateStore = class {
92
95
  record.consumedBy = idempotencyKey;
93
96
  return true;
94
97
  }
98
+ consumeTokenAndReserveIdempotency(token, idempotencyKey, scope, reservation) {
99
+ const tokenRecord = this.tokens.get(token);
100
+ if (!tokenRecord || tokenRecord.consumedBy && tokenRecord.consumedBy !== idempotencyKey) return;
101
+ if (!this.idempotency.get(scope)) this.idempotency.set(scope, {
102
+ status: "reserved",
103
+ ...reservation
104
+ });
105
+ tokenRecord.consumedBy = idempotencyKey;
106
+ return this.idempotency.get(scope);
107
+ }
95
108
  getIdempotency(key) {
96
109
  return this.idempotency.get(key);
97
110
  }
@@ -169,6 +182,22 @@ function stable(value) {
169
182
  function fingerprint(value) {
170
183
  return createHash("sha256").update(stable(value)).digest("hex");
171
184
  }
185
+ function envelopeBinding(envelope, key) {
186
+ return createHmacDurableJobPayloadSigner({
187
+ keyId: "data-surface-envelope-v1",
188
+ key
189
+ }).sign(envelope);
190
+ }
191
+ function validSigningKey(key) {
192
+ return key !== void 0 && (typeof key === "string" ? Buffer.byteLength(key) : key.byteLength) >= 32;
193
+ }
194
+ function bindingMatches(envelope, key) {
195
+ const { binding, ...unsigned } = envelope;
196
+ return createHmacDurableJobPayloadSigner({
197
+ keyId: "data-surface-envelope-v1",
198
+ key
199
+ }).verify(unsigned, binding);
200
+ }
172
201
  function identityKey(identity) {
173
202
  return stable(canonicalIdentity(identity));
174
203
  }
@@ -417,7 +446,7 @@ function createDataSurfaceActionAdapter(options) {
417
446
  }
418
447
  return result(request, true, void 0, outcomesDetails(outcomes));
419
448
  }
420
- async function executeBackgroundOnce(request, context, token, reference) {
449
+ async function executeBackgroundOnce(request, token, reference) {
421
450
  const ownerToken = randomBytes(16).toString("base64url");
422
451
  const executionFingerprint = fingerprint({
423
452
  kind: "background-execution",
@@ -445,19 +474,32 @@ function createDataSurfaceActionAdapter(options) {
445
474
  if (winner.requestFingerprint !== executionFingerprint) return result(request, false, "idempotency_conflict");
446
475
  if (winner.status === "completed") return replayResult(request, winner.result);
447
476
  if (winner.ownerToken === ownerToken) {
477
+ let refreshed;
478
+ try {
479
+ const resolved = await options.resolveDeferredPrincipal?.(reference);
480
+ if (!resolved || resolved.principal.runAsUserId !== reference.runAsUserId || resolved.principal.tenantId !== reference.tenantId || (resolved.principal.actsAsProfileId ?? null) !== reference.actsAsProfileId || (resolved.onBehalfOfUserId ?? null) !== reference.onBehalfOfUserId || (resolved.agentClass ?? null) !== (reference.agentClass ?? null) || !Array.isArray(resolved.principal.allowedTools)) throw new Error("Deferred data-surface action principal binding could not be resolved safely");
481
+ refreshed = resolved;
482
+ } catch (error) {
483
+ const reason = options.mapError?.(error, request);
484
+ if (!reason) {
485
+ await state.releaseIdempotency(executionScope, ownerToken);
486
+ throw error;
487
+ }
488
+ const denied = result(request, false, reason);
489
+ if (!await state.completeIdempotency(executionScope, ownerToken, denied)) throw new Error("Lost background action idempotency reservation");
490
+ return denied;
491
+ }
448
492
  let executed;
493
+ let mutationStarted = false;
449
494
  try {
450
- const refreshed = await options.resolveDeferredPrincipal?.(reference);
451
- if (!refreshed || refreshed.principal.runAsUserId !== reference.runAsUserId || refreshed.principal.tenantId !== reference.tenantId || (refreshed.principal.actsAsProfileId ?? null) !== reference.actsAsProfileId || (refreshed.onBehalfOfUserId ?? null) !== reference.onBehalfOfUserId || (refreshed.agentClass ?? null) !== (reference.agentClass ?? null) || !Array.isArray(refreshed.principal.allowedTools)) throw new Error("Deferred data-surface action principal binding could not be resolved safely");
452
495
  const { permissions: _permissions, ...livePrincipal } = refreshed;
453
- executed = await authorizedApply(request, {
454
- ...context,
455
- principal: livePrincipal
456
- }, token, false);
496
+ executed = await authorizedApply(request, { principal: livePrincipal }, token, false, () => {
497
+ mutationStarted = true;
498
+ });
457
499
  } catch (error) {
458
500
  const reason = options.mapError?.(error, request);
459
501
  if (!reason) {
460
- await state.releaseIdempotency(executionScope, ownerToken);
502
+ if (!mutationStarted) await state.releaseIdempotency(executionScope, ownerToken);
461
503
  throw error;
462
504
  }
463
505
  executed = result(request, false, reason);
@@ -473,7 +515,7 @@ function createDataSurfaceActionAdapter(options) {
473
515
  }
474
516
  return result(request, false, "idempotency_in_progress");
475
517
  }
476
- async function authorizedApply(request, context, token, allowBackground) {
518
+ async function authorizedApply(request, context, token, allowBackground, beforeMutation) {
477
519
  const idempotencyKey = request.idempotencyKey;
478
520
  if (!idempotencyKey) return result(request, false, "invalid_request");
479
521
  const deferredPrincipalReference = Object.freeze({
@@ -501,13 +543,25 @@ function createDataSurfaceActionAdapter(options) {
501
543
  } else if (invocation.action.confirmation === "required") return result(request, false, "confirmation_required");
502
544
  else if (invocation.selection.revision !== request.expectedRevision) return result(request, false, "stale_revision");
503
545
  if (invocation.action.execution === "background" && allowBackground) {
504
- if (!options.backgroundQueue || !options.resolveDeferredPrincipal) return result(request, false, "background_unavailable");
546
+ if (!options.backgroundQueue || !validIdentifier(options.backgroundHandlerId) || !options.resolveDeferredPrincipal || !validSigningKey(options.deferredEnvelopeSigningKey)) return result(request, false, "background_unavailable");
547
+ const { confirmationToken: _confirmationToken, ...deferredRequest } = request;
548
+ const unsignedEnvelope = {
549
+ version: 1,
550
+ handlerId: options.backgroundHandlerId ?? "",
551
+ request: deferredRequest,
552
+ principal: deferredPrincipalReference,
553
+ ...token ? { previewToken: token } : {}
554
+ };
505
555
  const queued = await options.backgroundQueue.enqueue({
506
556
  idempotencyKey,
507
557
  identity: request.identity,
508
558
  actionId: request.actionId,
509
559
  rowIds: invocation.selection.rowIds,
510
- run: () => executeBackgroundOnce(request, context, token, deferredPrincipalReference)
560
+ envelope: {
561
+ ...unsignedEnvelope,
562
+ binding: envelopeBinding(unsignedEnvelope, options.deferredEnvelopeSigningKey)
563
+ },
564
+ run: () => executeBackgroundOnce(request, token, deferredPrincipalReference)
511
565
  });
512
566
  return result(request, true, void 0, {
513
567
  accepted: invocation.selection.rowIds.length,
@@ -519,6 +573,7 @@ function createDataSurfaceActionAdapter(options) {
519
573
  jobRequestId: request.requestId
520
574
  });
521
575
  }
576
+ beforeMutation?.();
522
577
  return executeForeground(request, invocation);
523
578
  });
524
579
  }
@@ -554,16 +609,34 @@ function createDataSurfaceActionAdapter(options) {
554
609
  token = await state.getToken(confirmationToken);
555
610
  if (!token || token.expiresAt <= now()) return result(request, false, "invalid_or_expired_confirmation");
556
611
  if (token.actorUserId !== actorUserId || token.tenantId !== tenantId || token.onBehalfOfUserId !== onBehalfOfUserId || token.actsAsProfileId !== actsAsProfileId || token.agentClass !== agentClass || token.identityKey !== identityKey(request.identity) || token.actionId !== request.actionId || token.requestFingerprint !== requestFingerprintValue) return result(request, false, "confirmation_mismatch");
557
- if (!await state.markTokenConsumed(confirmationToken, idempotencyKey)) return result(request, false, "confirmation_replayed");
558
612
  }
613
+ const backgroundPreflight = await runAsPrincipal({
614
+ ...boundContext.principal,
615
+ action: "data_surface.action.apply",
616
+ auditMetadata: boundContext.principal.auditMetadata
617
+ }, async (run) => {
618
+ const surface = await options.resolveSurface(run, request.identity);
619
+ if (identityKey(surface.descriptor.identity) !== identityKey(request.identity)) return void 0;
620
+ const action = surface.actions[request.actionId];
621
+ const declared = surface.descriptor.actions.find(({ id }) => id === request.actionId);
622
+ if (!action || !declared || action.descriptor.id !== declared.id) return void 0;
623
+ if (action.execution === "background" && (!options.backgroundQueue || !validIdentifier(options.backgroundHandlerId) || !options.resolveDeferredPrincipal || !validSigningKey(options.deferredEnvelopeSigningKey))) return result(request, false, "background_unavailable");
624
+ });
625
+ if (backgroundPreflight) return backgroundPreflight;
559
626
  const ownerToken = randomBytes(16).toString("base64url");
627
+ const reservation = {
628
+ requestFingerprint: requestFingerprintValue,
629
+ ownerToken,
630
+ reservedAt: now()
631
+ };
632
+ let firstWinner;
633
+ if (confirmationToken) {
634
+ firstWinner = await state.consumeTokenAndReserveIdempotency(confirmationToken, idempotencyKey, idempotencyScope, reservation);
635
+ if (!firstWinner) return result(request, false, "confirmation_replayed");
636
+ }
560
637
  const maxPolls = Math.max(1, Math.ceil(idempotencyWaitTimeoutMs / idempotencyPollIntervalMs));
561
638
  for (let poll = 0; poll <= maxPolls; poll += 1) {
562
- const winner = await state.reserveIdempotency(idempotencyScope, {
563
- requestFingerprint: requestFingerprintValue,
564
- ownerToken,
565
- reservedAt: now()
566
- });
639
+ const winner = poll === 0 && firstWinner ? firstWinner : await state.reserveIdempotency(idempotencyScope, reservation);
567
640
  if (winner.requestFingerprint !== requestFingerprintValue) return result(request, false, "idempotency_conflict");
568
641
  if (winner.status === "completed") return replayResult(request, winner.result);
569
642
  if (winner.ownerToken === ownerToken) {
@@ -589,12 +662,112 @@ function createDataSurfaceActionAdapter(options) {
589
662
  }
590
663
  return result(request, false, "idempotency_in_progress");
591
664
  }
665
+ async function executeDeferred(envelope) {
666
+ if (!validSigningKey(options.deferredEnvelopeSigningKey) || !bindingMatches(envelope, options.deferredEnvelopeSigningKey)) throw new Error("Invalid durable data-surface action envelope binding");
667
+ const principal = envelope.principal;
668
+ if (envelope.version !== 1 || !options.backgroundHandlerId || envelope.handlerId !== options.backgroundHandlerId || !principal || !validIdentifier(principal.runAsUserId) || principal.tenantId !== null && !validIdentifier(principal.tenantId) || principal.actsAsProfileId !== null && !validIdentifier(principal.actsAsProfileId) || principal.onBehalfOfUserId !== null && !validIdentifier(principal.onBehalfOfUserId) || principal.agentClass !== void 0 && !validIdentifier(principal.agentClass)) return result(envelope.request, false, "invalid_request");
669
+ const invalid = validateRequest(envelope.request, "apply");
670
+ if (invalid) return result(envelope.request, false, invalid);
671
+ return executeBackgroundOnce(snapshotRequest(envelope.request), envelope.previewToken, Object.freeze({ ...envelope.principal }));
672
+ }
592
673
  return {
593
674
  preview,
594
- apply
675
+ apply,
676
+ executeDeferred
595
677
  };
596
678
  }
597
679
  //#endregion
680
+ //#region src/server/jobs-data-surface-action-queue.ts
681
+ var __defProp$1 = Object.defineProperty;
682
+ var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
683
+ var __decorateClass$1 = (decorators, target, key, kind) => {
684
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc$1(target, key) : target;
685
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
686
+ if (kind && result) __defProp$1(target, key, result);
687
+ return result;
688
+ };
689
+ var handlers = /* @__PURE__ */ new Map();
690
+ var SmrtDataSurfaceActionTask = class extends SmrtObject {
691
+ tenantId = null;
692
+ args = {
693
+ version: 1,
694
+ envelope: {}
695
+ };
696
+ async run(args = this.args, context) {
697
+ const envelope = args?.envelope;
698
+ const executionContext = getActiveJobExecutionContext() ?? context;
699
+ if (executionContext && !isRunnerExecutionContext(executionContext)) throw new Error("Invalid durable data-surface action job context");
700
+ let jobTenantId;
701
+ if (executionContext) {
702
+ const runnerTenantId = executionContext.job.tenantId;
703
+ if (runnerTenantId === null) jobTenantId = null;
704
+ else if (typeof runnerTenantId === "string" && runnerTenantId.length > 0) jobTenantId = runnerTenantId;
705
+ else throw new Error("Invalid durable data-surface action job tenant");
706
+ } else jobTenantId = this.tenantId ?? getTenantId() ?? null;
707
+ if (args?.version !== 1 || envelope?.version !== 1 || typeof envelope.handlerId !== "string" || envelope.handlerId.length === 0 || envelope.principal?.tenantId !== jobTenantId) throw new Error("Invalid durable data-surface action envelope");
708
+ const handler = handlers.get(envelope.handlerId);
709
+ if (!handler) throw new Error(`No data-surface action handler registered for ${envelope.handlerId}`);
710
+ const result = await handler(envelope);
711
+ if (!result.ok && result.reason === "idempotency_in_progress") throw new Error("Data-surface action outcome requires reconciliation");
712
+ return result;
713
+ }
714
+ };
715
+ __decorateClass$1([tenantId({ nullable: true })], SmrtDataSurfaceActionTask.prototype, "tenantId", 2);
716
+ __decorateClass$1([field({
717
+ type: "json",
718
+ required: true
719
+ })], SmrtDataSurfaceActionTask.prototype, "args", 2);
720
+ __decorateClass$1([backgroundEligible()], SmrtDataSurfaceActionTask.prototype, "run", 1);
721
+ SmrtDataSurfaceActionTask = __decorateClass$1([TenantScoped({ mode: "optional" }), smrt({
722
+ tableName: "_smrt_data_surface_action_tasks",
723
+ api: false,
724
+ cli: false,
725
+ mcp: false
726
+ })], SmrtDataSurfaceActionTask);
727
+ function registerDataSurfaceBackgroundActionHandler(handlerId, execute) {
728
+ if (!handlerId || handlerId.length > 256) throw new Error("Data-surface action handlerId must contain 1-256 characters");
729
+ const existing = handlers.get(handlerId);
730
+ if (existing && existing !== execute) throw new Error(`Data-surface action handler already registered: ${handlerId}`);
731
+ handlers.set(handlerId, execute);
732
+ return () => {
733
+ if (handlers.get(handlerId) === execute) handlers.delete(handlerId);
734
+ };
735
+ }
736
+ function createJobsDataSurfaceBackgroundQueue(options) {
737
+ return {
738
+ unregister: registerDataSurfaceBackgroundActionHandler(options.handlerId, options.execute),
739
+ async enqueue(job) {
740
+ if (job.envelope.handlerId !== options.handlerId) throw new Error("Data-surface action envelope handler mismatch");
741
+ const persisted = await enqueueDataSurfaceActionJob(options, job.envelope);
742
+ if (!persisted.id) throw new Error("Durable data-surface action job has no ID");
743
+ return {
744
+ jobId: persisted.id,
745
+ details: { queue: persisted.queue }
746
+ };
747
+ }
748
+ };
749
+ }
750
+ async function enqueueDataSurfaceActionJob(options, envelope) {
751
+ await ObjectRegistry.ensureManifestLoaded("SmrtJob");
752
+ const jobs = await SmrtJobCollection.create({ db: options.db });
753
+ const registered = ObjectRegistry.getClassByConstructor(SmrtDataSurfaceActionTask) ?? ObjectRegistry.getClass("SmrtDataSurfaceActionTask");
754
+ const objectType = registered?.qualifiedName ?? registered?.name ?? SmrtDataSurfaceActionTask.name;
755
+ return jobs.enqueueJob({
756
+ tenantId: envelope.principal.tenantId,
757
+ queue: options.queue ?? "data-surface-actions",
758
+ objectType,
759
+ objectId: null,
760
+ method: "run",
761
+ args: {
762
+ version: 1,
763
+ envelope
764
+ },
765
+ priority: options.priority ?? 70,
766
+ timeout: options.timeout ?? 3e5,
767
+ maxAttempts: options.maxAttempts ?? 3
768
+ }, { tenantJobCap: options.tenantJobCap });
769
+ }
770
+ //#endregion
598
771
  //#region src/server/serialization.ts
599
772
  function serializeResolvedAgent(resolved) {
600
773
  const manifest = resolved.manifest;
@@ -614,6 +787,281 @@ function serializeResolvedAgent(resolved) {
614
787
  };
615
788
  }
616
789
  //#endregion
617
- export { InMemoryDataSurfaceActionStateStore, buildRouteMap, createDataSurfaceActionAdapter, extractAgentManifest, extractAgentPackagesFromConfig, isBoundedDataSurfaceJsonValue, loadManifestsFromConfig, loadManifestsFromPackages, loadSlotConfigs, resolveAPIRoute, serializeResolvedAgent };
790
+ //#region src/server/sql-data-surface-action-state.ts
791
+ var __defProp = Object.defineProperty;
792
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
793
+ var __decorateClass = (decorators, target, key, kind) => {
794
+ var result = kind > 1 ? void 0 : kind ? __getOwnPropDesc(target, key) : target;
795
+ for (var i = decorators.length - 1, decorator; i >= 0; i--) if (decorator = decorators[i]) result = (kind ? decorator(target, key, result) : decorator(result)) || result;
796
+ if (kind && result) __defProp(target, key, result);
797
+ return result;
798
+ };
799
+ var TOKEN_TABLE = "_smrt_data_surface_action_tokens";
800
+ var IDEMPOTENCY_TABLE = "_smrt_data_surface_action_idempotency";
801
+ var INTERNAL_SURFACE = {
802
+ api: false,
803
+ cli: false,
804
+ mcp: false
805
+ };
806
+ var DataSurfaceActionTokenState = class extends SmrtObject {
807
+ tokenHash = "";
808
+ record = emptyTokenRecord();
809
+ consumedBy = null;
810
+ };
811
+ __decorateClass([field({
812
+ type: "text",
813
+ required: true,
814
+ unique: true
815
+ })], DataSurfaceActionTokenState.prototype, "tokenHash", 2);
816
+ __decorateClass([field({
817
+ type: "json",
818
+ required: true
819
+ })], DataSurfaceActionTokenState.prototype, "record", 2);
820
+ __decorateClass([field({
821
+ type: "text",
822
+ nullable: true
823
+ })], DataSurfaceActionTokenState.prototype, "consumedBy", 2);
824
+ DataSurfaceActionTokenState = __decorateClass([smrt({
825
+ tableName: "_smrt_data_surface_action_tokens",
826
+ ...INTERNAL_SURFACE
827
+ })], DataSurfaceActionTokenState);
828
+ var DataSurfaceActionIdempotencyState = class extends SmrtObject {
829
+ keyHash = "";
830
+ status = "reserved";
831
+ requestFingerprint = "";
832
+ ownerHash = null;
833
+ reservedAt = null;
834
+ result = null;
835
+ recovery = null;
836
+ };
837
+ __decorateClass([field({
838
+ type: "text",
839
+ required: true,
840
+ unique: true
841
+ })], DataSurfaceActionIdempotencyState.prototype, "keyHash", 2);
842
+ __decorateClass([field({
843
+ type: "text",
844
+ required: true
845
+ })], DataSurfaceActionIdempotencyState.prototype, "status", 2);
846
+ __decorateClass([field({
847
+ type: "text",
848
+ required: true
849
+ })], DataSurfaceActionIdempotencyState.prototype, "requestFingerprint", 2);
850
+ __decorateClass([field({
851
+ type: "text",
852
+ nullable: true
853
+ })], DataSurfaceActionIdempotencyState.prototype, "ownerHash", 2);
854
+ __decorateClass([field({
855
+ type: "text",
856
+ nullable: true
857
+ })], DataSurfaceActionIdempotencyState.prototype, "reservedAt", 2);
858
+ __decorateClass([field({
859
+ type: "json",
860
+ nullable: true
861
+ })], DataSurfaceActionIdempotencyState.prototype, "result", 2);
862
+ __decorateClass([field({
863
+ type: "json",
864
+ nullable: true
865
+ })], DataSurfaceActionIdempotencyState.prototype, "recovery", 2);
866
+ DataSurfaceActionIdempotencyState = __decorateClass([smrt({
867
+ tableName: "_smrt_data_surface_action_idempotency",
868
+ ...INTERNAL_SURFACE
869
+ })], DataSurfaceActionIdempotencyState);
870
+ var DataSurfaceActionStateCorruptionError = class extends Error {
871
+ constructor(table) {
872
+ super(`Malformed durable data-surface action state in ${table}`);
873
+ this.name = "DataSurfaceActionStateCorruptionError";
874
+ }
875
+ };
876
+ var SqlDataSurfaceActionStateStore = class {
877
+ db;
878
+ now;
879
+ authorizeRecovery;
880
+ constructor(options) {
881
+ this.db = options.db;
882
+ this.now = options.now ?? Date.now;
883
+ this.authorizeRecovery = options.authorizeRecovery;
884
+ }
885
+ async putToken(token, record) {
886
+ const timestamp = new Date(this.now()).toISOString();
887
+ await this.db.query(`INSERT INTO ${TOKEN_TABLE}
888
+ (id, slug, context, created_at, updated_at, token_hash, record, consumed_by)
889
+ VALUES (?, ?, '', ?, ?, ?, ?, NULL)
890
+ ON CONFLICT(token_hash) DO NOTHING`, randomUUID(), `action-token-${randomUUID()}`, timestamp, timestamp, secretHash(token), JSON.stringify(record));
891
+ }
892
+ async getToken(token) {
893
+ const row = (await this.db.query(`SELECT record, consumed_by FROM ${TOKEN_TABLE} WHERE token_hash = ? LIMIT 1`, secretHash(token))).rows[0];
894
+ if (!row) return void 0;
895
+ return {
896
+ ...tokenRecord(parseObject(row.record, TOKEN_TABLE)),
897
+ ...typeof row.consumed_by === "string" ? { consumedBy: row.consumed_by } : {}
898
+ };
899
+ }
900
+ async markTokenConsumed(token, idempotencyKey) {
901
+ return (await this.db.query(`UPDATE ${TOKEN_TABLE}
902
+ SET consumed_by = ?, updated_at = ?
903
+ WHERE token_hash = ? AND consumed_by IS NULL
904
+ RETURNING token_hash`, secretHash(idempotencyKey), new Date(this.now()).toISOString(), secretHash(token))).rows.length === 1;
905
+ }
906
+ async consumeTokenAndReserveIdempotency(token, idempotencyKey, scope, reservation) {
907
+ const transaction = this.db.transaction;
908
+ if (!transaction) throw new Error("Durable data-surface action state requires database transactions");
909
+ return await transaction.call(this.db, async (tx) => {
910
+ const timestamp = new Date(this.now()).toISOString();
911
+ if ((await tx.query(`UPDATE ${TOKEN_TABLE}
912
+ SET consumed_by = ?, updated_at = ?
913
+ WHERE token_hash = ?
914
+ AND (consumed_by IS NULL OR consumed_by = ?)
915
+ RETURNING token_hash`, secretHash(idempotencyKey), timestamp, secretHash(token), secretHash(idempotencyKey))).rows.length !== 1) return void 0;
916
+ await tx.query(`INSERT INTO ${IDEMPOTENCY_TABLE}
917
+ (id, slug, context, created_at, updated_at, key_hash, status,
918
+ request_fingerprint, owner_hash, reserved_at, result, recovery)
919
+ VALUES (?, ?, '', ?, ?, ?, 'reserved', ?, ?, ?, NULL, NULL)
920
+ ON CONFLICT(key_hash) DO NOTHING`, randomUUID(), `action-idempotency-${randomUUID()}`, timestamp, timestamp, secretHash(scope), reservation.requestFingerprint, secretHash(reservation.ownerToken), String(reservation.reservedAt));
921
+ const current = await this.getIdempotencyWithOwner(scope, reservation.ownerToken, tx);
922
+ if (!current) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
923
+ return current;
924
+ });
925
+ }
926
+ async getIdempotency(key) {
927
+ const row = (await this.db.query(`SELECT status, request_fingerprint, owner_hash, reserved_at, result, recovery
928
+ FROM ${IDEMPOTENCY_TABLE} WHERE key_hash = ? LIMIT 1`, secretHash(key))).rows[0];
929
+ return row ? idempotencyRecord(row) : void 0;
930
+ }
931
+ async reserveIdempotency(key, reservation) {
932
+ const timestamp = new Date(this.now()).toISOString();
933
+ await this.db.query(`INSERT INTO ${IDEMPOTENCY_TABLE}
934
+ (id, slug, context, created_at, updated_at, key_hash, status,
935
+ request_fingerprint, owner_hash, reserved_at, result, recovery)
936
+ VALUES (?, ?, '', ?, ?, ?, 'reserved', ?, ?, ?, NULL, NULL)
937
+ ON CONFLICT(key_hash) DO NOTHING`, randomUUID(), `action-idempotency-${randomUUID()}`, timestamp, timestamp, secretHash(key), reservation.requestFingerprint, secretHash(reservation.ownerToken), String(reservation.reservedAt));
938
+ const current = await this.getIdempotencyWithOwner(key, reservation.ownerToken);
939
+ if (!current) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
940
+ return current;
941
+ }
942
+ async completeIdempotency(key, ownerToken, result) {
943
+ return (await this.db.query(`UPDATE ${IDEMPOTENCY_TABLE}
944
+ SET status = 'completed', result = ?, owner_hash = NULL,
945
+ reserved_at = NULL, updated_at = ?
946
+ WHERE key_hash = ? AND status = 'reserved' AND owner_hash = ?
947
+ RETURNING key_hash`, JSON.stringify(result), new Date(this.now()).toISOString(), secretHash(key), secretHash(ownerToken))).rows.length === 1;
948
+ }
949
+ async releaseIdempotency(key, ownerToken) {
950
+ return (await this.db.query(`DELETE FROM ${IDEMPOTENCY_TABLE}
951
+ WHERE key_hash = ? AND status = 'reserved' AND owner_hash = ?
952
+ RETURNING key_hash`, secretHash(key), secretHash(ownerToken))).rows.length === 1;
953
+ }
954
+ async reconcileIdempotency(key, request) {
955
+ if (!request.requestFingerprint || !Number.isSafeInteger(request.reservedAt) || request.reservedAt < 0 || !request.authorizedBy || request.authorizedBy.length > 256 || !request.evidence || request.evidence.length > 2048) return false;
956
+ if (!this.authorizeRecovery || !await this.authorizeRecovery(Object.freeze({ ...request }))) return false;
957
+ const recovery = {
958
+ authorizedBy: request.authorizedBy,
959
+ evidence: request.evidence,
960
+ reconciledAt: this.now()
961
+ };
962
+ return (await this.db.query(`UPDATE ${IDEMPOTENCY_TABLE}
963
+ SET status = 'completed', result = ?, recovery = ?, owner_hash = NULL,
964
+ reserved_at = NULL, updated_at = ?
965
+ WHERE key_hash = ? AND status = 'reserved'
966
+ AND request_fingerprint = ? AND reserved_at = ?
967
+ RETURNING key_hash`, JSON.stringify(request.result), JSON.stringify(recovery), new Date(recovery.reconciledAt).toISOString(), secretHash(key), request.requestFingerprint, String(request.reservedAt))).rows.length === 1;
968
+ }
969
+ async getIdempotencyWithOwner(key, ownerToken, db = this.db) {
970
+ const row = (await db.query(`SELECT status, request_fingerprint, owner_hash, reserved_at, result, recovery
971
+ FROM ${IDEMPOTENCY_TABLE} WHERE key_hash = ? LIMIT 1`, secretHash(key))).rows[0];
972
+ if (!row) return void 0;
973
+ const record = idempotencyRecord(row);
974
+ if (record.status === "reserved" && row.owner_hash === secretHash(ownerToken)) return {
975
+ ...record,
976
+ ownerToken
977
+ };
978
+ return record;
979
+ }
980
+ };
981
+ function createSqlDataSurfaceActionStateStore(options) {
982
+ return new SqlDataSurfaceActionStateStore(options);
983
+ }
984
+ function secretHash(value) {
985
+ return createHash("sha256").update(value).digest("hex");
986
+ }
987
+ function parseObject(value, table) {
988
+ let parsed = value;
989
+ if (typeof value === "string") try {
990
+ parsed = JSON.parse(value);
991
+ } catch {
992
+ throw new DataSurfaceActionStateCorruptionError(table);
993
+ }
994
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new DataSurfaceActionStateCorruptionError(table);
995
+ return parsed;
996
+ }
997
+ function idempotencyRecord(row) {
998
+ const requestFingerprint = row.request_fingerprint;
999
+ if (typeof requestFingerprint !== "string") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
1000
+ if (row.status === "reserved") {
1001
+ const reservedAt = Number(row.reserved_at);
1002
+ if (typeof row.owner_hash !== "string" || !Number.isSafeInteger(reservedAt)) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
1003
+ return {
1004
+ status: "reserved",
1005
+ requestFingerprint,
1006
+ ownerToken: "",
1007
+ reservedAt
1008
+ };
1009
+ }
1010
+ if (row.status !== "completed") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
1011
+ const result = actionResult(parseObject(row.result, IDEMPOTENCY_TABLE));
1012
+ const recovery = row.recovery == null ? void 0 : recoveryEvidence(parseObject(row.recovery, IDEMPOTENCY_TABLE));
1013
+ return {
1014
+ status: "completed",
1015
+ requestFingerprint,
1016
+ result,
1017
+ ...recovery ? { recovery } : {}
1018
+ };
1019
+ }
1020
+ function tokenRecord(value) {
1021
+ if (!Number.isSafeInteger(value.expiresAt) || !Number.isSafeInteger(value.revision) || [
1022
+ "actorUserId",
1023
+ "identityKey",
1024
+ "actionId",
1025
+ "actionFingerprint",
1026
+ "queryFingerprint",
1027
+ "selectionFingerprint",
1028
+ "resolvedRowsFingerprint",
1029
+ "requestFingerprint"
1030
+ ].some((key) => typeof value[key] !== "string") || ![
1031
+ "tenantId",
1032
+ "onBehalfOfUserId",
1033
+ "actsAsProfileId",
1034
+ "agentClass"
1035
+ ].every((key) => value[key] === null || typeof value[key] === "string")) throw new DataSurfaceActionStateCorruptionError(TOKEN_TABLE);
1036
+ return value;
1037
+ }
1038
+ function actionResult(value) {
1039
+ if (value.version !== 1 || typeof value.requestId !== "string" || typeof value.actionId !== "string" || value.phase !== "apply" || typeof value.ok !== "boolean" || !value.identity || typeof value.identity !== "object" || Array.isArray(value.identity) || value.reason !== void 0 && typeof value.reason !== "string") throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
1040
+ return value;
1041
+ }
1042
+ function recoveryEvidence(value) {
1043
+ if (typeof value.authorizedBy !== "string" || typeof value.evidence !== "string" || !Number.isSafeInteger(value.reconciledAt)) throw new DataSurfaceActionStateCorruptionError(IDEMPOTENCY_TABLE);
1044
+ return value;
1045
+ }
1046
+ function emptyTokenRecord() {
1047
+ return {
1048
+ expiresAt: 0,
1049
+ actorUserId: "",
1050
+ tenantId: null,
1051
+ onBehalfOfUserId: null,
1052
+ actsAsProfileId: null,
1053
+ agentClass: null,
1054
+ identityKey: "",
1055
+ actionId: "",
1056
+ actionFingerprint: "",
1057
+ revision: 0,
1058
+ queryFingerprint: "",
1059
+ selectionFingerprint: "",
1060
+ resolvedRowsFingerprint: "",
1061
+ requestFingerprint: ""
1062
+ };
1063
+ }
1064
+ //#endregion
1065
+ export { DataSurfaceActionIdempotencyState, DataSurfaceActionStateCorruptionError, DataSurfaceActionTokenState, InMemoryDataSurfaceActionStateStore, SmrtDataSurfaceActionTask, SqlDataSurfaceActionStateStore, buildRouteMap, createDataSurfaceActionAdapter, createJobsDataSurfaceBackgroundQueue, createSqlDataSurfaceActionStateStore, extractAgentManifest, extractAgentPackagesFromConfig, isBoundedDataSurfaceJsonValue, loadManifestsFromConfig, loadManifestsFromPackages, loadSlotConfigs, registerDataSurfaceBackgroundActionHandler, resolveAPIRoute, serializeResolvedAgent };
618
1066
 
619
1067
  //# sourceMappingURL=server.js.map