settld 0.1.5 → 0.2.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.
Files changed (87) hide show
  1. package/README.md +32 -0
  2. package/SETTLD_VERSION +1 -1
  3. package/bin/settld.js +63 -0
  4. package/docs/CIRCLE_SANDBOX_E2E.md +12 -0
  5. package/docs/QUICKSTART_MCP.md +41 -1
  6. package/docs/QUICKSTART_MCP_HOSTS.md +172 -89
  7. package/docs/QUICKSTART_POLICY_PACKS.md +65 -0
  8. package/docs/QUICKSTART_PROFILES.md +198 -0
  9. package/docs/README.md +18 -0
  10. package/docs/RELEASE_CHECKLIST.md +26 -0
  11. package/docs/RELEASING.md +1 -0
  12. package/docs/SLO.md +62 -1
  13. package/docs/SUMMARY.md +1 -0
  14. package/docs/gitbook/README.md +13 -1
  15. package/docs/gitbook/quickstart.md +57 -58
  16. package/docs/integrations/README.md +1 -0
  17. package/docs/integrations/openclaw/PUBLIC_QUICKSTART.md +108 -0
  18. package/docs/ops/DISPUTE_FINANCE_RECONCILIATION_PACKET.md +56 -0
  19. package/docs/ops/KERNEL_V0_SHIP_GATE.md +3 -1
  20. package/docs/ops/MCP_COMPATIBILITY_MATRIX.md +8 -6
  21. package/docs/ops/PRODUCTION_DEPLOYMENT_CHECKLIST.md +46 -9
  22. package/docs/ops/TRUST_CONFIG_WIZARD.md +37 -24
  23. package/docs/ops/VERCEL_MONOREPO_DEPLOY.md +42 -0
  24. package/docs/plans/2026-02-20-trust-os-v1-jira-backlog.md +348 -0
  25. package/docs/plans/2026-02-21-agent-economic-actor-operating-model.md +169 -0
  26. package/docs/plans/2026-02-21-trust-os-v1-strategy.md +241 -0
  27. package/docs/research/2026-02-21-agent-spend-host-landscape.md +57 -0
  28. package/docs/spec/ArbitrationOutcomeMapping.v1.md +62 -0
  29. package/docs/spec/DisputeCaseLifecycle.v1.md +51 -0
  30. package/docs/spec/OperatorAction.v1.md +90 -0
  31. package/docs/spec/PolicyDecision.v1.md +83 -0
  32. package/docs/spec/README.md +5 -0
  33. package/docs/spec/SettlementDecisionRecord.v2.md +2 -0
  34. package/docs/spec/schemas/OperatorAction.v1.schema.json +113 -0
  35. package/docs/spec/schemas/PolicyDecision.v1.schema.json +74 -0
  36. package/docs/spec/schemas/SettlementDecisionRecord.v2.schema.json +1 -0
  37. package/docs/spec/x402-error-codes.v1.txt +14 -0
  38. package/package.json +14 -1
  39. package/scripts/ci/build-launch-cutover-packet.mjs +177 -21
  40. package/scripts/ci/run-10x-throughput-drill.mjs +76 -4
  41. package/scripts/ci/run-10x-throughput-incident-rehearsal.mjs +49 -6
  42. package/scripts/ci/run-mcp-host-cert-matrix.mjs +201 -0
  43. package/scripts/ci/run-mcp-host-smoke.mjs +203 -5
  44. package/scripts/ci/run-offline-verification-parity-gate.mjs +762 -0
  45. package/scripts/ci/run-onboarding-host-success-gate.mjs +516 -0
  46. package/scripts/ci/run-onboarding-policy-slo-gate.mjs +537 -0
  47. package/scripts/ci/run-production-cutover-gate.mjs +540 -0
  48. package/scripts/ci/run-public-openclaw-npx-smoke.mjs +148 -0
  49. package/scripts/ci/run-release-promotion-guard.mjs +756 -0
  50. package/scripts/doctor/mcp-host.mjs +120 -0
  51. package/scripts/mcp/settld-mcp-server.mjs +330 -20
  52. package/scripts/ops/dispute-finance-reconciliation-packet.mjs +313 -0
  53. package/scripts/ops/hosted-baseline-evidence.mjs +286 -77
  54. package/scripts/ops/run-x402-hitl-smoke.mjs +607 -0
  55. package/scripts/policy/cli.mjs +600 -0
  56. package/scripts/profile/cli.mjs +1324 -0
  57. package/scripts/register-entity-secret.mjs +102 -0
  58. package/scripts/setup/circle-bootstrap.mjs +310 -0
  59. package/scripts/setup/host-config.mjs +617 -0
  60. package/scripts/setup/login.mjs +299 -0
  61. package/scripts/setup/onboard.mjs +1578 -0
  62. package/scripts/setup/openclaw-onboard.mjs +423 -0
  63. package/scripts/setup/session-store.mjs +65 -0
  64. package/scripts/setup/wizard.mjs +986 -0
  65. package/scripts/slo/check.mjs +123 -62
  66. package/scripts/spec/generate-protocol-vectors.mjs +88 -0
  67. package/scripts/test/run.sh +23 -9
  68. package/scripts/vercel/ignore-dashboard.sh +23 -0
  69. package/scripts/vercel/ignore-mkdocs.sh +2 -0
  70. package/services/x402-gateway/src/server.js +147 -36
  71. package/src/api/app.js +2345 -267
  72. package/src/api/middleware/trust-kernel.js +114 -0
  73. package/src/api/openapi.js +598 -3
  74. package/src/api/persistence.js +184 -0
  75. package/src/api/store.js +277 -0
  76. package/src/core/agent-wallets.js +134 -0
  77. package/src/core/event-policy.js +21 -2
  78. package/src/core/operator-action.js +303 -0
  79. package/src/core/policy-decision.js +322 -0
  80. package/src/core/policy-packs.js +207 -0
  81. package/src/core/profile-fingerprint.js +27 -0
  82. package/src/core/profile-simulation-reasons.js +84 -0
  83. package/src/core/profile-templates.js +242 -0
  84. package/src/core/settlement-kernel.js +27 -1
  85. package/src/core/wallet-assignment-resolver.js +129 -0
  86. package/src/core/wallet-provider-bootstrap.js +365 -0
  87. package/src/db/store-pg.js +631 -0
