@openwop/openwop-conformance 2.42.6 → 2.42.8
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 +34 -0
- package/README.md +3 -3
- package/coverage.md +1 -1
- package/dist/lib/soft-skip.js +13 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/conformance-envelope-retry-attempted.json +2 -1
- package/package.json +2 -2
- package/requirements.json +581 -175
- package/scenario-majors.json +22 -3
- package/schemas/CORPUS-STAMP.json +70 -67
- package/src/lib/audit-checkpoint.ts +72 -0
- package/src/lib/ext-claims.ts +96 -0
- package/src/lib/mock-ai-budgets.ts +40 -0
- package/src/lib/soft-skip.ts +14 -0
- package/src/scenarios/a2a-peer-authority.test.ts +6 -13
- package/src/scenarios/a2a-task-roundtrip.test.ts +6 -34
- package/src/scenarios/a2a-version-negotiation.test.ts +6 -12
- package/src/scenarios/a2ui-surface-delta-transport.test.ts +23 -12
- package/src/scenarios/agent-channel-dispatch.test.ts +13 -4
- package/src/scenarios/agent-live-invocation-bracket.test.ts +1 -1
- package/src/scenarios/agent-manifest-runtime.test.ts +3 -0
- package/src/scenarios/aiEnvelope.capBreached.test.ts +2 -2
- package/src/scenarios/aiEnvelope.contractRefusal.test.ts +2 -2
- package/src/scenarios/aiEnvelope.schemaDrift.test.ts +2 -2
- package/src/scenarios/aiEnvelope.universalKinds.test.ts +2 -2
- package/src/scenarios/aiproviders-selfhosted-honesty.test.ts +2 -2
- package/src/scenarios/append-ordering.test.ts +26 -21
- package/src/scenarios/artifact-auth.test.ts +3 -1
- package/src/scenarios/artifact-type-registration-source.test.ts +2 -2
- package/src/scenarios/audit-checkpoint-signature.test.ts +63 -0
- package/src/scenarios/audit-checkpoint-vectors.test.ts +93 -0
- package/src/scenarios/audit-log-integrity.test.ts +7 -16
- package/src/scenarios/auth-mtls.test.ts +7 -6
- package/src/scenarios/auth-subject-link-key-class.test.ts +3 -3
- package/src/scenarios/auth-subject-link.test.ts +19 -18
- package/src/scenarios/blob-presign-expiry.test.ts +2 -2
- package/src/scenarios/byok-roundtrip.test.ts +4 -1
- package/src/scenarios/compensation-behavior.test.ts +10 -5
- package/src/scenarios/conformance-advertised-seam-required.test.ts +6 -0
- package/src/scenarios/connection-pack-apihosts.test.ts +2 -2
- package/src/scenarios/connection-provider-resolution.test.ts +2 -2
- package/src/scenarios/context-budget-transcript-bound.test.ts +7 -3
- package/src/scenarios/context-summarization-replay.test.ts +2 -0
- package/src/scenarios/cost-attribution.test.ts +3 -1
- package/src/scenarios/debugBundle.test.ts +12 -4
- package/src/scenarios/discovery.test.ts +6 -2
- package/src/scenarios/dispatch-cross-worker-handoff.test.ts +2 -2
- package/src/scenarios/dispatch-input-mapping.test.ts +2 -2
- package/src/scenarios/dispatch-output-mapping.test.ts +15 -2
- package/src/scenarios/distillation-token-budget.test.ts +2 -2
- package/src/scenarios/durability-poison-exhaustion.test.ts +2 -2
- package/src/scenarios/envelope-completion-distinguishes-truncation.test.ts +51 -15
- package/src/scenarios/envelope-nl-to-format-engaged.test.ts +2 -2
- package/src/scenarios/envelope-recovery-applied.test.ts +2 -2
- package/src/scenarios/envelope-refusal-shape.test.ts +2 -2
- package/src/scenarios/envelope-retry-attempted.test.ts +2 -2
- package/src/scenarios/envelope-retry-exhausted.test.ts +2 -2
- package/src/scenarios/envelope-tier-one-subset-static.test.ts +1 -1
- package/src/scenarios/envelope-truncation-cap-exhaustion.test.ts +2 -2
- package/src/scenarios/eventOrdering.test.ts +17 -10
- package/src/scenarios/form-content-instantiation.test.ts +2 -2
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +2 -2
- package/src/scenarios/highConcurrency.test.ts +9 -9
- package/src/scenarios/idempotency-concurrent-claim.test.ts +5 -5
- package/src/scenarios/idempotency-key-determinism.test.ts +8 -8
- package/src/scenarios/idempotency.test.ts +6 -6
- package/src/scenarios/idempotencyRetry.test.ts +16 -11
- package/src/scenarios/interrupt-token-matrix.test.ts +24 -11
- package/src/scenarios/mcp-cache-tenant-scope.test.ts +3 -3
- package/src/scenarios/mcp-current-auth-boundary.test.ts +9 -6
- package/src/scenarios/mcp-extension-opacity.test.ts +6 -5
- package/src/scenarios/mcp-mrtr-roundtrip.test.ts +9 -8
- package/src/scenarios/mcp-version-negotiation.test.ts +5 -10
- package/src/scenarios/media-url-inline-cap.test.ts +3 -2
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +2 -0
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +2 -2
- package/src/scenarios/memory-consolidation-idempotent.test.ts +2 -2
- package/src/scenarios/memory-injection-budget.test.ts +2 -2
- package/src/scenarios/multi-party-conversation-behavioral.test.ts +2 -2
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +9 -9
- package/src/scenarios/multi-region-idempotency.test.ts +2 -2
- package/src/scenarios/pack-registry-isolation.test.ts +13 -12
- package/src/scenarios/pack-registry.test.ts +3 -2
- package/src/scenarios/pause-resume.test.ts +30 -11
- package/src/scenarios/prompt-read-workspace-membership-enforced.test.ts +43 -34
- package/src/scenarios/provider-usage.test.ts +2 -2
- package/src/scenarios/redaction.test.ts +7 -5
- package/src/scenarios/redactionAdversarial.test.ts +5 -4
- package/src/scenarios/registry-public.test.ts +5 -1
- package/src/scenarios/replay-divergence-at-refusal.test.ts +10 -7
- package/src/scenarios/replay-fanout-suppression.test.ts +7 -4
- package/src/scenarios/replay-fork-arbitrary.test.ts +2 -2
- package/src/scenarios/replay-side-effect-suppression.test.ts +2 -2
- package/src/scenarios/replayDeterminism.test.ts +9 -4
- package/src/scenarios/runner-ledger.test.ts +4 -0
- package/src/scenarios/runtime-capabilities.test.ts +2 -0
- package/src/scenarios/speech-synthesis-roundtrip.test.ts +1 -1
- package/src/scenarios/streamReconnect.test.ts +2 -2
- package/src/scenarios/subrun-approval-gate.test.ts +2 -2
- package/src/scenarios/subworkflow-input-mapping.test.ts +2 -2
- package/src/scenarios/tool-catalog-projection.test.ts +10 -7
- package/src/scenarios/tool-session-lifecycle.test.ts +1 -1
- package/src/scenarios/trigger-ingestion.test.ts +2 -2
- package/src/scenarios/v2-a2ui-v09-surface.test.ts +15 -0
- package/src/scenarios/v2-client-version-header.test.ts +124 -0
- package/src/scenarios/v2-effect-identity-business-key.test.ts +12 -0
- package/src/scenarios/v2-ext-family-claims.test.ts +62 -0
- package/src/scenarios/v2-ext-rest-transport.test.ts +151 -0
- package/src/scenarios/v2-id-grammar.test.ts +17 -5
- package/src/scenarios/v2-idempotency-in-flight.test.ts +3 -0
- package/src/scenarios/v2-legacy-profiles-absent.test.ts +4 -6
- package/src/scenarios/v2-mcp-tasks.test.ts +1 -1
- package/src/scenarios/v2-min-client-version.test.ts +20 -14
- package/src/scenarios/v2-preferred-version-default.test.ts +19 -1
- package/src/scenarios/v2-refresh-sla.test.ts +6 -5
- package/src/scenarios/v2-sse-last-event-id-cursor.test.ts +3 -3
- package/src/scenarios/v2-sse-last-event-id.test.ts +2 -0
- package/src/scenarios/v2-version-header-honored.test.ts +3 -0
- package/src/scenarios/v2-webhook-durable-delivery.test.ts +2 -0
- package/src/scenarios/v2-webhook-generated-secret.test.ts +76 -0
- package/src/scenarios/v2-webhook-secret-rotation.test.ts +8 -1
- package/src/scenarios/verifier-gating.test.ts +2 -2
- package/src/scenarios/version-fold.test.ts +2 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +7 -6
- package/src/scenarios/webhook-signed-delivery.test.ts +2 -0
- package/src/scenarios/workflow-chain-host-expansion.test.ts +10 -1
- package/src/scenarios/workflow-variable-format.test.ts +2 -2
- package/src/scenarios/workload-identity-chain-bounds.test.ts +6 -3
- package/vectors/audit-checkpoint-v1.json +260 -0
|
@@ -168,6 +168,9 @@ describe('v2-version-header-honored (RFC 0172 §A.3)', () => {
|
|
|
168
168
|
},
|
|
169
169
|
req(ID, DOC, `a host that does not serve major 2 MUST refuse OpenWOP-Version: 2.0 with 406 protocol_version_unsupported and details.protocolVersions[] echoing [${advertised.join(', ')}]; a host that LISTS a 2.x member MUST serve it (§1.3 row 1), not refuse it`),
|
|
170
170
|
).toEqual({ status: 406, code: 'protocol_version_unsupported', echoed: [...advertised].sort(), listsMajor2: false });
|
|
171
|
+
// partial-witness-ok: the 406 refusal was held to §1.3 row 2 in full above. It is a
|
|
172
|
+
// complete, correct answer, and there is no second representation to compare bytes
|
|
173
|
+
// against.
|
|
171
174
|
return softSkip('inapplicable', 'the host refused major 2 with the specified 406 — a correct answer, and there is no second representation to compare bytes against');
|
|
172
175
|
}
|
|
173
176
|
if (asked.status !== 200) {
|
|
@@ -366,6 +366,8 @@ describe('RFC 0173 §B — webhook-durable-delivery (gated on webhooks)', () =>
|
|
|
366
366
|
}
|
|
367
367
|
}
|
|
368
368
|
} else {
|
|
369
|
+
// partial-witness-ok: delivery and retry were observed above; the spacing leg binds only
|
|
370
|
+
// an advertised backoff other than none.
|
|
369
371
|
softSkip('inapplicable', 'no advertised retryPolicy.backoff other than none — the spacing leg is not asserted');
|
|
370
372
|
}
|
|
371
373
|
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RFC 0221 — a secret the host generates is returned once (`spec/v2/core/webhooks.md`
|
|
3
|
+
* §Surfaces; suite 2.42.8, target major 2; gated on the `webhooks` family and the
|
|
4
|
+
* `conformance-noop` fixture).
|
|
5
|
+
*
|
|
6
|
+
* `registerWebhook` makes `secret` optional. When the request omits it, the host
|
|
7
|
+
* MUST generate one and return it in the `201` as `secret`, the only time it
|
|
8
|
+
* appears on the wire, and every delivery to that subscription MUST verify under
|
|
9
|
+
* it (`OpenWOP-Signature`, scheme `v1`). When the request supplies a secret, the
|
|
10
|
+
* `201` MUST NOT echo it (RFC 0201 §B.6, now for every subscription).
|
|
11
|
+
*
|
|
12
|
+
* How it FAILS: a `201` without `secret` for a secret-less registration (the
|
|
13
|
+
* subscriber can never verify a delivery); a returned secret the deliveries are
|
|
14
|
+
* not signed with; a `201` that echoes a supplied secret.
|
|
15
|
+
*
|
|
16
|
+
* @see spec/v2/core/webhooks.md §Surfaces
|
|
17
|
+
* @see RFCS/0221-generated-webhook-secret-returned-once.md
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { afterEach, describe, it, expect } from 'vitest';
|
|
21
|
+
import { createHmac } from 'node:crypto';
|
|
22
|
+
import { req } from '../lib/requirement-ids.js';
|
|
23
|
+
import { readErrorCode } from '../lib/error-envelope.js';
|
|
24
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
25
|
+
import { v2Discovery, gateFamily } from '../lib/v2.js';
|
|
26
|
+
import { hitHeader, startModalReceiver, type ModalHit } from '../lib/webhook-receiver.js';
|
|
27
|
+
import { SW_FIXTURE, deliveriesFor, driveRun, loopbackRefusal, registerSw, unregisterAllSw, waitFor } from '../lib/standard-webhooks.js';
|
|
28
|
+
|
|
29
|
+
const ID = 'openwop.requirement.0221.generated-secret-returned';
|
|
30
|
+
const SPEC = 'RFC 0221 · webhooks.md §Surfaces';
|
|
31
|
+
|
|
32
|
+
let closeReceiver: (() => Promise<void>) | null = null;
|
|
33
|
+
afterEach(async () => {
|
|
34
|
+
await unregisterAllSw();
|
|
35
|
+
if (closeReceiver) { const c = closeReceiver; closeReceiver = null; await c(); }
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
function v1SignedBy(secret: string, h: ModalHit): boolean {
|
|
39
|
+
const ts = hitHeader(h, 'openwop-timestamp');
|
|
40
|
+
return ts !== undefined && hitHeader(h, 'openwop-signature') === `sha256=${createHmac('sha256', secret).update(`${ts}.${h.body}`, 'utf8').digest('hex')}`;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
describe('RFC 0221 — a host-generated webhook secret is returned once', () => {
|
|
44
|
+
it('a secret-less registration returns the generated secret, and deliveries verify under it; a supplied secret is never echoed', async () => {
|
|
45
|
+
let doc: Record<string, unknown> | null = null;
|
|
46
|
+
try { doc = await v2Discovery(); } catch { doc = null; }
|
|
47
|
+
if (!doc) return softSkip('blocked', 'v2 discovery unreachable');
|
|
48
|
+
if (!(await gateFamily('webhooks'))) return softSkip('inapplicable', 'webhooks family not advertised (gate recorded under openwop.family.webhooks)');
|
|
49
|
+
const fixtures = Array.isArray(doc['fixtures']) ? (doc['fixtures'] as unknown[]) : [];
|
|
50
|
+
if (!fixtures.includes(SW_FIXTURE)) return softSkip('inapplicable', `${SW_FIXTURE} fixture not advertised — no run to deliver`);
|
|
51
|
+
|
|
52
|
+
const rx = await startModalReceiver();
|
|
53
|
+
closeReceiver = rx.close;
|
|
54
|
+
const target = rx.urlFor('no-echo');
|
|
55
|
+
const reg = await registerSw({ url: target.url, events: ['run.completed'] });
|
|
56
|
+
const refused = loopbackRefusal(reg, target.tunnelled);
|
|
57
|
+
if (refused) return softSkip('blocked', refused);
|
|
58
|
+
if (reg.status !== 201) return softSkip('blocked', `registerWebhook answered ${reg.status} ${readErrorCode(reg.json) ?? ''} — webhooks.md §Surfaces owns that contract`);
|
|
59
|
+
|
|
60
|
+
const body = reg.json as { webhookId: string; secret?: unknown };
|
|
61
|
+
expect(typeof body.secret === 'string' && body.secret.length > 0, req(ID, SPEC, 'a registration that omits secret MUST get the host-generated secret back in the 201 as a non-empty string')).toBe(true);
|
|
62
|
+
const secret = body.secret as string;
|
|
63
|
+
|
|
64
|
+
const run = await driveRun();
|
|
65
|
+
expect(run.status, req(ID, 'runs.md §Create', 'POST /runs MUST answer 201 for the noop fixture')).toBe(201);
|
|
66
|
+
await waitFor(() => deliveriesFor(rx.hits, body.webhookId, run.runId).length > 0, 15_000);
|
|
67
|
+
const delivery = deliveriesFor(rx.hits, body.webhookId, run.runId)[0];
|
|
68
|
+
expect(delivery, req(ID, 'webhooks.md §Durability', 'the subscription MUST receive the run.completed delivery')).toBeDefined();
|
|
69
|
+
expect(v1SignedBy(secret, delivery!), req(ID, SPEC, 'the delivery MUST verify (OpenWOP-Signature, scheme v1) under the secret the 201 returned')).toBe(true);
|
|
70
|
+
|
|
71
|
+
const supplied = `conformance-${'s'.repeat(24)}`;
|
|
72
|
+
const own = await registerSw({ url: rx.urlFor('no-echo').url, events: ['run.completed'], secret: supplied });
|
|
73
|
+
expect(own.status, req(ID, SPEC, `a registration with a supplied secret MUST answer 201 (got ${own.status} ${readErrorCode(own.json) ?? ''})`)).toBe(201);
|
|
74
|
+
expect(own.text.includes(supplied) || (own.json as { secret?: unknown }).secret !== undefined, req(ID, 'RFC 0221 · RFC 0201 §B.6', 'the 201 MUST NOT echo a supplied secret')).toBe(false);
|
|
75
|
+
});
|
|
76
|
+
});
|
|
@@ -117,7 +117,14 @@ describe('RFC 0201 §E — secret rotation overlaps, then retires (gated on webh
|
|
|
117
117
|
// After the overlap: only when it fits the operator's wait cap.
|
|
118
118
|
const expiresAt = Date.parse(body.previousSecretExpiresAt ?? '');
|
|
119
119
|
if (expiresAt - Date.now() > CAP_MS) {
|
|
120
|
-
|
|
120
|
+
// partial-witness-ok: the tenant check, the rotate response and the dual signature during
|
|
121
|
+
// the overlap were asserted above. "The previous secret MUST NOT sign after
|
|
122
|
+
// previousSecretExpiresAt" cannot be observed inside a bounded run when the host's
|
|
123
|
+
// advertised webhooks.secretRotation.overlapSeconds outlasts the suite's wait cap, and
|
|
124
|
+
// blocking every long-overlap host would deny certification to conforming hosts
|
|
125
|
+
// (steward ruling, 2026-09-27). The row detail names both numbers. Raise
|
|
126
|
+
// OPENWOP_WEBHOOK_RETRY_WAIT_MS past the overlap to witness it.
|
|
127
|
+
return softSkip('inapplicable', `the post-overlap leg is not observed: the advertised webhooks.secretRotation.overlapSeconds is ${overlapSeconds}s (expiry ${new Date(expiresAt).toISOString()}) and the suite wait cap is ${CAP_MS}ms; raise OPENWOP_WEBHOOK_RETRY_WAIT_MS past the overlap to witness it`);
|
|
121
128
|
}
|
|
122
129
|
await new Promise((r) => setTimeout(r, Math.max(0, expiresAt - Date.now()) + 1_500));
|
|
123
130
|
const after = await oneDelivery(rx, webhookId);
|
|
@@ -24,7 +24,7 @@ import { driver } from '../lib/driver.js';
|
|
|
24
24
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
25
25
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
26
26
|
import { req } from '../lib/requirement-ids.js';
|
|
27
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
27
|
+
import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
28
28
|
|
|
29
29
|
const SEAM = '/v1/host/sample/agents/verify-run';
|
|
30
30
|
|
|
@@ -62,7 +62,7 @@ describe('verifier-gating (RFC 0090 §B)', () => {
|
|
|
62
62
|
|
|
63
63
|
// PASS verdict → completes normally.
|
|
64
64
|
const passRes = await driver.post(SEAM, { simulateVerdict: 'pass' });
|
|
65
|
-
if (passRes.status === 404) return
|
|
65
|
+
if (passRes.status === 404) return blockedDespiteAssertions('the verify seam answered the fail verdict but not the pass verdict — pass-completes is unobserved');
|
|
66
66
|
expect(
|
|
67
67
|
verifiedVerdict(passRes.json) === 'pass',
|
|
68
68
|
req('openwop.it.verifier-gating.a-fail-verdict-blocks-commit-on-a-gating-host-a-pass-verdict-completes', 'RFC 0090 §A', 'a verify-run forcing a pass MUST emit agent.verified{verdict:"pass"}'),
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
*/
|
|
35
35
|
|
|
36
36
|
import { describe, it, expect } from 'vitest';
|
|
37
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
37
|
+
import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
38
38
|
import { driver, type OpenWOPResponse } from '../lib/driver.js';
|
|
39
39
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
40
40
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
@@ -106,7 +106,7 @@ describe.skipIf(SKIP_NO_FIXTURE)('version-fold: forced engine versions fold-best
|
|
|
106
106
|
if (create.status === 403 && errCode(create.json) === 'force_engine_version_forbidden') {
|
|
107
107
|
// eslint-disable-next-line no-console
|
|
108
108
|
console.warn('[version-fold] API key is production-scoped (403 force_engine_version_forbidden); skipping');
|
|
109
|
-
return
|
|
109
|
+
return blockedDespiteAssertions('[version-fold] API key is production-scoped (403 force_engine_version_forbidden) — the advertised forceEngineVersionRange cannot be exercised; run with a test key');
|
|
110
110
|
}
|
|
111
111
|
expect(
|
|
112
112
|
create.status,
|
|
@@ -65,12 +65,13 @@ describe('wasm-pack-invoke-suspended: suspend → resume round-trip', () => {
|
|
|
65
65
|
'RFCS/0008-wasm-abi.md §D',
|
|
66
66
|
"if a host doesn't implement WASM-driven suspends it MUST surface a recognizable code",
|
|
67
67
|
)).toBe(true);
|
|
68
|
-
|
|
68
|
+
// A failed run carrying the recognizable code IS one of the two conformant
|
|
69
|
+
// outcomes: the requirement was observed.
|
|
70
|
+
} else {
|
|
71
|
+
// Completed path: the reference pack never suspends. Asserting
|
|
72
|
+
// 'completed' confirms the host did not spuriously enter a
|
|
73
|
+
// waiting-* state.
|
|
74
|
+
expect(terminal.status).toBe('completed');
|
|
69
75
|
}
|
|
70
|
-
|
|
71
|
-
// Completed path: the reference pack never suspends. Asserting
|
|
72
|
-
// 'completed' confirms the host did not spuriously enter a
|
|
73
|
-
// waiting-* state.
|
|
74
|
-
expect(terminal.status).toBe('completed');
|
|
75
76
|
});
|
|
76
77
|
});
|
|
@@ -390,6 +390,8 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
|
|
|
390
390
|
// `X-openwop-*` counterpart and the signature verifies reading either.
|
|
391
391
|
const dual = first.headers['openwop-signature'];
|
|
392
392
|
if (dual === undefined) {
|
|
393
|
+
// partial-witness-ok: every X-openwop-* delivery MUST was asserted above; dual-emitting
|
|
394
|
+
// the OpenWOP-* family is an RFC 0165 §C.1 SHOULD.
|
|
393
395
|
softSkip('inapplicable', 'host does not yet dual-emit the OpenWOP-* webhook header family (RFC 0165 §C.1 — SHOULD)');
|
|
394
396
|
} else {
|
|
395
397
|
for (const [neu, old] of [
|
|
@@ -169,7 +169,10 @@ describe('workflow-chain-host-expansion: live host wraps expansion algorithm cor
|
|
|
169
169
|
const [fm, fn] = PACK.version.split('.').map(Number);
|
|
170
170
|
const older = hm! < fm! || (hm === fm && hn! < fn!);
|
|
171
171
|
expect(older, req('openwop.it.workflow-chain-host-expansion.positive-1-node-chain-expansion-matches-the-reference-library-for-the-bundled-pa', 'workflow-chain-host-expansion', `host bundles workflow-chain-sample ${body.packVersion}; this suite's fixture is ${PACK.version} — a host pack NEWER than the suite is unaccounted for`)).toBe(true);
|
|
172
|
-
|
|
172
|
+
// No note here (until 2026-09-27 a blocked soft-skip turned this leg into
|
|
173
|
+
// a partial-witness pass): the chain this leg expands is identical in the
|
|
174
|
+
// older pack, so the comparison below is complete. The RFC 0157 legs,
|
|
175
|
+
// whose chains an older pack lacks, record their own `blocked`.
|
|
173
176
|
}
|
|
174
177
|
expect(typeof body.expansionId).toBe('string');
|
|
175
178
|
expect(body.expansionId.length).toBeGreaterThan(0);
|
|
@@ -321,6 +324,9 @@ describe('workflow-chain-host-expansion: live host wraps expansion algorithm cor
|
|
|
321
324
|
// the sign flipped.
|
|
322
325
|
expect(res.status >= 400, req('openwop.it.workflow-chain-host-expansion.rfc-0157-a-chain-carrying-an-unwind-policy-is-refused-with-capability-required-o', 'compensation.md §"Workflow policy"', 'a non-advertising host MUST refuse a chain carrying `compensation` (policy) — 4xx, not 200')).toBe(true);
|
|
323
326
|
expect(readErrorCode(res.json), req('openwop.it.workflow-chain-host-expansion.rfc-0157-a-chain-carrying-an-unwind-policy-is-refused-with-capability-required-o', 'capabilities.md §"Unsupported capability — refusal contract"', '`capability_required`')).toBe('capability_required');
|
|
327
|
+
// partial-witness-ok: the half of this requirement that binds a
|
|
328
|
+
// non-advertising host — refuse with capability_required — was observed
|
|
329
|
+
// above; the expand-on-an-advertiser half does not apply to this host.
|
|
324
330
|
return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!advertises` returned early');
|
|
325
331
|
}
|
|
326
332
|
expect(res.status, req('openwop.it.workflow-chain-host-expansion.rfc-0157-a-chain-carrying-an-unwind-policy-is-refused-with-capability-required-o', 'workflow-chain-packs.md §"Compensation (RFC 0157)"', 'an advertising host expands the policy-carrying chain')).toBe(200);
|
|
@@ -338,6 +344,9 @@ describe('workflow-chain-host-expansion: live host wraps expansion algorithm cor
|
|
|
338
344
|
if (body.settings?.compensation !== undefined) {
|
|
339
345
|
expect(body.settings.compensation, req('openwop.it.workflow-chain-host-expansion.rfc-0157-a-chain-carrying-an-unwind-policy-is-refused-with-capability-required-o', 'workflow-chain-packs.md §"Compensation (RFC 0157)" rule 9b', 'the chain policy becomes settings.compensation, copied verbatim')).toEqual(expected.settingsCompensation);
|
|
340
346
|
} else {
|
|
347
|
+
// partial-witness-ok: the node carry was observed above; echoing
|
|
348
|
+
// `settings` on the expand seam is host-optional, so the rule 9b policy
|
|
349
|
+
// carry is unobservable through it on a conforming host.
|
|
341
350
|
softSkip('blocked', 'the expand seam does not echo `settings`, so the policy → settings.compensation carry (rule 9b) is unobservable through it; the node carry above was witnessed');
|
|
342
351
|
}
|
|
343
352
|
});
|
|
@@ -48,7 +48,7 @@ import { behaviorGate } from '../lib/behavior-gate.js';
|
|
|
48
48
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
49
49
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
50
50
|
import { driver } from '../lib/driver.js';
|
|
51
|
-
import { softSkip } from '../lib/soft-skip.js';
|
|
51
|
+
import { softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
52
52
|
import { req } from '../lib/requirement-ids.js';
|
|
53
53
|
const WORKFLOW_DEF = join(SCHEMAS_DIR, 'workflow-definition.schema.json');
|
|
54
54
|
// S38 (2026-08-17): `spec/` is NOT in the published package (`files`), so a path built
|
|
@@ -193,7 +193,7 @@ describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gat
|
|
|
193
193
|
if (res.status === 404) return softSkip('blocked', 'precondition not met — `res.status === 404` returned early (A host advertising deferredParameters but not yet serving the variables[]-returning seam extension soft-skips (404), as does a host with the seam disabled in…');
|
|
194
194
|
expect(res.status, req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§Deferred-parameter expansion', 'the deferred-expand seam returns 200')).toBe(200);
|
|
195
195
|
const variables = (res.json as { variables?: Array<{ name: string; type?: string; format?: string }> }).variables;
|
|
196
|
-
if (!Array.isArray(variables)) return
|
|
196
|
+
if (!Array.isArray(variables)) return blockedDespiteAssertions('the deferred-expand seam answered 200 without variables[] — the minted WorkflowVariable.format is unobserved');
|
|
197
197
|
const fmt = (n: string): string | undefined => variables.find((v) => v.name === n)?.format;
|
|
198
198
|
|
|
199
199
|
expect(fmt('email'), req('openwop.it.workflow-variable-format.b1-deferred-expansion-mints-format-onto-the-workflowvariable-string-copied-req-7', '§WorkflowVariable', 'req 7: a string param\'s recognised `format` is minted verbatim')).toBe('email');
|
|
@@ -36,7 +36,7 @@ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
|
|
|
36
36
|
import { driver } from '../lib/driver.js';
|
|
37
37
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
38
38
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
39
|
-
import { seamAbsent, softSkip } from '../lib/soft-skip.js';
|
|
39
|
+
import { seamAbsent, softSkip, blockedDespiteAssertions } from '../lib/soft-skip.js';
|
|
40
40
|
import { req } from '../lib/requirement-ids.js';
|
|
41
41
|
|
|
42
42
|
const DELEGATION_PROFILE = 'openwop-workload-identity-delegation';
|
|
@@ -94,7 +94,7 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
94
94
|
const depth = caps.maxChainDepth as number;
|
|
95
95
|
const chain = Array.from({ length: depth + 1 }, (_, i) => hop(i + 1));
|
|
96
96
|
const r = await resolve({ identity: { ...IDENTITY, delegation: { chain, audience: 'openwop-host', expiresAt: LIVE } }, expectedAudience: 'openwop-host' });
|
|
97
|
-
if (r === null) return
|
|
97
|
+
if (r === null) return blockedDespiteAssertions('the identity-resolve seam is unwired — the maxChainDepth refusal is unobserved');
|
|
98
98
|
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-longer-than-the-advertised-maxchaindepth-is-refused', r, 'delegation_chain_too_long', `a chain of ${depth + 1} hops exceeds the advertised bound of ${depth} — each hop is another party the host trusts transitively`);
|
|
99
99
|
});
|
|
100
100
|
|
|
@@ -113,7 +113,10 @@ describe('RFC 0154 §B — delegation chain bounds (capability-gated behavior)',
|
|
|
113
113
|
expect(r.status >= 400, req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'RFCS/0154 §B', 'a cyclic chain is refused')).toBe(true);
|
|
114
114
|
expect(['delegation_chain_cyclic', 'delegation_chain_too_long'], req('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', 'spec/v1/host-sample-test-seams.md §20', 'cyclic or too-long — the bound is below the cycle length')).toContain(readErrorCode(r.json));
|
|
115
115
|
expect(readRetriable(r.json)).toBe(false);
|
|
116
|
-
|
|
116
|
+
// partial-witness-ok: with maxChainDepth < 3 a 3-hop cyclic chain is also too long. The
|
|
117
|
+
// non-retriable refusal (either code) was asserted above; the cyclic-specific code cannot
|
|
118
|
+
// be told apart here.
|
|
119
|
+
return softSkip('inapplicable', 'maxChainDepth < 3: the cyclic chain is also too long, so the refusal was checked for either code');
|
|
117
120
|
}
|
|
118
121
|
expectRefusal('openwop.it.workload-identity-chain-bounds.a-chain-that-revisits-a-subject-is-refused-as-cyclic', r, 'delegation_chain_cyclic', 'a subject appearing twice is a chain that loops back through authority it already spent — unbounded laundering with a bounded length');
|
|
119
122
|
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$comment": [
|
|
3
|
+
"RFC 0218 — vectors for the audit checkpoint preimage (spec/v1/auth-profiles.md §\"Audit-log integrity\" 3).",
|
|
4
|
+
"A conforming producer or verifier MUST reproduce every entryHash, merkleRoot and signature here, and MUST refuse every `refusals` case.",
|
|
5
|
+
"The key is RFC 8032 §7.1 TEST 1. It is a published test key: never use it for anything else."
|
|
6
|
+
],
|
|
7
|
+
"spec": "RFC 0218; RFC 8785; RFC 8032",
|
|
8
|
+
"construction": {
|
|
9
|
+
"entryHash": "lowercase-hex SHA-256 of the entry's RFC 8785 JCS bytes (the value the next entry carries as prevHash)",
|
|
10
|
+
"range": "entries with sequence in (P, atSequence], P = the previous checkpoint's atSequence, or 0 for the first",
|
|
11
|
+
"parent": "lowercase-hex SHA-256 of the ASCII bytes of left ‖ right (each 64 lowercase hex)",
|
|
12
|
+
"odd": "a last odd node is promoted unchanged, never duplicated",
|
|
13
|
+
"signature": "Ed25519 over the 32 bytes merkleRoot hex-decodes to; base64 (RFC 4648 §4, padded)"
|
|
14
|
+
},
|
|
15
|
+
"publicKeySpkiBase64": "MCowBQYDK2VwAyEA11qYAYKxCrfVS/7TyWQHOg7hcvPapiMlrwIaaPcHURo=",
|
|
16
|
+
"entries": [
|
|
17
|
+
{
|
|
18
|
+
"entry": {
|
|
19
|
+
"action": "run.created",
|
|
20
|
+
"actor": "key:vector-1",
|
|
21
|
+
"atSequence": 1,
|
|
22
|
+
"details": {
|
|
23
|
+
"runId": "run-1"
|
|
24
|
+
},
|
|
25
|
+
"occurredAt": "2026-09-27T00:00:01.000Z",
|
|
26
|
+
"prevHash": null,
|
|
27
|
+
"target": "run-1"
|
|
28
|
+
},
|
|
29
|
+
"entryHash": "2bb3eb03f572491c7334c1a5a12a43e5786663fd4df773b0beb9d05a5d1b496e"
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"entry": {
|
|
33
|
+
"action": "run.created",
|
|
34
|
+
"actor": "key:vector-0",
|
|
35
|
+
"atSequence": 2,
|
|
36
|
+
"details": {
|
|
37
|
+
"runId": "run-2"
|
|
38
|
+
},
|
|
39
|
+
"occurredAt": "2026-09-27T00:00:02.000Z",
|
|
40
|
+
"prevHash": "2bb3eb03f572491c7334c1a5a12a43e5786663fd4df773b0beb9d05a5d1b496e",
|
|
41
|
+
"target": "run-2"
|
|
42
|
+
},
|
|
43
|
+
"entryHash": "66892fdd522ad21a99968866d05cb92f4c1db98ddfebf747817891d744060fda"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"entry": {
|
|
47
|
+
"action": "interrupt.resolved",
|
|
48
|
+
"actor": "key:vector-1",
|
|
49
|
+
"atSequence": 3,
|
|
50
|
+
"details": {
|
|
51
|
+
"runId": "run-3"
|
|
52
|
+
},
|
|
53
|
+
"occurredAt": "2026-09-27T00:00:03.000Z",
|
|
54
|
+
"prevHash": "66892fdd522ad21a99968866d05cb92f4c1db98ddfebf747817891d744060fda",
|
|
55
|
+
"target": "run-3"
|
|
56
|
+
},
|
|
57
|
+
"entryHash": "5873ed119eb591fa8c4af1d7d9d947662c78ca5569c57733ebab3d2b399da6fb"
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
"entry": {
|
|
61
|
+
"action": "run.created",
|
|
62
|
+
"actor": "key:vector-0",
|
|
63
|
+
"atSequence": 4,
|
|
64
|
+
"details": {
|
|
65
|
+
"runId": "run-4"
|
|
66
|
+
},
|
|
67
|
+
"occurredAt": "2026-09-27T00:00:04.000Z",
|
|
68
|
+
"prevHash": "5873ed119eb591fa8c4af1d7d9d947662c78ca5569c57733ebab3d2b399da6fb",
|
|
69
|
+
"target": "run-4"
|
|
70
|
+
},
|
|
71
|
+
"entryHash": "4e48e5dd88bf081edfdbb176a89cfb75697fd4adec954272d65f804ce5643518"
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"entry": {
|
|
75
|
+
"action": "run.created",
|
|
76
|
+
"actor": "key:vector-1",
|
|
77
|
+
"atSequence": 5,
|
|
78
|
+
"details": {
|
|
79
|
+
"note": "ünïcode ✓",
|
|
80
|
+
"votes": [
|
|
81
|
+
1,
|
|
82
|
+
2
|
|
83
|
+
]
|
|
84
|
+
},
|
|
85
|
+
"occurredAt": "2026-09-27T00:00:05.000Z",
|
|
86
|
+
"prevHash": "4e48e5dd88bf081edfdbb176a89cfb75697fd4adec954272d65f804ce5643518",
|
|
87
|
+
"target": "run-5"
|
|
88
|
+
},
|
|
89
|
+
"entryHash": "9930634e696476f67dce8ea69bd89e6ef1d77dbe3279ec694e47e7dacda47f8a"
|
|
90
|
+
},
|
|
91
|
+
{
|
|
92
|
+
"entry": {
|
|
93
|
+
"action": "interrupt.resolved",
|
|
94
|
+
"actor": "key:vector-0",
|
|
95
|
+
"atSequence": 6,
|
|
96
|
+
"details": {
|
|
97
|
+
"runId": "run-6"
|
|
98
|
+
},
|
|
99
|
+
"occurredAt": "2026-09-27T00:00:06.000Z",
|
|
100
|
+
"prevHash": "9930634e696476f67dce8ea69bd89e6ef1d77dbe3279ec694e47e7dacda47f8a",
|
|
101
|
+
"target": "run-6"
|
|
102
|
+
},
|
|
103
|
+
"entryHash": "eda0c73fa14541ca52f2fa9446f07f319a3192c54e66a9a23a48fe22f5c5316f"
|
|
104
|
+
},
|
|
105
|
+
{
|
|
106
|
+
"entry": {
|
|
107
|
+
"action": "run.created",
|
|
108
|
+
"actor": "key:vector-1",
|
|
109
|
+
"atSequence": 7,
|
|
110
|
+
"details": {
|
|
111
|
+
"runId": "run-7"
|
|
112
|
+
},
|
|
113
|
+
"occurredAt": "2026-09-27T00:00:07.000Z",
|
|
114
|
+
"prevHash": "eda0c73fa14541ca52f2fa9446f07f319a3192c54e66a9a23a48fe22f5c5316f",
|
|
115
|
+
"target": "run-7"
|
|
116
|
+
},
|
|
117
|
+
"entryHash": "5d2e74535536c835517898427b6e152fcdd5ed79751d165ffe736d66529ea2c0"
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"entry": {
|
|
121
|
+
"action": "run.created",
|
|
122
|
+
"actor": "key:vector-0",
|
|
123
|
+
"atSequence": 8,
|
|
124
|
+
"details": {
|
|
125
|
+
"runId": "run-8"
|
|
126
|
+
},
|
|
127
|
+
"occurredAt": "2026-09-27T00:00:08.000Z",
|
|
128
|
+
"prevHash": "5d2e74535536c835517898427b6e152fcdd5ed79751d165ffe736d66529ea2c0",
|
|
129
|
+
"target": "run-8"
|
|
130
|
+
},
|
|
131
|
+
"entryHash": "ebe7b92183a8fe2712cb524d7b5ebf052ef85b1c0cffd8b9a977a33d80cb8a4c"
|
|
132
|
+
},
|
|
133
|
+
{
|
|
134
|
+
"entry": {
|
|
135
|
+
"action": "interrupt.resolved",
|
|
136
|
+
"actor": "key:vector-1",
|
|
137
|
+
"atSequence": 9,
|
|
138
|
+
"details": {
|
|
139
|
+
"runId": "run-9"
|
|
140
|
+
},
|
|
141
|
+
"occurredAt": "2026-09-27T00:00:09.000Z",
|
|
142
|
+
"prevHash": "ebe7b92183a8fe2712cb524d7b5ebf052ef85b1c0cffd8b9a977a33d80cb8a4c",
|
|
143
|
+
"target": "run-9"
|
|
144
|
+
},
|
|
145
|
+
"entryHash": "6c3648ec20243c855823056e5ae63b52e7d3e570feef5f6d7a182e084bde6c08"
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
"entry": {
|
|
149
|
+
"action": "run.created",
|
|
150
|
+
"actor": "key:vector-0",
|
|
151
|
+
"atSequence": 10,
|
|
152
|
+
"details": {
|
|
153
|
+
"runId": "run-10"
|
|
154
|
+
},
|
|
155
|
+
"occurredAt": "2026-09-27T00:00:10.000Z",
|
|
156
|
+
"prevHash": "6c3648ec20243c855823056e5ae63b52e7d3e570feef5f6d7a182e084bde6c08",
|
|
157
|
+
"target": "run-10"
|
|
158
|
+
},
|
|
159
|
+
"entryHash": "740826be7291e62cd84199d8702eb7b32db57f12376a6bc13f680acdaba61bce"
|
|
160
|
+
},
|
|
161
|
+
{
|
|
162
|
+
"entry": {
|
|
163
|
+
"action": "run.created",
|
|
164
|
+
"actor": "key:vector-1",
|
|
165
|
+
"atSequence": 11,
|
|
166
|
+
"details": {
|
|
167
|
+
"runId": "run-11"
|
|
168
|
+
},
|
|
169
|
+
"occurredAt": "2026-09-27T00:00:11.000Z",
|
|
170
|
+
"prevHash": "740826be7291e62cd84199d8702eb7b32db57f12376a6bc13f680acdaba61bce",
|
|
171
|
+
"target": "run-11"
|
|
172
|
+
},
|
|
173
|
+
"entryHash": "31d04cd504983f8c9c740128167063a6c4708139e9a9d1b9891e8933792390e4"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"entry": {
|
|
177
|
+
"action": "interrupt.resolved",
|
|
178
|
+
"actor": "key:vector-0",
|
|
179
|
+
"atSequence": 12,
|
|
180
|
+
"details": {
|
|
181
|
+
"runId": "run-12"
|
|
182
|
+
},
|
|
183
|
+
"occurredAt": "2026-09-27T00:00:12.000Z",
|
|
184
|
+
"prevHash": "31d04cd504983f8c9c740128167063a6c4708139e9a9d1b9891e8933792390e4",
|
|
185
|
+
"target": "run-12"
|
|
186
|
+
},
|
|
187
|
+
"entryHash": "213f18d0c231ca4206f7aba10ed4b5a11a7b51f71d77dd4c4f04455a24238fe1"
|
|
188
|
+
}
|
|
189
|
+
],
|
|
190
|
+
"checkpoints": [
|
|
191
|
+
{
|
|
192
|
+
"atSequence": 1,
|
|
193
|
+
"previousAtSequence": 0,
|
|
194
|
+
"leafCount": 1,
|
|
195
|
+
"merkleRoot": "2bb3eb03f572491c7334c1a5a12a43e5786663fd4df773b0beb9d05a5d1b496e",
|
|
196
|
+
"signature": "+O3og3Lozimsu+l0X6NZ3bTpFhvE6bFdVNqOUpAeHGoRSgeAyuufmXPKwoHcnAr4SqxdUtaXhjiyp4jicjYnAg=="
|
|
197
|
+
},
|
|
198
|
+
{
|
|
199
|
+
"atSequence": 3,
|
|
200
|
+
"previousAtSequence": 1,
|
|
201
|
+
"leafCount": 2,
|
|
202
|
+
"merkleRoot": "42750e6edbe7a4e7f10f5af0ebc0a25c45db6f89193dd2688cca5cc405d95b32",
|
|
203
|
+
"signature": "9Yo8N2iXAYLIXIlysqH0OfLkSlgzX4Za4fM/S/1MtIdS1khdBUZ6kTDAYT2Z0XhH0/Qq/KMJA4qjfJekx4oHDg=="
|
|
204
|
+
},
|
|
205
|
+
{
|
|
206
|
+
"atSequence": 7,
|
|
207
|
+
"previousAtSequence": 3,
|
|
208
|
+
"leafCount": 4,
|
|
209
|
+
"merkleRoot": "5e277e32a11f6a3ae377850cdf199e6726daa32e0d83873b698f07aa3096c3e2",
|
|
210
|
+
"signature": "rcjz09tb2ltcoaMYUKjZbp7iY2AHo0WXrJyOPny8T5u/uryyYIgVyzBaEZ3BT3kbAT1y+x7MHtStEt1sIsn2Dw=="
|
|
211
|
+
},
|
|
212
|
+
{
|
|
213
|
+
"atSequence": 12,
|
|
214
|
+
"previousAtSequence": 7,
|
|
215
|
+
"leafCount": 5,
|
|
216
|
+
"merkleRoot": "44a4232543a863aa7fc48477ceb0872e5513dd18db568201990b1b83b4fbbf96",
|
|
217
|
+
"signature": "axFFc3cFhUONP0Y7Is1vWMg12nNvEiT1aZV54XBcj7LN58VbVqGSWQS5jJ5fdzYM0DjfdPJK1x+pKR4KAuCfCg=="
|
|
218
|
+
}
|
|
219
|
+
],
|
|
220
|
+
"refusals": [
|
|
221
|
+
{
|
|
222
|
+
"id": "interior-nodes-as-leaves",
|
|
223
|
+
"why": "the 4-leaf checkpoint at 7 recomputed from its two level-1 nodes gives the same root; a verifier that does not hold the leaf count to atSequence − P accepts it",
|
|
224
|
+
"checkpointAtSequence": 7,
|
|
225
|
+
"leaves": [
|
|
226
|
+
"62cf43b39d9b7ad76060add3967e09f876aa8d962bafd224e1190c3bb7495f3d",
|
|
227
|
+
"a4c9291d9fa45b513d9f22ad3fb8f3d9716869e747c822e49845d4f164328de9"
|
|
228
|
+
],
|
|
229
|
+
"expectedLeaves": 4
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"id": "uppercase-leaf",
|
|
233
|
+
"why": "leaves are lowercase hex; an uppercase leaf hashes to a different parent",
|
|
234
|
+
"leaves": [
|
|
235
|
+
"2BB3EB03F572491C7334C1A5A12A43E5786663FD4DF773B0BEB9D05A5D1B496E"
|
|
236
|
+
],
|
|
237
|
+
"expectedLeaves": 1
|
|
238
|
+
},
|
|
239
|
+
{
|
|
240
|
+
"id": "empty-range",
|
|
241
|
+
"why": "a checkpoint anchors at least one entry",
|
|
242
|
+
"leaves": [],
|
|
243
|
+
"expectedLeaves": 0
|
|
244
|
+
}
|
|
245
|
+
],
|
|
246
|
+
"signatureRefusals": [
|
|
247
|
+
{
|
|
248
|
+
"id": "signed-canonical-json",
|
|
249
|
+
"why": "a signature over the checkpoint object's canonical JSON instead of the root bytes (the reading the schema description once gave) does not verify",
|
|
250
|
+
"merkleRoot": "42750e6edbe7a4e7f10f5af0ebc0a25c45db6f89193dd2688cca5cc405d95b32",
|
|
251
|
+
"signature": "PNlGy3uTxxx3RXhFHBTV1wCm4XpluOy5QXDR4gWHQjh4tNBEYKN0ur7KJxvNXIHTWdKZSWK2Yk0SB4cJk+ZhBw=="
|
|
252
|
+
},
|
|
253
|
+
{
|
|
254
|
+
"id": "signed-hex-text",
|
|
255
|
+
"why": "a signature over the 64 ASCII hex characters instead of the 32 decoded bytes does not verify",
|
|
256
|
+
"merkleRoot": "42750e6edbe7a4e7f10f5af0ebc0a25c45db6f89193dd2688cca5cc405d95b32",
|
|
257
|
+
"signature": "niqG8/dnZLpwv8WfBW082VDfTi5UCrH7uck+hSh3a6ne3RhO+KWvPbyLK++Fw2vumLqD6P4zfTfEgamjxM4ZDA=="
|
|
258
|
+
}
|
|
259
|
+
]
|
|
260
|
+
}
|