@openwop/openwop-conformance 2.43.0 → 2.43.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 +16 -0
- package/README.md +3 -3
- package/dist/cli.js +15 -12
- package/dist/lib/certification-bundle-v3.js +19 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/conformance-approval-reject-loopback.json +33 -0
- package/fixtures/conformance-approval-reject-routed.json +32 -0
- package/fixtures/conformance-approval-timeout-approve.json +46 -0
- package/fixtures/conformance-approval-timeout.json +28 -0
- package/fixtures.md +57 -1
- package/package.json +2 -2
- package/requirements.json +193 -30
- package/scenario-majors.json +5 -2
- package/schemas/CORPUS-STAMP.json +44 -44
- package/src/cli.ts +15 -12
- package/src/lib/certification-bundle-v3.ts +25 -0
- package/src/lib/standard-webhooks.ts +53 -0
- package/src/scenarios/agent-org-chart-scoping.test.ts +63 -0
- package/src/scenarios/v2-agent-org-chart-served-shape.test.ts +63 -0
- package/src/scenarios/v2-approval-reject-disposition.test.ts +106 -6
- package/src/scenarios/v2-error-registry.test.ts +59 -0
- package/src/scenarios/v2-manifest-ceiling-refused.test.ts +21 -9
- package/src/scenarios/v2-manifest-hatch-carried.test.ts +23 -7
- package/src/scenarios/v2-pack-isolation.test.ts +10 -6
- package/src/scenarios/v2-peer-dependency-declared.test.ts +18 -6
- package/src/scenarios/v2-webhook-secret-rotation.test.ts +28 -11
|
@@ -15,8 +15,10 @@
|
|
|
15
15
|
* packs at all (`pack_kind_invalid`) records `skipped` for that leg.
|
|
16
16
|
*
|
|
17
17
|
* Installs through the seams-profile publish seam
|
|
18
|
-
* (`/conformance/seams/packs-test/…`, RFC 0168 §C.2). Gated on
|
|
19
|
-
*
|
|
18
|
+
* (`/conformance/seams/packs-test/…`, RFC 0168 §C.2). Gated on the seams
|
|
19
|
+
* profile; not on `packs` since suite 2.43.1 (#1706) — the hatch binds at install
|
|
20
|
+
* on every path. A seam 404 records `blocked` when `packs` is advertised and
|
|
21
|
+
* `inapplicable` when it is not.
|
|
20
22
|
*
|
|
21
23
|
* @see RFCS/0177-v2-registry-packs-and-extension-tail.md §C.2
|
|
22
24
|
* @see spec/v2/core/packs.md §"The manifest schema family"
|
|
@@ -30,7 +32,7 @@ import { readErrorCode } from '../lib/error-envelope.js';
|
|
|
30
32
|
import { req } from '../lib/requirement-ids.js';
|
|
31
33
|
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
32
34
|
import { seamsProfileAdvertised, targetMajor } from '../lib/seams.js';
|
|
33
|
-
import { v2Discovery,
|
|
35
|
+
import { v2Discovery, familyAdvertised } from '../lib/v2.js';
|
|
34
36
|
|
|
35
37
|
const SECTION = 'packs.md §"The manifest schema family" (RFC 0177 §C.2)';
|
|
36
38
|
|
|
@@ -67,6 +69,17 @@ async function publish(manifest: Record<string, unknown>, files: Record<string,
|
|
|
67
69
|
headers: { 'Content-Type': 'application/octet-stream' },
|
|
68
70
|
});
|
|
69
71
|
}
|
|
72
|
+
/**
|
|
73
|
+
* The publish seam answered 404. Whether that is a defect depends on `packs`
|
|
74
|
+
* (#1706): a host advertising `packs` claims an install path and must mount it
|
|
75
|
+
* (`blocked`); a host that omits `packs` may have no install path the suite can
|
|
76
|
+
* drive (`inapplicable`). §A.1 binds at install on every path, not on `packs`.
|
|
77
|
+
*/
|
|
78
|
+
async function seamMissing(): Promise<undefined> {
|
|
79
|
+
if (await familyAdvertised('packs')) return softSkip('blocked', 'packs-test publish seam answered 404 — packs and the seams profile are advertised but the seam is not mounted');
|
|
80
|
+
return softSkip('inapplicable', 'packs-test publish seam answered 404 and packs is not advertised — no install path the suite can drive');
|
|
81
|
+
}
|
|
82
|
+
|
|
70
83
|
async function preflight(): Promise<{ kind: SoftSkipKind; reason: string } | null> {
|
|
71
84
|
if (targetMajor() !== 2) return { kind: 'inapplicable', reason: 'suite 2.0.0 v2 scenario: OPENWOP_TARGET_MAJOR is not 2' };
|
|
72
85
|
let doc: Record<string, unknown> | null;
|
|
@@ -76,7 +89,6 @@ async function preflight(): Promise<{ kind: SoftSkipKind; reason: string } | nul
|
|
|
76
89
|
doc = null;
|
|
77
90
|
}
|
|
78
91
|
if (!doc) return { kind: 'blocked', reason: 'discovery unreachable — /.well-known/openwop (OpenWOP-Version: 2.0) did not answer 200 JSON' };
|
|
79
|
-
if (!(await gateFamily('packs'))) return { kind: 'inapplicable', reason: 'v2 discovery does not advertise the packs family (RFC 0169 §A.2)' };
|
|
80
92
|
if (!seamsProfileAdvertised(doc)) return { kind: 'inapplicable', reason: 'host does not advertise conformance.seamsProfile: openwop-conformance-seams-v2 — the packs-test publish seam is the only install path the suite can drive (RFC 0168 §C.1)' };
|
|
81
93
|
return null;
|
|
82
94
|
}
|
|
@@ -93,10 +105,14 @@ describe('v2-manifest-hatch-carried (RFC 0177 §C.2)', () => {
|
|
|
93
105
|
engines: { openwop: '>=2.0.0 <3.0.0' },
|
|
94
106
|
runtime: { language: 'javascript', entry: 'index.mjs', format: 'esm' },
|
|
95
107
|
nodes: [{ typeId: `${name}.echo`, version: '1.0.0', category: 'data', role: 'pure' }],
|
|
96
|
-
|
|
108
|
+
// systemPrompt: agent-manifest requires exactly one of systemPrompt /
|
|
109
|
+
// systemPromptRef (oneOf). Without it the fixture was schema-invalid for a
|
|
110
|
+
// reason unrelated to the hatch, and a conforming host had to refuse it
|
|
111
|
+
// (2.43.1; reported by MyndHyve).
|
|
112
|
+
agents: [{ agentId: `${name}.helper`, persona: 'Hatch Helper', modelClass: 'general', systemPrompt: 'You are a conformance fixture.', 'x-vendor-note': 'a hatch property the host MUST ignore' }],
|
|
97
113
|
};
|
|
98
114
|
const res = await publish(manifest, { 'index.mjs': ENTRY });
|
|
99
|
-
if (res.status === 404) return
|
|
115
|
+
if (res.status === 404) return seamMissing();
|
|
100
116
|
expect([200, 201].includes(res.status), req('openwop.requirement.0177.manifest-hatch-carried.agents-x-field', SECTION, `agent-manifest admits ^(openwop-|x-|vendor\\.) — a pack whose agents[0] carries x-vendor-note MUST install (got ${res.status} ${readErrorCode(res.json) ?? ''})`)).toBe(true);
|
|
101
117
|
});
|
|
102
118
|
|
|
@@ -112,7 +128,7 @@ describe('v2-manifest-hatch-carried (RFC 0177 §C.2)', () => {
|
|
|
112
128
|
prompts: [{ templateId: `${name}.system`, version: '1.0.0', kind: 'system', text: 'You are a conformance fixture.', 'x-vendor-note': 'a hatch property the host MUST ignore' }],
|
|
113
129
|
};
|
|
114
130
|
const res = await publish(manifest, {});
|
|
115
|
-
if (res.status === 404) return
|
|
131
|
+
if (res.status === 404) return seamMissing();
|
|
116
132
|
const code = readErrorCode(res.json);
|
|
117
133
|
if (res.status >= 400 && code === 'pack_kind_invalid') return softSkip('skipped', 'host does not install kind: prompt packs (pack_kind_invalid) — the prompt-template hatch cannot be observed on this host');
|
|
118
134
|
expect([200, 201].includes(res.status), req('openwop.requirement.0177.manifest-hatch-carried.prompt-template-x-field', SECTION, `prompt-template admits ^(openwop-|x-|vendor\\.) — a prompt pack whose prompts[0] carries x-vendor-note MUST install (got ${res.status} ${code ?? ''})`)).toBe(true);
|
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* RFC 0173 §B — `pack-isolation` (suite 2.0.0, target major 2; gated on `
|
|
2
|
+
* RFC 0173 §B — `pack-isolation` (suite 2.0.0, target major 2; gated on `sandbox`).
|
|
3
|
+
*
|
|
4
|
+
* Gated on `sandbox` alone since suite 2.43.1 (#1706). It was gated on `packs`
|
|
5
|
+
* + `sandbox`, but `packs` advertises registry resolution (packs.md §The
|
|
6
|
+
* `packs` capability), which a host executing only local packs correctly omits.
|
|
7
|
+
* `sandbox` is the advertisement §B requires of any host executing packs.
|
|
3
8
|
*
|
|
4
9
|
* Isolation binds with pack EXECUTION: a host that executes third-party packs
|
|
5
10
|
* MUST enforce the eight `node-pack-sandbox-*` invariants and MUST advertise
|
|
@@ -41,11 +46,10 @@ async function discovery(): Promise<Record<string, unknown> | null> {
|
|
|
41
46
|
try { return await v2Discovery(); } catch { return null; }
|
|
42
47
|
}
|
|
43
48
|
|
|
44
|
-
/** Gate for every leg:
|
|
49
|
+
/** Gate for every leg: sandbox advertised. Returns the sandbox facet or null (reason recorded). */
|
|
45
50
|
async function gated(): Promise<Record<string, unknown> | null> {
|
|
46
51
|
const doc = await discovery();
|
|
47
52
|
if (!doc) { softSkip('blocked', 'discovery unreachable'); return null; }
|
|
48
|
-
if (!(await gateFamily('packs'))) { softSkip('inapplicable', 'packs family not advertised — no pack execution, no isolation obligation (gate recorded under openwop.family.packs)'); return null; }
|
|
49
53
|
const sandbox = await gateFamily('sandbox');
|
|
50
54
|
if (!sandbox) { softSkip('inapplicable', 'sandbox family not advertised (gate recorded under openwop.family.sandbox)'); return null; }
|
|
51
55
|
return sandbox;
|
|
@@ -56,7 +60,7 @@ async function invoke(typeId: string, extra: Record<string, unknown> = {}): Prom
|
|
|
56
60
|
const doc = await discovery();
|
|
57
61
|
if (!doc || !seamsProfileAdvertised(doc)) { softSkip('inapplicable', `the ${typeId} leg is seam-driven — seams profile (conformance.seamsProfile = openwop-conformance-seams-v2) not advertised`); return null; }
|
|
58
62
|
const res = await driver.post(INVOKE, { typeId, ...extra });
|
|
59
|
-
if (res.status === 404 || res.status === 403 || res.status === 405) { seamAbsent(`host advertises
|
|
63
|
+
if (res.status === 404 || res.status === 403 || res.status === 405) { seamAbsent(`host advertises sandbox but ${INVOKE} answered ${res.status} — the ${typeId} leg is unobservable (host-sample-test-seams.md §8)`); return null; }
|
|
60
64
|
// The same id is ALSO asserted by its own `it` above, and that is the copy the
|
|
61
65
|
// registry can see: `generate-requirement-registry.mjs` harvests `req(…)` only
|
|
62
66
|
// within an `it`, so this helper-level call alone left the id out of
|
|
@@ -74,7 +78,7 @@ function expectEscape(id: string, r: InvokeResult, escapeKind: string, invariant
|
|
|
74
78
|
expect(r.error?.details?.escapeKind, req(id, 'host-sample-test-seams.md §8 SandboxError', `escapeKind MUST be ${escapeKind}`)).toBe(escapeKind);
|
|
75
79
|
}
|
|
76
80
|
|
|
77
|
-
describe('RFC 0173 §B — pack-isolation (gated on
|
|
81
|
+
describe('RFC 0173 §B — pack-isolation (gated on sandbox)', () => {
|
|
78
82
|
it('sandbox.isolationModel names a real mechanism, never node:vm', async () => {
|
|
79
83
|
const sandbox = await gated();
|
|
80
84
|
if (!sandbox) return softSkip('inapplicable', 'gate not met (reason recorded above)');
|
|
@@ -94,7 +98,7 @@ describe('RFC 0173 §B — pack-isolation (gated on packs + sandbox)', () => {
|
|
|
94
98
|
const doc = await discovery();
|
|
95
99
|
if (!doc || !seamsProfileAdvertised(doc)) return softSkip('inapplicable', 'seam-driven — seams profile (conformance.seamsProfile = openwop-conformance-seams-v2) not advertised');
|
|
96
100
|
const res = await driver.post(INVOKE, { typeId: 'misbehave.fs-escape-read' });
|
|
97
|
-
if (res.status === 404 || res.status === 403 || res.status === 405) return seamAbsent(`host advertises
|
|
101
|
+
if (res.status === 404 || res.status === 403 || res.status === 405) return seamAbsent(`host advertises sandbox but ${INVOKE} answered ${res.status}`);
|
|
98
102
|
expect(
|
|
99
103
|
res.status,
|
|
100
104
|
req('openwop.requirement.0173.pack-isolation.seam', 'host-sample-test-seams.md §8', `${INVOKE} MUST answer 200 { result } | 200 { error } — the eight behavioural legs are unobservable otherwise`),
|
|
@@ -17,8 +17,10 @@
|
|
|
17
17
|
* failure; any other refusal fails.
|
|
18
18
|
*
|
|
19
19
|
* Both legs install through the seams-profile publish seam
|
|
20
|
-
* (`/conformance/seams/packs-test/…`, RFC 0168 §C.2). Gated on
|
|
21
|
-
*
|
|
20
|
+
* (`/conformance/seams/packs-test/…`, RFC 0168 §C.2). Gated on the seams
|
|
21
|
+
* profile; not on `packs` since suite 2.43.1 (#1706) — the rule binds at install
|
|
22
|
+
* on every path. A seam 404 records `blocked` when `packs` is advertised and
|
|
23
|
+
* `inapplicable` when it is not.
|
|
22
24
|
*
|
|
23
25
|
* @see RFCS/0177-v2-registry-packs-and-extension-tail.md §B.1, §B.2
|
|
24
26
|
* @see spec/v2/core/packs.md §"Peer-dependency identifiers", §"The alias table"
|
|
@@ -35,7 +37,7 @@ import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
|
35
37
|
import { req } from '../lib/requirement-ids.js';
|
|
36
38
|
import { softSkip, type SoftSkipKind } from '../lib/soft-skip.js';
|
|
37
39
|
import { seamsProfileAdvertised, targetMajor } from '../lib/seams.js';
|
|
38
|
-
import { v2Discovery,
|
|
40
|
+
import { v2Discovery, familyAdvertised } from '../lib/v2.js';
|
|
39
41
|
|
|
40
42
|
const SECTION = 'packs.md §"Peer-dependency identifiers" (RFC 0177 §B.1)';
|
|
41
43
|
const ALIASES = join(SCHEMAS_DIR, '..', 'spec', 'v2', 'peer-dependency-aliases.json');
|
|
@@ -86,6 +88,17 @@ async function publish(manifest: Record<string, unknown>) {
|
|
|
86
88
|
headers: { 'Content-Type': 'application/octet-stream' },
|
|
87
89
|
});
|
|
88
90
|
}
|
|
91
|
+
/**
|
|
92
|
+
* The publish seam answered 404. Whether that is a defect depends on `packs`
|
|
93
|
+
* (#1706): a host advertising `packs` claims an install path and must mount it
|
|
94
|
+
* (`blocked`); a host that omits `packs` may have no install path the suite can
|
|
95
|
+
* drive (`inapplicable`). §A.1 binds at install on every path, not on `packs`.
|
|
96
|
+
*/
|
|
97
|
+
async function seamMissing(): Promise<undefined> {
|
|
98
|
+
if (await familyAdvertised('packs')) return softSkip('blocked', 'packs-test publish seam answered 404 — packs and the seams profile are advertised but the seam is not mounted');
|
|
99
|
+
return softSkip('inapplicable', 'packs-test publish seam answered 404 and packs is not advertised — no install path the suite can drive');
|
|
100
|
+
}
|
|
101
|
+
|
|
89
102
|
async function preflight(): Promise<{ kind: SoftSkipKind; reason: string } | null> {
|
|
90
103
|
if (targetMajor() !== 2) return { kind: 'inapplicable', reason: 'suite 2.0.0 v2 scenario: OPENWOP_TARGET_MAJOR is not 2' };
|
|
91
104
|
let doc: Record<string, unknown> | null;
|
|
@@ -95,7 +108,6 @@ async function preflight(): Promise<{ kind: SoftSkipKind; reason: string } | nul
|
|
|
95
108
|
doc = null;
|
|
96
109
|
}
|
|
97
110
|
if (!doc) return { kind: 'blocked', reason: 'discovery unreachable — /.well-known/openwop (OpenWOP-Version: 2.0) did not answer 200 JSON' };
|
|
98
|
-
if (!(await gateFamily('packs'))) return { kind: 'inapplicable', reason: 'v2 discovery does not advertise the packs family (RFC 0169 §A.2)' };
|
|
99
111
|
if (!seamsProfileAdvertised(doc)) return { kind: 'inapplicable', reason: 'host does not advertise conformance.seamsProfile: openwop-conformance-seams-v2 — the packs-test publish seam is the only install path the suite can drive (RFC 0168 §C.1)' };
|
|
100
112
|
return null;
|
|
101
113
|
}
|
|
@@ -105,7 +117,7 @@ describe('v2-peer-dependency-declared (RFC 0177 §B.1/§B.2)', () => {
|
|
|
105
117
|
const skip = await preflight();
|
|
106
118
|
if (skip) return softSkip(skip.kind, skip.reason);
|
|
107
119
|
const res = await publish(nodePack(freshName('peer-undefined'), { 'host.nonexistent': 'required' }));
|
|
108
|
-
if (res.status === 404) return
|
|
120
|
+
if (res.status === 404) return seamMissing();
|
|
109
121
|
expect(res.status, req('openwop.requirement.0177.peer-dependency-declared.undefined-key', SECTION, 'a key absent from spec/v2/declaration.json MUST be refused with 400 pack_peer_dependency_undefined')).toBe(400);
|
|
110
122
|
expect(readErrorCode(res.json), req('openwop.requirement.0177.peer-dependency-declared.undefined-key', SECTION, 'the refusal code MUST be pack_peer_dependency_undefined')).toBe('pack_peer_dependency_undefined');
|
|
111
123
|
});
|
|
@@ -124,7 +136,7 @@ describe('v2-peer-dependency-declared (RFC 0177 §B.1/§B.2)', () => {
|
|
|
124
136
|
}
|
|
125
137
|
if (!chosen) return softSkip('inapplicable', 'no facet-less alias row names a family this host advertises — the overlap alias cannot be exercised');
|
|
126
138
|
const res = await publish(nodePack(freshName('peer-alias'), { [chosen.alias]: 'required' }));
|
|
127
|
-
if (res.status === 404) return
|
|
139
|
+
if (res.status === 404) return seamMissing();
|
|
128
140
|
const code = readErrorCode(res.json);
|
|
129
141
|
if (res.status === 400 && code === 'pack_peer_dependency_undefined') return softSkip('skipped', `host does not resolve the overlap alias ${chosen.alias} (RFC 0177 §B.2 is a MAY during the overlap; MUST NOT after v1 end-of-support)`);
|
|
130
142
|
expect([200, 201].includes(res.status), req('openwop.requirement.0177.peer-dependency-declared.alias-overlap', 'packs.md §"The alias table" (RFC 0177 §B.2)', `alias ${chosen.alias} → ${chosen.family} (advertised) MUST install through the overlap or be refused only with pack_peer_dependency_undefined (got ${res.status} ${code ?? ''})`)).toBe(true);
|
|
@@ -10,15 +10,20 @@
|
|
|
10
10
|
* checked before lookup (`403 id_tenant_mismatch`), refuses a subscription that
|
|
11
11
|
* did not opt in (`400 validation_error`), and returns no secret.
|
|
12
12
|
*
|
|
13
|
-
* The post-overlap leg waits for `previousSecretExpiresAt`
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
13
|
+
* The post-overlap leg waits for `previousSecretExpiresAt` when `overlapSeconds`
|
|
14
|
+
* fits inside the suite's retry-wait cap (`OPENWOP_WEBHOOK_RETRY_WAIT_MS`,
|
|
15
|
+
* operator-raisable). Past the cap (suite 2.43.1), a seams-profile host that
|
|
16
|
+
* serves the OPTIONAL `shortenRotationOverlap` seam (host-sample-test-seams.md
|
|
17
|
+
* §29) has this one subscription's overlap cut to a few seconds, and the leg
|
|
18
|
+
* waits for that; a served seam that breaks its contract records `blocked`.
|
|
19
|
+
* A host without the seam is not observed, and the row says so with a
|
|
20
|
+
* `partial-witness:` detail — which the acceptance predicate refuses to cite as
|
|
21
|
+
* `executed-pass` (RFC 0201 §Falsifiability).
|
|
18
22
|
*
|
|
19
23
|
* How it FAILS: signing only with the new secret (one entry); moving
|
|
20
24
|
* `OpenWOP-Signature` to the new secret at rotation; a foreign-tenant id that
|
|
21
|
-
* reaches the lookup (`404` instead of `403`); a response that echoes the secret
|
|
25
|
+
* reaches the lookup (`404` instead of `403`); a response that echoes the secret;
|
|
26
|
+
* the previous secret still signing after the (shortened) overlap.
|
|
22
27
|
*
|
|
23
28
|
* @see spec/v2/core/webhooks.md §Standard Webhooks
|
|
24
29
|
* @see RFCS/0201-standard-webhooks-signature-scheme.md §E
|
|
@@ -32,6 +37,7 @@ import { readErrorCode } from '../lib/error-envelope.js';
|
|
|
32
37
|
import { softSkip } from '../lib/soft-skip.js';
|
|
33
38
|
import { projectBoundId } from '../lib/bound-id.js';
|
|
34
39
|
import { retryWaitCapMs } from '../lib/webhook-retry-window.js';
|
|
40
|
+
import { seamsProfileAdvertised } from '../lib/seams.js';
|
|
35
41
|
import { hitHeader, mintWhsec, startModalReceiver, verifyStandardWebhooks, type ModalHit } from '../lib/webhook-receiver.js';
|
|
36
42
|
import {
|
|
37
43
|
STANDARD_WEBHOOKS_ALG,
|
|
@@ -39,12 +45,15 @@ import {
|
|
|
39
45
|
driveRun,
|
|
40
46
|
loopbackRefusal,
|
|
41
47
|
registerSw,
|
|
48
|
+
shortenRotationOverlap,
|
|
42
49
|
swGate,
|
|
43
50
|
unregisterAllSw,
|
|
44
51
|
waitFor,
|
|
45
52
|
} from '../lib/standard-webhooks.js';
|
|
46
53
|
|
|
47
54
|
const CAP_MS = retryWaitCapMs();
|
|
55
|
+
/** The overlap the §29 seam sets: long enough that the call returns inside it, short enough to wait out. */
|
|
56
|
+
const SEAM_OVERLAP_SECONDS = 3;
|
|
48
57
|
|
|
49
58
|
let closeReceiver: (() => Promise<void>) | null = null;
|
|
50
59
|
afterEach(async () => {
|
|
@@ -114,17 +123,25 @@ describe('RFC 0201 §E — secret rotation overlaps, then retires (gated on webh
|
|
|
114
123
|
expect(underOld.matched, req(ID, 'RFC 0201 §E.20', 'one entry MUST verify under the previous secret')).toBe(1);
|
|
115
124
|
expect(v1SignedBy(s1, d), req(ID, 'RFC 0201 §E.20', 'OpenWOP-Signature MUST stay on the previous secret during the overlap')).toBe(true);
|
|
116
125
|
|
|
117
|
-
// After the overlap:
|
|
118
|
-
|
|
126
|
+
// After the overlap: when it fits the operator's wait cap, or when the §29 seam shortens it.
|
|
127
|
+
let expiresAt = Date.parse(body.previousSecretExpiresAt ?? '');
|
|
119
128
|
if (expiresAt - Date.now() > CAP_MS) {
|
|
129
|
+
const unobserved = (why: string): string => `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, and ${why}; raise OPENWOP_WEBHOOK_RETRY_WAIT_MS past the overlap, or serve the seam, to witness it`;
|
|
120
130
|
// partial-witness-ok: the tenant check, the rotate response and the dual signature during
|
|
121
131
|
// the overlap were asserted above. "The previous secret MUST NOT sign after
|
|
122
132
|
// previousSecretExpiresAt" cannot be observed inside a bounded run when the host's
|
|
123
133
|
// advertised webhooks.secretRotation.overlapSeconds outlasts the suite's wait cap, and
|
|
124
134
|
// blocking every long-overlap host would deny certification to conforming hosts
|
|
125
|
-
// (steward ruling, 2026-09-27). The
|
|
126
|
-
//
|
|
127
|
-
return softSkip('inapplicable',
|
|
135
|
+
// (steward ruling, 2026-09-27). The §29 seam closes it, but only for a host that
|
|
136
|
+
// claims the seams profile; this one did not. The row detail names both numbers.
|
|
137
|
+
if (!seamsProfileAdvertised(g.doc)) return softSkip('inapplicable', unobserved('the seams profile is not advertised, so the optional shortenRotationOverlap seam (host-sample-test-seams.md §29) is not offered'));
|
|
138
|
+
const cut = await shortenRotationOverlap(webhookId, SEAM_OVERLAP_SECONDS, foreign);
|
|
139
|
+
// partial-witness-ok: as above — the dual-signing half was asserted. The §29 seam is
|
|
140
|
+
// OPTIONAL and newer than this leg, so a host that does not serve it keeps the partial
|
|
141
|
+
// witness it held before the seam existed, never `blocked`.
|
|
142
|
+
if (!cut.ok && cut.unserved) return softSkip('inapplicable', unobserved(cut.reason));
|
|
143
|
+
if (!cut.ok) return softSkip('blocked', cut.reason);
|
|
144
|
+
expiresAt = cut.expiresAt;
|
|
128
145
|
}
|
|
129
146
|
await new Promise((r) => setTimeout(r, Math.max(0, expiresAt - Date.now()) + 1_500));
|
|
130
147
|
const after = await oneDelivery(rx, webhookId);
|