@@ -1,33 +1,43 @@
1
1
  import assert from "node:assert/strict";
2
2
  import fs from "node:fs/promises";
3
3
 
4
- const API_BASE_URL = process.env.SLO_API_BASE_URL ?? "http://127.0.0.1:3000";
5
- const METRICS_PATH = process.env.SLO_METRICS_PATH ?? "/metrics";
6
- const METRICS_FILE = process.env.SLO_METRICS_FILE ?? null;
4
+ const SLO_CHECK_SCHEMA_VERSION = "OperationalSloCheck.v1";
7
5
 
8
- const MAX_HTTP_5XX_TOTAL = Number(process.env.SLO_MAX_HTTP_5XX_TOTAL ?? "0");
9
- const MAX_OUTBOX_PENDING = Number(process.env.SLO_MAX_OUTBOX_PENDING ?? "200");
10
- const MAX_DELIVERY_DLQ = Number(process.env.SLO_MAX_DELIVERY_DLQ ?? "0");
11
- const MAX_DELIVERIES_PENDING = Number(process.env.SLO_MAX_DELIVERIES_PENDING ?? "0");
12
- const MAX_DELIVERIES_FAILED = Number(process.env.SLO_MAX_DELIVERIES_FAILED ?? "0");
6
+ function normalizeOptionalString(value) {
7
+ if (typeof value !== "string") return null;
8
+ const trimmed = value.trim();
9
+ return trimmed === "" ? null : trimmed;
10
+ }
13
11
 
