@fabricorg/platform-host 0.4.3 → 0.5.1
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/CHANGELOG.md +10 -0
- package/README.md +12 -0
- package/dist/index.cjs +261 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +82 -4
- package/dist/index.d.ts +82 -4
- package/dist/index.js +262 -3
- package/dist/index.js.map +1 -1
- package/package.json +66 -62
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# @fabricorg/platform-host
|
|
2
2
|
|
|
3
|
+
## 0.5.1 — 2026-07-20
|
|
4
|
+
|
|
5
|
+
- Include the audit-safe, redacted adapter input in execution-attestation extraction so integrations can identify external resources even when provider responses are minimal.
|
|
6
|
+
|
|
7
|
+
## 0.5.0 — 2026-07-20
|
|
8
|
+
|
|
9
|
+
- Persist resolved mutation footprints and execution-principal delegation.
|
|
10
|
+
- Persist and enforce policy obligations through external execution attestations.
|
|
11
|
+
- Add append-safe external reconciliation evidence to memory and PostgreSQL stores.
|
|
12
|
+
|
|
3
13
|
## 0.4.3 — 2026-07-20
|
|
4
14
|
|
|
5
15
|
- Cast approval-resume leases to `timestamptz` for PostgreSQL and Lakebase SQL adapters.
|
package/README.md
CHANGED
|
@@ -86,6 +86,18 @@ and pass an opaque identifier instead; action parameters are intentionally durab
|
|
|
86
86
|
Hosts should additionally configure `redactActionParameters` as a fail-safe allowlist for actions
|
|
87
87
|
whose ingress is adjacent to secret material; redaction runs before the invocation row is created.
|
|
88
88
|
|
|
89
|
+
## External mutation governance
|
|
90
|
+
|
|
91
|
+
Hosts can configure `resolveMutationGovernance` to persist an approved, provider-neutral
|
|
92
|
+
`MutationFootprint` and `ExecutionPrincipal` after schema validation. `resolvePolicyObligations`
|
|
93
|
+
makes required controls durable. `extractExecutionAttestation` converts successful adapter output
|
|
94
|
+
and the already-redacted adapter input into audit-safe external evidence and can satisfy named obligations. Required obligations that are
|
|
95
|
+
still pending or failed prevent completion.
|
|
96
|
+
|
|
97
|
+
The built-in stores persist footprints, delegation, obligations, attestations, and reconciliation
|
|
98
|
+
observations. Provider-specific authentication and clients do not belong here. For Databricks, use
|
|
99
|
+
the optional Platform integration exported by `@fabric-harness/databricks`.
|
|
100
|
+
|
|
89
101
|
`MemoryPlatformHostStore` is for tests and local demos. Production control planes use
|
|
90
102
|
`PostgresPlatformHostStore` with Databricks Lakebase (or standard Postgres), call
|
|
91
103
|
`ensureSchema()` at startup, and hydrate projections from `listEvents()`.
|
package/dist/index.cjs
CHANGED
|
@@ -129,6 +129,23 @@ function createGovernedActionHost(options) {
|
|
|
129
129
|
const message = parsed.error.issues.map((issue) => `${issue.path.join(".") || "(root)"}: ${issue.message}`).join("; ");
|
|
130
130
|
return fail(invocation, "validation_failed", message);
|
|
131
131
|
}
|
|
132
|
+
if (options.resolveMutationGovernance && !invocation.mutationFootprint) {
|
|
133
|
+
const governanceStore2 = asGovernanceStore(options.store);
|
|
134
|
+
if (!governanceStore2) {
|
|
135
|
+
return fail(invocation, "failed", "The configured mutation-governance resolver requires a governance-capable store.");
|
|
136
|
+
}
|
|
137
|
+
const context = await options.resolveMutationGovernance({
|
|
138
|
+
actionInvocationId,
|
|
139
|
+
actionId: action.actionId,
|
|
140
|
+
tenantId,
|
|
141
|
+
spaceId,
|
|
142
|
+
actorId: invocation.actorId,
|
|
143
|
+
actorType: invocation.actorType,
|
|
144
|
+
parameters: parsed.data
|
|
145
|
+
});
|
|
146
|
+
platform.assertMutationGovernanceContext(context);
|
|
147
|
+
invocation = await governanceStore2.recordMutationGovernance(actionInvocationId, tenantId, spaceId, context);
|
|
148
|
+
}
|
|
132
149
|
if (invocation.actorType === "agent" && options.hitlEvaluator) {
|
|
133
150
|
const hitlParameters = isRecord(parsed.data) ? parsed.data : invocation.parameters;
|
|
134
151
|
const approvalStore = asApprovalStore(options.store);
|
|
@@ -219,6 +236,26 @@ function createGovernedActionHost(options) {
|
|
|
219
236
|
}, `compliance:${aggregate.policyId}`);
|
|
220
237
|
return fail(invocation, "blocked_by_policy", reason);
|
|
221
238
|
}
|
|
239
|
+
if (options.resolvePolicyObligations) {
|
|
240
|
+
const governanceStore2 = asGovernanceStore(options.store);
|
|
241
|
+
if (!governanceStore2) {
|
|
242
|
+
return fail(invocation, "failed", "Policy obligations require a governance-capable store.");
|
|
243
|
+
}
|
|
244
|
+
const existing = await governanceStore2.listPolicyObligations(actionInvocationId, tenantId, spaceId);
|
|
245
|
+
if (existing.length === 0) {
|
|
246
|
+
const createdAt = now();
|
|
247
|
+
const obligations = await options.resolvePolicyObligations(outcomes);
|
|
248
|
+
await governanceStore2.appendPolicyObligations(obligations.map((obligation) => ({
|
|
249
|
+
...obligation,
|
|
250
|
+
status: obligation.status ?? "pending",
|
|
251
|
+
actionInvocationId,
|
|
252
|
+
tenantId,
|
|
253
|
+
spaceId,
|
|
254
|
+
createdAt,
|
|
255
|
+
updatedAt: createdAt
|
|
256
|
+
})));
|
|
257
|
+
}
|
|
258
|
+
}
|
|
222
259
|
const binding = action.stateMachine;
|
|
223
260
|
if (binding) {
|
|
224
261
|
const entityId = binding.getEntityId(parsed.data);
|
|
@@ -374,6 +411,22 @@ function createGovernedActionHost(options) {
|
|
|
374
411
|
output,
|
|
375
412
|
updatedAt: now()
|
|
376
413
|
});
|
|
414
|
+
if (options.extractExecutionAttestation) {
|
|
415
|
+
const attestation = await options.extractExecutionAttestation({
|
|
416
|
+
actionInvocationId,
|
|
417
|
+
adapterInvocationId,
|
|
418
|
+
adapterType: step.adapterType,
|
|
419
|
+
operation: step.operation,
|
|
420
|
+
vendor: adapter.vendor,
|
|
421
|
+
input: recordedInput,
|
|
422
|
+
startedAt,
|
|
423
|
+
completedAt: now(),
|
|
424
|
+
output
|
|
425
|
+
});
|
|
426
|
+
if (attestation) {
|
|
427
|
+
await recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId);
|
|
428
|
+
}
|
|
429
|
+
}
|
|
377
430
|
await appendEvent(invocation, {
|
|
378
431
|
eventType: "AdapterInvocationSucceeded",
|
|
379
432
|
subjectType: adapterEventSubject.subjectType,
|
|
@@ -400,6 +453,14 @@ function createGovernedActionHost(options) {
|
|
|
400
453
|
await appendEvent(invocation, event, `domain:${index}`, action.version);
|
|
401
454
|
}
|
|
402
455
|
}
|
|
456
|
+
const governanceStore = asGovernanceStore(options.store);
|
|
457
|
+
if (governanceStore && options.resolvePolicyObligations) {
|
|
458
|
+
const obligations = await governanceStore.listPolicyObligations(actionInvocationId, tenantId, spaceId);
|
|
459
|
+
const unsatisfied = obligations.filter((obligation) => (obligation.required ?? true) && obligation.status !== "satisfied" && obligation.status !== "waived");
|
|
460
|
+
if (unsatisfied.length > 0) {
|
|
461
|
+
return fail(invocation, "failed", `Unsatisfied policy obligations: ${unsatisfied.map((item) => item.id).join(", ")}`);
|
|
462
|
+
}
|
|
463
|
+
}
|
|
403
464
|
const result = withoutPrivateHostFields(data, eventResultFields);
|
|
404
465
|
await options.store.updateActionInvocation(actionInvocationId, tenantId, spaceId, {
|
|
405
466
|
status: "completed",
|
|
@@ -479,6 +540,38 @@ function createGovernedActionHost(options) {
|
|
|
479
540
|
if (!decision.approved) return actionResult(transitioned);
|
|
480
541
|
return executeInvocation(actionInvocationId, tenantId, spaceId, { leaseOwner });
|
|
481
542
|
}
|
|
543
|
+
async function recordExecutionAttestation(actionInvocationId, tenantId, spaceId, attestation, adapterInvocationId) {
|
|
544
|
+
const governanceStore = asGovernanceStore(options.store);
|
|
545
|
+
if (!governanceStore) throw new Error("Execution attestations require a governance-capable store.");
|
|
546
|
+
const invocation = await options.store.getActionInvocation(actionInvocationId, tenantId, spaceId);
|
|
547
|
+
if (!invocation) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
548
|
+
const id = lifecycleId("att", actionInvocationId, adapterInvocationId ?? `${attestation.provider}:${attestation.operation}:${attestation.externalOperationId ?? "external"}`);
|
|
549
|
+
await governanceStore.appendExecutionAttestation({
|
|
550
|
+
...attestation,
|
|
551
|
+
id,
|
|
552
|
+
actionInvocationId,
|
|
553
|
+
tenantId,
|
|
554
|
+
spaceId,
|
|
555
|
+
...adapterInvocationId ? { adapterInvocationId } : {},
|
|
556
|
+
recordedAt: now()
|
|
557
|
+
});
|
|
558
|
+
for (const obligationId of attestation.satisfiesObligations ?? []) {
|
|
559
|
+
await governanceStore.updatePolicyObligation(actionInvocationId, obligationId, "satisfied", attestation.evidenceReferences);
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
async function recordExternalReconciliation(actionInvocationId, tenantId, spaceId, reconciliation) {
|
|
563
|
+
const governanceStore = asGovernanceStore(options.store);
|
|
564
|
+
if (!governanceStore) throw new Error("External reconciliation requires a governance-capable store.");
|
|
565
|
+
const invocation = await options.store.getActionInvocation(actionInvocationId, tenantId, spaceId);
|
|
566
|
+
if (!invocation) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
567
|
+
await governanceStore.appendExternalReconciliation({
|
|
568
|
+
...reconciliation,
|
|
569
|
+
id: lifecycleId("rec", actionInvocationId, `${reconciliation.provider}:${reconciliation.attempt}`),
|
|
570
|
+
actionInvocationId,
|
|
571
|
+
tenantId,
|
|
572
|
+
spaceId
|
|
573
|
+
});
|
|
574
|
+
}
|
|
482
575
|
async function appendEvent(invocation, event, deduplicationKey, defaultEventSchemaVersion = 1) {
|
|
483
576
|
const timestamp = now();
|
|
484
577
|
const envelope = {
|
|
@@ -519,7 +612,7 @@ function createGovernedActionHost(options) {
|
|
|
519
612
|
...invocation.hitlRiskTier ? { hitlRiskTier: invocation.hitlRiskTier } : {}
|
|
520
613
|
};
|
|
521
614
|
}
|
|
522
|
-
return { submitAction, executeInvocation, resumeApprovedInvocation };
|
|
615
|
+
return { submitAction, executeInvocation, resumeApprovedInvocation, recordExecutionAttestation, recordExternalReconciliation };
|
|
523
616
|
}
|
|
524
617
|
function actionResult(invocation) {
|
|
525
618
|
return {
|
|
@@ -535,6 +628,10 @@ function asApprovalStore(store) {
|
|
|
535
628
|
const candidate = store;
|
|
536
629
|
return typeof candidate.recordHitlDecision === "function" && typeof candidate.beginApprovalDecision === "function" ? store : void 0;
|
|
537
630
|
}
|
|
631
|
+
function asGovernanceStore(store) {
|
|
632
|
+
const candidate = store;
|
|
633
|
+
return typeof candidate.recordMutationGovernance === "function" && typeof candidate.appendPolicyObligations === "function" && typeof candidate.appendExecutionAttestation === "function" ? store : void 0;
|
|
634
|
+
}
|
|
538
635
|
function numberValue(value) {
|
|
539
636
|
return typeof value === "number" && Number.isFinite(value) ? value : void 0;
|
|
540
637
|
}
|
|
@@ -597,6 +694,9 @@ var MemoryPlatformHostStore = class {
|
|
|
597
694
|
policyEvaluations = [];
|
|
598
695
|
adapterInvocations = [];
|
|
599
696
|
events = [];
|
|
697
|
+
policyObligations = [];
|
|
698
|
+
executionAttestations = [];
|
|
699
|
+
externalReconciliations = [];
|
|
600
700
|
async transaction(run) {
|
|
601
701
|
return run(this.db);
|
|
602
702
|
}
|
|
@@ -665,6 +765,40 @@ var MemoryPlatformHostStore = class {
|
|
|
665
765
|
if (this.policyEvaluations.some((candidate) => candidate.id === record.id)) return;
|
|
666
766
|
this.policyEvaluations.push(record);
|
|
667
767
|
}
|
|
768
|
+
async recordMutationGovernance(actionInvocationId, tenantId, spaceId, context) {
|
|
769
|
+
const record = await this.getActionInvocation(actionInvocationId, tenantId, spaceId);
|
|
770
|
+
if (!record) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
771
|
+
record.mutationFootprint = context.footprint;
|
|
772
|
+
record.executionPrincipal = context.executionPrincipal;
|
|
773
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
774
|
+
return record;
|
|
775
|
+
}
|
|
776
|
+
async appendPolicyObligations(records) {
|
|
777
|
+
for (const record of records) {
|
|
778
|
+
if (!this.policyObligations.some((candidate) => candidate.actionInvocationId === record.actionInvocationId && candidate.id === record.id)) {
|
|
779
|
+
this.policyObligations.push(record);
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
async listPolicyObligations(actionInvocationId, tenantId, spaceId) {
|
|
784
|
+
return this.policyObligations.filter((record) => record.actionInvocationId === actionInvocationId && record.tenantId === tenantId && record.spaceId === spaceId);
|
|
785
|
+
}
|
|
786
|
+
async updatePolicyObligation(actionInvocationId, obligationId, status, evidenceReferences) {
|
|
787
|
+
const record = this.policyObligations.find((candidate) => candidate.actionInvocationId === actionInvocationId && candidate.id === obligationId);
|
|
788
|
+
if (!record) throw new Error(`Policy obligation not found: ${obligationId}`);
|
|
789
|
+
record.status = status;
|
|
790
|
+
if (evidenceReferences) record.evidenceReferences = evidenceReferences;
|
|
791
|
+
record.updatedAt = /* @__PURE__ */ new Date();
|
|
792
|
+
}
|
|
793
|
+
async appendExecutionAttestation(record) {
|
|
794
|
+
if (!this.executionAttestations.some((candidate) => candidate.id === record.id)) this.executionAttestations.push(record);
|
|
795
|
+
}
|
|
796
|
+
async listExecutionAttestations(actionInvocationId, tenantId, spaceId) {
|
|
797
|
+
return this.executionAttestations.filter((record) => record.actionInvocationId === actionInvocationId && record.tenantId === tenantId && record.spaceId === spaceId);
|
|
798
|
+
}
|
|
799
|
+
async appendExternalReconciliation(record) {
|
|
800
|
+
if (!this.externalReconciliations.some((candidate) => candidate.id === record.id)) this.externalReconciliations.push(record);
|
|
801
|
+
}
|
|
668
802
|
async createAdapterInvocation(record) {
|
|
669
803
|
if (this.adapterInvocations.some((candidate) => candidate.id === record.id)) return;
|
|
670
804
|
this.adapterInvocations.push(record);
|
|
@@ -739,6 +873,7 @@ var PostgresPlatformHostStore = class {
|
|
|
739
873
|
attempt_count integer NOT NULL DEFAULT 0, lease_owner text,
|
|
740
874
|
lease_expires_at timestamptz, hitl_route text, hitl_risk_tier text,
|
|
741
875
|
hitl_reason text, hitl_policy_version text, approval_decision jsonb,
|
|
876
|
+
mutation_footprint jsonb, execution_principal jsonb,
|
|
742
877
|
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
|
|
743
878
|
);
|
|
744
879
|
ALTER TABLE fabric_platform.action_invocations
|
|
@@ -750,7 +885,9 @@ var PostgresPlatformHostStore = class {
|
|
|
750
885
|
ADD COLUMN IF NOT EXISTS hitl_risk_tier text,
|
|
751
886
|
ADD COLUMN IF NOT EXISTS hitl_reason text,
|
|
752
887
|
ADD COLUMN IF NOT EXISTS hitl_policy_version text,
|
|
753
|
-
ADD COLUMN IF NOT EXISTS approval_decision jsonb
|
|
888
|
+
ADD COLUMN IF NOT EXISTS approval_decision jsonb,
|
|
889
|
+
ADD COLUMN IF NOT EXISTS mutation_footprint jsonb,
|
|
890
|
+
ADD COLUMN IF NOT EXISTS execution_principal jsonb;
|
|
754
891
|
CREATE UNIQUE INDEX IF NOT EXISTS action_invocations_idempotency_idx
|
|
755
892
|
ON fabric_platform.action_invocations
|
|
756
893
|
(tenant_id, space_id, action_id, idempotency_key)
|
|
@@ -769,6 +906,24 @@ var PostgresPlatformHostStore = class {
|
|
|
769
906
|
input jsonb NOT NULL, output jsonb, error text, attempt integer NOT NULL,
|
|
770
907
|
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL
|
|
771
908
|
);
|
|
909
|
+
CREATE TABLE IF NOT EXISTS fabric_platform.policy_obligations (
|
|
910
|
+
action_invocation_id text NOT NULL, id text NOT NULL,
|
|
911
|
+
tenant_id text NOT NULL, space_id text NOT NULL, type text NOT NULL,
|
|
912
|
+
status text NOT NULL, required boolean NOT NULL DEFAULT true,
|
|
913
|
+
description text, evidence_references jsonb, metadata jsonb,
|
|
914
|
+
created_at timestamptz NOT NULL, updated_at timestamptz NOT NULL,
|
|
915
|
+
PRIMARY KEY (action_invocation_id,id)
|
|
916
|
+
);
|
|
917
|
+
CREATE TABLE IF NOT EXISTS fabric_platform.execution_attestations (
|
|
918
|
+
id text PRIMARY KEY, action_invocation_id text NOT NULL,
|
|
919
|
+
tenant_id text NOT NULL, space_id text NOT NULL, adapter_invocation_id text,
|
|
920
|
+
attestation jsonb NOT NULL, recorded_at timestamptz NOT NULL
|
|
921
|
+
);
|
|
922
|
+
CREATE TABLE IF NOT EXISTS fabric_platform.external_reconciliations (
|
|
923
|
+
id text PRIMARY KEY, action_invocation_id text NOT NULL,
|
|
924
|
+
tenant_id text NOT NULL, space_id text NOT NULL,
|
|
925
|
+
reconciliation jsonb NOT NULL
|
|
926
|
+
);
|
|
772
927
|
CREATE TABLE IF NOT EXISTS fabric_platform.event_sequences (
|
|
773
928
|
tenant_id text NOT NULL, space_id text NOT NULL, next_sequence bigint NOT NULL,
|
|
774
929
|
PRIMARY KEY (tenant_id, space_id)
|
|
@@ -914,6 +1069,80 @@ var PostgresPlatformHostStore = class {
|
|
|
914
1069
|
]
|
|
915
1070
|
);
|
|
916
1071
|
}
|
|
1072
|
+
async recordMutationGovernance(actionInvocationId, tenantId, spaceId, context) {
|
|
1073
|
+
const result = await this.sql.query(
|
|
1074
|
+
`UPDATE fabric_platform.action_invocations SET mutation_footprint=$4::jsonb,
|
|
1075
|
+
execution_principal=$5::jsonb, updated_at=now()
|
|
1076
|
+
WHERE id=$1 AND tenant_id=$2 AND space_id=$3 RETURNING *`,
|
|
1077
|
+
[actionInvocationId, tenantId, spaceId, JSON.stringify(context.footprint), JSON.stringify(context.executionPrincipal)]
|
|
1078
|
+
);
|
|
1079
|
+
if (!result.rows[0]) throw new Error(`ActionInvocation not found: ${actionInvocationId}`);
|
|
1080
|
+
return toActionRecord(result.rows[0]);
|
|
1081
|
+
}
|
|
1082
|
+
async appendPolicyObligations(records) {
|
|
1083
|
+
for (const record of records) {
|
|
1084
|
+
await this.sql.query(
|
|
1085
|
+
`INSERT INTO fabric_platform.policy_obligations
|
|
1086
|
+
(action_invocation_id,id,tenant_id,space_id,type,status,required,description,evidence_references,metadata,created_at,updated_at)
|
|
1087
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9::jsonb,$10::jsonb,$11,$12)
|
|
1088
|
+
ON CONFLICT (action_invocation_id,id) DO NOTHING`,
|
|
1089
|
+
[
|
|
1090
|
+
record.actionInvocationId,
|
|
1091
|
+
record.id,
|
|
1092
|
+
record.tenantId,
|
|
1093
|
+
record.spaceId,
|
|
1094
|
+
record.type,
|
|
1095
|
+
record.status,
|
|
1096
|
+
record.required ?? true,
|
|
1097
|
+
record.description ?? null,
|
|
1098
|
+
record.evidenceReferences ? JSON.stringify(record.evidenceReferences) : null,
|
|
1099
|
+
record.metadata ? JSON.stringify(record.metadata) : null,
|
|
1100
|
+
record.createdAt,
|
|
1101
|
+
record.updatedAt
|
|
1102
|
+
]
|
|
1103
|
+
);
|
|
1104
|
+
}
|
|
1105
|
+
}
|
|
1106
|
+
async listPolicyObligations(actionInvocationId, tenantId, spaceId) {
|
|
1107
|
+
const result = await this.sql.query(
|
|
1108
|
+
`SELECT * FROM fabric_platform.policy_obligations WHERE action_invocation_id=$1 AND tenant_id=$2 AND space_id=$3 ORDER BY created_at,id`,
|
|
1109
|
+
[actionInvocationId, tenantId, spaceId]
|
|
1110
|
+
);
|
|
1111
|
+
return result.rows.map(toObligationRecord);
|
|
1112
|
+
}
|
|
1113
|
+
async updatePolicyObligation(actionInvocationId, obligationId, status, evidenceReferences) {
|
|
1114
|
+
await this.sql.query(
|
|
1115
|
+
`UPDATE fabric_platform.policy_obligations SET status=$3,
|
|
1116
|
+
evidence_references=COALESCE($4::jsonb,evidence_references), updated_at=now()
|
|
1117
|
+
WHERE action_invocation_id=$1 AND id=$2`,
|
|
1118
|
+
[actionInvocationId, obligationId, status, evidenceReferences ? JSON.stringify(evidenceReferences) : null]
|
|
1119
|
+
);
|
|
1120
|
+
}
|
|
1121
|
+
async appendExecutionAttestation(record) {
|
|
1122
|
+
const { id, actionInvocationId, tenantId, spaceId, adapterInvocationId, recordedAt, ...attestation } = record;
|
|
1123
|
+
await this.sql.query(
|
|
1124
|
+
`INSERT INTO fabric_platform.execution_attestations
|
|
1125
|
+
(id,action_invocation_id,tenant_id,space_id,adapter_invocation_id,attestation,recorded_at)
|
|
1126
|
+
VALUES ($1,$2,$3,$4,$5,$6::jsonb,$7) ON CONFLICT (id) DO NOTHING`,
|
|
1127
|
+
[id, actionInvocationId, tenantId, spaceId, adapterInvocationId ?? null, JSON.stringify(attestation), recordedAt]
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
async listExecutionAttestations(actionInvocationId, tenantId, spaceId) {
|
|
1131
|
+
const result = await this.sql.query(
|
|
1132
|
+
`SELECT * FROM fabric_platform.execution_attestations WHERE action_invocation_id=$1 AND tenant_id=$2 AND space_id=$3 ORDER BY recorded_at,id`,
|
|
1133
|
+
[actionInvocationId, tenantId, spaceId]
|
|
1134
|
+
);
|
|
1135
|
+
return result.rows.map(toAttestationRecord);
|
|
1136
|
+
}
|
|
1137
|
+
async appendExternalReconciliation(record) {
|
|
1138
|
+
const { id, actionInvocationId, tenantId, spaceId, ...reconciliation } = record;
|
|
1139
|
+
await this.sql.query(
|
|
1140
|
+
`INSERT INTO fabric_platform.external_reconciliations
|
|
1141
|
+
(id,action_invocation_id,tenant_id,space_id,reconciliation)
|
|
1142
|
+
VALUES ($1,$2,$3,$4,$5::jsonb) ON CONFLICT (id) DO NOTHING`,
|
|
1143
|
+
[id, actionInvocationId, tenantId, spaceId, JSON.stringify(reconciliation)]
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
917
1146
|
async createAdapterInvocation(record) {
|
|
918
1147
|
await this.sql.query(
|
|
919
1148
|
`INSERT INTO fabric_platform.adapter_invocations
|
|
@@ -1111,11 +1340,41 @@ function toActionRecord(row) {
|
|
|
1111
1340
|
...row.hitl_reason ? { hitlReason: String(row.hitl_reason) } : {},
|
|
1112
1341
|
...row.hitl_policy_version ? { hitlPolicyVersion: String(row.hitl_policy_version) } : {},
|
|
1113
1342
|
...row.approval_decision ? { approvalDecision: toApprovalDecision(row.approval_decision) } : {},
|
|
1343
|
+
...row.mutation_footprint ? { mutationFootprint: row.mutation_footprint } : {},
|
|
1344
|
+
...row.execution_principal ? { executionPrincipal: row.execution_principal } : {},
|
|
1114
1345
|
...row.error ? { error: String(row.error) } : {},
|
|
1115
1346
|
createdAt: new Date(row.created_at),
|
|
1116
1347
|
updatedAt: new Date(row.updated_at)
|
|
1117
1348
|
};
|
|
1118
1349
|
}
|
|
1350
|
+
function toObligationRecord(row) {
|
|
1351
|
+
return {
|
|
1352
|
+
id: String(row.id),
|
|
1353
|
+
actionInvocationId: String(row.action_invocation_id),
|
|
1354
|
+
tenantId: String(row.tenant_id),
|
|
1355
|
+
spaceId: String(row.space_id),
|
|
1356
|
+
type: String(row.type),
|
|
1357
|
+
status: String(row.status),
|
|
1358
|
+
required: Boolean(row.required),
|
|
1359
|
+
...row.description ? { description: String(row.description) } : {},
|
|
1360
|
+
...row.evidence_references ? { evidenceReferences: row.evidence_references } : {},
|
|
1361
|
+
...row.metadata ? { metadata: row.metadata } : {},
|
|
1362
|
+
createdAt: new Date(row.created_at),
|
|
1363
|
+
updatedAt: new Date(row.updated_at)
|
|
1364
|
+
};
|
|
1365
|
+
}
|
|
1366
|
+
function toAttestationRecord(row) {
|
|
1367
|
+
const attestation = row.attestation;
|
|
1368
|
+
return {
|
|
1369
|
+
...attestation,
|
|
1370
|
+
id: String(row.id),
|
|
1371
|
+
actionInvocationId: String(row.action_invocation_id),
|
|
1372
|
+
tenantId: String(row.tenant_id),
|
|
1373
|
+
spaceId: String(row.space_id),
|
|
1374
|
+
...row.adapter_invocation_id ? { adapterInvocationId: String(row.adapter_invocation_id) } : {},
|
|
1375
|
+
recordedAt: new Date(row.recorded_at)
|
|
1376
|
+
};
|
|
1377
|
+
}
|
|
1119
1378
|
function toApprovalDecision(value) {
|
|
1120
1379
|
const decision = value;
|
|
1121
1380
|
return {
|