14
- function assertFiniteNumber(n, name) {
12
+ export function assertFiniteNumber(n, name) {
15
13
  if (!Number.isFinite(n)) throw new TypeError(`${name} must be finite`);
16
14
  }
17
15
 
18
- for (const [k, v] of [
19
- ["SLO_MAX_HTTP_5XX_TOTAL", MAX_HTTP_5XX_TOTAL],
20
- ["SLO_MAX_OUTBOX_PENDING", MAX_OUTBOX_PENDING],
21
- ["SLO_MAX_DELIVERY_DLQ", MAX_DELIVERY_DLQ],
22
- ["SLO_MAX_DELIVERIES_PENDING", MAX_DELIVERIES_PENDING],
23
- ["SLO_MAX_DELIVERIES_FAILED", MAX_DELIVERIES_FAILED]
24
- ]) {
25
- assertFiniteNumber(v, k);
26
- if (v < 0) throw new TypeError(`${k} must be >= 0`);
16
+ export function parseSloThresholds(env = process.env) {
17
+ return {
18
+ maxHttp5xxTotal: Number(env.SLO_MAX_HTTP_5XX_TOTAL ?? "0"),
19
+ maxOutboxPending: Number(env.SLO_MAX_OUTBOX_PENDING ?? "200"),
20
+ maxDeliveryDlq: Number(env.SLO_MAX_DELIVERY_DLQ ?? "0"),
21
+ maxDeliveriesPending: Number(env.SLO_MAX_DELIVERIES_PENDING ?? "0"),
22
+ maxDeliveriesFailed: Number(env.SLO_MAX_DELIVERIES_FAILED ?? "0")
23
+ };
24
+ }
25
+
26
+ export function validateSloThresholds(thresholds) {
27
+ for (const [k, v] of [
28
+ ["SLO_MAX_HTTP_5XX_TOTAL", thresholds.maxHttp5xxTotal],
29
+ ["SLO_MAX_OUTBOX_PENDING", thresholds.maxOutboxPending],
30
+ ["SLO_MAX_DELIVERY_DLQ", thresholds.maxDeliveryDlq],
31
+ ["SLO_MAX_DELIVERIES_PENDING", thresholds.maxDeliveriesPending],
32
+ ["SLO_MAX_DELIVERIES_FAILED", thresholds.maxDeliveriesFailed]
33
+ ]) {
34
+ assertFiniteNumber(v, k);
35
+ if (v < 0) throw new TypeError(`${k} must be >= 0`);
36
+ }
27
37
  }
28
38
 
29
39
  function sleep(ms) {
30
- return new Promise((r) => setTimeout(r, ms));
40
+ return new Promise((resolve) => setTimeout(resolve, ms));
31
41
  }
32
42
 
33
43
  async function fetchTextWithTimeout(url, timeoutMs = 5000) {
@@ -43,7 +53,6 @@ async function fetchTextWithTimeout(url, timeoutMs = 5000) {
43
53
  }
44
54
 
45
55
  function unescapeLabelValue(value) {
46
- // Prometheus exposition escaping.
47
56
  return String(value).replaceAll("\\\\", "\\").replaceAll("\\n", "\n").replaceAll('\\"', '"');
48
57
  }
49
58
 
@@ -94,13 +103,12 @@ function parseLabels(src) {
94
103
  return labels;
95
104
  }
96
105
 
97
- function parsePrometheusText(text) {
106
+ export function parsePrometheusText(text) {
98
107
  const series = [];
99
108
  const lines = String(text ?? "").split("\n");
100
109
  for (const lineRaw of lines) {
101
110
  const line = lineRaw.trim();
102
111
  if (!line || line.startsWith("#")) continue;
103
- // name{labels} value
104
112
  const m = /^([a-zA-Z_:][a-zA-Z0-9_:]*)(\{[^}]*\})?\s+([-+]?(\d+(\.\d*)?|\.\d+)([eE][-+]?\d+)?|NaN|Inf|-Inf)\s*$/.exec(line);
105
113
  if (!m) continue;
106
114
  const name = m[1];
@@ -112,67 +120,120 @@ function parsePrometheusText(text) {
112
120
  return series;
113
121
  }
114
122
 
115
- function sumWhere(series, { name, where = () => true } = {}) {
123
+ export function sumWhere(series, { name, where = () => true } = {}) {
116
124
  let sum = 0;
117
- for (const s of series) {
118
- if (s.name !== name) continue;
119
- if (!where(s.labels, s.value)) continue;
120
- const v = Number(s.value);
121
- if (!Number.isFinite(v)) continue;
122
- sum += v;
125
+ for (const sample of series) {
126
+ if (sample.name !== name) continue;
127
+ if (!where(sample.labels, sample.value)) continue;
128
+ const value = Number(sample.value);
129
+ if (!Number.isFinite(value)) continue;
130
+ sum += value;
123
131
  }
124
132
  return sum;
125
133
  }
126
134
 
127
- function getOne(series, { name, where = () => true } = {}) {
128
- for (const s of series) {
129
- if (s.name !== name) continue;
130
- if (!where(s.labels, s.value)) continue;
131
- return Number(s.value);
135
+ export function getOne(series, { name, where = () => true } = {}) {
136
+ for (const sample of series) {
137
+ if (sample.name !== name) continue;
138
+ if (!where(sample.labels, sample.value)) continue;
139
+ return Number(sample.value);
132
140
  }
133
141
  return null;
134
142
  }
135
143
 
136
- async function main() {
137
- let metricsText;
138
- if (METRICS_FILE) {
139
- metricsText = await fs.readFile(METRICS_FILE, "utf8");
140
- } else {
141
- // Give the server a moment to flush post-lifecycle gauges.
142
- await sleep(250);
143
- const r = await fetchTextWithTimeout(`${API_BASE_URL}${METRICS_PATH}`, 10_000);
144
- assert.equal(r.status, 200, `GET ${METRICS_PATH} failed: http ${r.status}`);
145
- metricsText = r.text;
146
- }
147
-
148
- const series = parsePrometheusText(metricsText);
149
-
144
+ export function collectOperationalSloSummary(series) {
150
145
  const http5xxTotal = sumWhere(series, {
151
146
  name: "http_requests_total",
152
147
  where: (labels) => typeof labels.status === "string" && labels.status.startsWith("5")
153
148
  });
154
-
155
149
  const outboxPending = sumWhere(series, { name: "outbox_pending_gauge" });
156
- const deliveryDlq = getOne(series, { name: "delivery_dlq_pending_total_gauge" }) ?? 0;
157
- const deliveriesPending = getOne(series, { name: "deliveries_pending_gauge", where: (l) => l.state === "pending" }) ?? 0;
158
- const deliveriesFailed = getOne(series, { name: "deliveries_pending_gauge", where: (l) => l.state === "failed" }) ?? 0;
159
-
160
- const summary = {
150
+ const deliveryDlq = sumWhere(series, { name: "delivery_dlq_pending_total_gauge" });
151
+ const deliveriesPending = sumWhere(series, {
152
+ name: "deliveries_pending_gauge",
153
+ where: (labels) => labels.state === "pending"
154
+ });
155
+ const deliveriesFailed = sumWhere(series, {
156
+ name: "deliveries_pending_gauge",
157
+ where: (labels) => labels.state === "failed"
158
+ });
159
+ return {
161
160
  http5xxTotal,
162
161
  outboxPending,
163
162
  deliveryDlq,
164
163
  deliveriesPending,
165
164
  deliveriesFailed
166
165
  };
167
- // Single-line JSON for CI logs.
168
- console.log(JSON.stringify({ slo: summary }));
166
+ }
169
167
 
170
- assert.ok(http5xxTotal <= MAX_HTTP_5XX_TOTAL, `SLO breach: http 5xx total ${http5xxTotal} > ${MAX_HTTP_5XX_TOTAL}`);
171
- assert.ok(outboxPending <= MAX_OUTBOX_PENDING, `SLO breach: outbox pending ${outboxPending} > ${MAX_OUTBOX_PENDING}`);
172
- assert.ok(deliveryDlq <= MAX_DELIVERY_DLQ, `SLO breach: delivery DLQ ${deliveryDlq} > ${MAX_DELIVERY_DLQ}`);
173
- assert.ok(deliveriesPending <= MAX_DELIVERIES_PENDING, `SLO breach: deliveries pending ${deliveriesPending} > ${MAX_DELIVERIES_PENDING}`);
174
- assert.ok(deliveriesFailed <= MAX_DELIVERIES_FAILED, `SLO breach: deliveries failed ${deliveriesFailed} > ${MAX_DELIVERIES_FAILED}`);
168
+ export function assertOperationalSlo(summary, thresholds) {
169
+ assert.ok(
170
+ summary.http5xxTotal <= thresholds.maxHttp5xxTotal,
171
+ `SLO breach: http 5xx total ${summary.http5xxTotal} > ${thresholds.maxHttp5xxTotal}`
172
+ );
173
+ assert.ok(
174
+ summary.outboxPending <= thresholds.maxOutboxPending,
175
+ `SLO breach: outbox pending ${summary.outboxPending} > ${thresholds.maxOutboxPending}`
176
+ );
177
+ assert.ok(
178
+ summary.deliveryDlq <= thresholds.maxDeliveryDlq,
179
+ `SLO breach: delivery DLQ ${summary.deliveryDlq} > ${thresholds.maxDeliveryDlq}`
180
+ );
181
+ assert.ok(
182
+ summary.deliveriesPending <= thresholds.maxDeliveriesPending,
183
+ `SLO breach: deliveries pending ${summary.deliveriesPending} > ${thresholds.maxDeliveriesPending}`
184
+ );
185
+ assert.ok(
186
+ summary.deliveriesFailed <= thresholds.maxDeliveriesFailed,
187
+ `SLO breach: deliveries failed ${summary.deliveriesFailed} > ${thresholds.maxDeliveriesFailed}`
188
+ );
175
189
  }
176
190
 
177
- await main();
191
+ export async function loadMetricsText({
192
+ metricsFile = null,
193
+ apiBaseUrl = "http://127.0.0.1:3000",
194
+ metricsPath = "/metrics",
195
+ flushDelayMs = 250
196
+ } = {}) {
197
+ if (metricsFile) {
198
+ return await fs.readFile(metricsFile, "utf8");
199
+ }
200
+ await sleep(flushDelayMs);
201
+ const response = await fetchTextWithTimeout(`${apiBaseUrl}${metricsPath}`, 10_000);
202
+ assert.equal(response.status, 200, `GET ${metricsPath} failed: http ${response.status}`);
203
+ return response.text;
204
+ }
205
+
206
+ export async function runSloCheck({ env = process.env } = {}) {
207
+ const thresholds = parseSloThresholds(env);
208
+ validateSloThresholds(thresholds);
209
+ const metricsFile = normalizeOptionalString(env.SLO_METRICS_FILE);
210
+ const metricsText = await loadMetricsText({
211
+ metricsFile,
212
+ apiBaseUrl: env.SLO_API_BASE_URL ?? "http://127.0.0.1:3000",
213
+ metricsPath: env.SLO_METRICS_PATH ?? "/metrics"
214
+ });
215
+ const series = parsePrometheusText(metricsText);
216
+ const summary = collectOperationalSloSummary(series);
217
+ assertOperationalSlo(summary, thresholds);
218
+ return summary;
219
+ }
220
+
221
+ async function main() {
222
+ const summary = await runSloCheck({ env: process.env });
223
+ console.log(JSON.stringify({ schemaVersion: SLO_CHECK_SCHEMA_VERSION, slo: summary }));
224
+ }
178
225
 
226
+ const isDirectExecution = (() => {
227
+ try {
228
+ return import.meta.url === new URL(`file://${process.argv[1]}`).href;
229
+ } catch {
230
+ return false;
231
+ }
232
+ })();
233
+
234
+ if (isDirectExecution) {
235
+ main().catch((err) => {
236
+ process.stderr.write(`${err?.stack ?? err?.message ?? String(err)}\n`);
237
+ process.exit(1);
238
+ });
239
+ }
@@ -22,6 +22,8 @@ import { buildDisputeOpenEnvelopeV1 } from "../../src/core/dispute-open-envelope
22
22
  import { buildAgreementDelegationV1 } from "../../src/core/agreement-delegation.js";
23
23
  import { buildToolCallAgreementV1 } from "../../src/core/tool-call-agreement.js";
24
24
  import { buildToolCallEvidenceV1 } from "../../src/core/tool-call-evidence.js";
25
+ import { buildPolicyDecisionV1 } from "../../src/core/policy-decision.js";
26
+ import { computeOperatorActionHashV1, signOperatorActionV1 } from "../../src/core/operator-action.js";
25
27
 
26
28
  function bytes(text) {
27
29
  return new TextEncoder().encode(text);
@@ -499,6 +501,7 @@ async function main() {
499
501
  decisionReason: null,
500
502
  verificationStatus: "green",
501
503
  policyHashUsed: "3".repeat(64),
504
+ profileHashUsed: "a".repeat(64),
502
505
  verificationMethodHashUsed: "4".repeat(64),
503
506
  policyRef: {
504
507
  policyHash: "3".repeat(64),
@@ -518,6 +521,70 @@ async function main() {
518
521
  });
519
522
  const settlementDecisionRecordV2Canonical = canonicalJsonStringify(settlementDecisionRecordV2);
520
523
 
524
+ const policyDecision = buildPolicyDecisionV1({
525
+ decisionId: "pdec_run_vectors_0001_auto",
526
+ tenantId,
527
+ runId: agentRun.runId,
528
+ settlementId: agentRunSettlement.settlementId,
529
+ gateId: "gate_vectors_0001",
530
+ policyInput: {
531
+ policyId: "policy_vectors_0001",
532
+ policyVersion: 1
533
+ },
534
+ policyHashUsed: "3".repeat(64),
535
+ verificationMethodHashUsed: "4".repeat(64),
536
+ policyDecision: {
537
+ decisionMode: "automatic",
538
+ verificationStatus: "green",
539
+ runStatus: "completed",
540
+ shouldAutoResolve: true,
541
+ settlementStatus: "released",
542
+ releaseRatePct: 100,
543
+ releaseAmountCents: 1250,
544
+ refundAmountCents: 0,
545
+ reasonCodes: []
546
+ },
547
+ createdAt: "2026-02-01T00:02:00.000Z",
548
+ signerKeyId: keyId,
549
+ signerPrivateKeyPem: privateKeyPem
550
+ });
551
+ const policyDecisionCanonical = canonicalJsonStringify(policyDecision);
552
+
553
+ const operatorActionCore = {
554
+ schemaVersion: "OperatorAction.v1",
555
+ actionId: "opact_vectors_0001",
556
+ caseRef: {
557
+ kind: "dispute",
558
+ caseId: "dsp_run_vectors_0001"
559
+ },
560
+ action: "OVERRIDE_ALLOW",
561
+ justificationCode: "OPS_OVERRIDE_APPROVED",
562
+ justification: "vector override approved",
563
+ actor: {
564
+ operatorId: "op_vectors_0001",
565
+ role: "incident_commander",
566
+ tenantId,
567
+ sessionId: "ops_session_vectors_01",
568
+ metadata: {
569
+ source: "ops-console",
570
+ ticketId: "INC-VECTORS-1"
571
+ }
572
+ },
573
+ actedAt: "2026-02-01T00:02:10.000Z",
574
+ metadata: {
575
+ severity: "critical",
576
+ checklist: ["evidence-reviewed", "lead-approved"]
577
+ }
578
+ };
579
+ const operatorAction = signOperatorActionV1({
580
+ action: operatorActionCore,
581
+ signedAt: "2026-02-01T00:02:11.000Z",
582
+ publicKeyPem,
583
+ privateKeyPem
584
+ });
585
+ const operatorActionCanonical = canonicalJsonStringify(operatorAction);
586
+ const operatorActionCoreCanonical = canonicalJsonStringify(operatorActionCore);
587
+
521
588
  const settlementReceipt = buildSettlementReceipt({
522
589
  receiptId: "rcpt_run_vectors_0001_auto",
523
590
  tenantId,
@@ -843,6 +910,27 @@ async function main() {
843
910
  canonicalJson: settlementDecisionRecordV2Canonical,
844
911
  sha256: sha256Hex(settlementDecisionRecordV2Canonical)
845
912
  },
913
+ policyDecision: {
914
+ schemaVersion: policyDecision.schemaVersion,
915
+ decisionId: policyDecision.decisionId,
916
+ policyDecisionHash: policyDecision.policyDecisionHash,
917
+ evaluationHash: policyDecision.evaluationHash,
918
+ canonicalJson: policyDecisionCanonical,
919
+ sha256: sha256Hex(policyDecisionCanonical),
920
+ signatureKeyId: policyDecision.signature?.signerKeyId ?? null,
921
+ signature: policyDecision.signature?.signature ?? null
922
+ },
923
+ operatorAction: {
924
+ schemaVersion: operatorAction.schemaVersion,
925
+ actionId: operatorAction.actionId ?? null,
926
+ actionHash: operatorAction.signature?.actionHash ?? computeOperatorActionHashV1({ action: operatorActionCore }),
927
+ canonicalJson: operatorActionCanonical,
928
+ sha256: sha256Hex(operatorActionCanonical),
929
+ coreCanonicalJson: operatorActionCoreCanonical,
930
+ coreSha256: sha256Hex(operatorActionCoreCanonical),
931
+ signatureKeyId: operatorAction.signature?.keyId ?? null,
932
+ signature: operatorAction.signature?.signatureBase64 ?? null
933
+ },
846
934
  settlementReceipt: {
847
935
  schemaVersion: settlementReceipt.schemaVersion,
848
936
  receiptId: settlementReceipt.receiptId,
@@ -4,6 +4,7 @@ set -euo pipefail
4
4
  cd "$(dirname "$0")/../.."
5
5
 
6
6
  PROBLEM_TESTS=(
7
+ "test/api-e2e-x402-authorize-payment.test.js"
7
8
  "test/api-python-sdk-first-paid-task-smoke.test.js"
8
9
  "test/api-python-sdk-first-verified-run-smoke.test.js"
9
10
  "test/magic-link-onboarding-live-contract.test.js"
@@ -15,19 +16,32 @@ PROBLEM_TESTS=(
15
16
  "test/trust-config-wizard-cli.test.js"
16
17
  )
17
18
 
18
- declare -A PROBLEM_SET=()
19
- for fp in "${PROBLEM_TESTS[@]}"; do
20
- PROBLEM_SET["$fp"]=1
21
- done
19
+ NOO_REGRESSION_TEST_FILE="test/api-e2e-x402-authorize-payment.test.js"
20
+ REQUIRED_NOO_REGRESSION_TESTS=(
21
+ "API e2e: x402 authorize-payment and verify fail closed on missing or revoked passport when required"
22
+ "API e2e: x402 authorize-payment requires valid execution intent when enabled"
23
+ "API e2e: verify enforces strict request binding evidence for quote-bound authorization"
24
+ )
22
25
 
23
- mapfile -t ALL_TESTS < <(ls test/*.test.js | sort)
26
+ for test_name in "${REQUIRED_NOO_REGRESSION_TESTS[@]}"; do
27
+ if ! grep -F "test(\"${test_name}\"" "$NOO_REGRESSION_TEST_FILE" >/dev/null; then
28
+ echo "missing required NOO regression test: ${test_name}" >&2
29
+ exit 1
30
+ fi
31
+ done
24
32
 
25
33
  SAFE_TESTS=()
26
- for fp in "${ALL_TESTS[@]}"; do
27
- if [[ -n "${PROBLEM_SET[$fp]+x}" ]]; then
28
- continue
34
+ for fp in $(ls test/*.test.js | sort); do
35
+ is_problem_test=0
36
+ for problem in "${PROBLEM_TESTS[@]}"; do
37
+ if [[ "$fp" == "$problem" ]]; then
38
+ is_problem_test=1
39
+ break
40
+ fi
41
+ done
42
+ if [[ "$is_problem_test" -eq 0 ]]; then
43
+ SAFE_TESTS+=("$fp")
29
44
  fi
30
- SAFE_TESTS+=("$fp")
31
45
  done
32
46
 
33
47
  # Phase 1: bulk suite (fast).
@@ -0,0 +1,23 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ # Vercel "ignoreCommand" contract:
5
+ # - exit 0 => skip deployment
6
+ # - exit 1 => continue with deployment
7
+
8
+ if ! git rev-parse --verify HEAD^ >/dev/null 2>&1; then
9
+ # No parent commit context available; build to stay safe.
10
+ exit 1
11
+ fi
12
+
13
+ if git diff --quiet HEAD^ HEAD -- \
14
+ dashboard/ \
15
+ scripts/vercel/ignore-dashboard.sh \
16
+ .github/workflows/release.yml \
17
+ .github/workflows/tests.yml; then
18
+ # No website changes; skip dashboard deployment.
19
+ exit 0
20
+ fi
21
+
22
+ # Relevant website/deploy files changed; run deployment.
23
+ exit 1
@@ -11,6 +11,8 @@ if ! git rev-parse --verify HEAD^ >/dev/null 2>&1; then
11
11
  fi
12
12
 
13
13
  if git diff --quiet HEAD^ HEAD -- \
14
+ mkdocs/docs/ \
15
+ mkdocs/ \
14
16
  docs/ \
15
17
  mkdocs.yml \
16
18
  scripts/vercel/ \