@openwop/openwop-conformance 1.106.0 → 1.123.0

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 (154) hide show
  1. package/README.md +3 -3
  2. package/coverage.md +6 -0
  3. package/dist/cli.js +89 -45
  4. package/dist/lib/certification-bundle-verify.js +274 -0
  5. package/dist/lib/profiles.js +102 -19
  6. package/dist/lib/requirement-ledger.js +182 -0
  7. package/dist/lib/requirement-registry.js +110 -0
  8. package/dist/lib/scenario-disposition.js +249 -0
  9. package/dist/lib/soft-skip.js +82 -0
  10. package/fixtures/conformance-a2a-task-roundtrip.json +1 -1
  11. package/fixtures.md +1 -1
  12. package/package.json +1 -1
  13. package/schemas/CORPUS-STAMP.json +2 -2
  14. package/schemas/README.md +1 -0
  15. package/schemas/capabilities.schema.json +2 -2
  16. package/schemas/compensation-policy.schema.json +64 -0
  17. package/schemas/run-snapshot.schema.json +5 -0
  18. package/schemas/workflow-chain-pack-manifest.schema.json +125 -0
  19. package/schemas/workflow-definition.schema.json +4 -0
  20. package/src/cli.ts +99 -45
  21. package/src/lib/a2a-fake-peer.ts +290 -110
  22. package/src/lib/certification-bundle-verify.ts +356 -0
  23. package/src/lib/llm-cache-key-recipe.ts +6 -5
  24. package/src/lib/mcp-fake-server.ts +235 -72
  25. package/src/lib/profiles.ts +132 -19
  26. package/src/lib/requirement-ledger.ts +98 -1
  27. package/src/lib/requirement-registry.ts +47 -4
  28. package/src/lib/scenario-disposition.ts +291 -0
  29. package/src/lib/soft-skip.ts +89 -0
  30. package/src/lib/workflow-chain-expansion.ts +189 -0
  31. package/src/scenarios/a2a-1-0-agent-card.test.ts +179 -0
  32. package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +109 -0
  33. package/src/scenarios/a2a-card-runtime-consistency.test.ts +167 -0
  34. package/src/scenarios/a2a-peer-authority.test.ts +99 -0
  35. package/src/scenarios/a2a-task-roundtrip.test.ts +18 -6
  36. package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -2
  37. package/src/scenarios/agent-loop-stateful-resume.test.ts +3 -2
  38. package/src/scenarios/agent-loop-version5-shape.test.ts +2 -1
  39. package/src/scenarios/agent-loop-workspace-snapshot.test.ts +4 -3
  40. package/src/scenarios/approval-gate-flow.test.ts +5 -4
  41. package/src/scenarios/artifact-type-store-without-render.test.ts +2 -1
  42. package/src/scenarios/auth-scim-profile.test.ts +5 -4
  43. package/src/scenarios/authorization-fail-closed.test.ts +4 -3
  44. package/src/scenarios/certification-bundle-non-vacuous.test.ts +249 -0
  45. package/src/scenarios/certification-bundle-redaction.test.ts +182 -0
  46. package/src/scenarios/certification-floor-enforcement.test.ts +30 -6
  47. package/src/scenarios/chain-compensation-expansion.test.ts +243 -0
  48. package/src/scenarios/commitment-fired.test.ts +3 -2
  49. package/src/scenarios/compensation-behavior.test.ts +63 -0
  50. package/src/scenarios/compensation-profile.test.ts +144 -1
  51. package/src/scenarios/conformance-advertised-seam-required.test.ts +118 -0
  52. package/src/scenarios/conformance-execution-witness.test.ts +104 -0
  53. package/src/scenarios/core-manifest-and-extension-registry.test.ts +26 -0
  54. package/src/scenarios/credential-payload-redaction.test.ts +4 -3
  55. package/src/scenarios/credentials-capability-shape.test.ts +4 -3
  56. package/src/scenarios/cross-engine-append-behavior.test.ts +5 -4
  57. package/src/scenarios/cross-engine-append-ordering.test.ts +3 -2
  58. package/src/scenarios/cross-host-ancestry-endpoint.test.ts +5 -4
  59. package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
  60. package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
  61. package/src/scenarios/deadletter-retry-exhaustion.test.ts +5 -4
  62. package/src/scenarios/distillation-index-roundtrip.test.ts +3 -2
  63. package/src/scenarios/distillation-secret-carryforward.test.ts +3 -2
  64. package/src/scenarios/distillation-shape.test.ts +2 -1
  65. package/src/scenarios/distillation-stable-archive.test.ts +4 -3
  66. package/src/scenarios/distillation-token-budget.test.ts +4 -3
  67. package/src/scenarios/feedback-correction-redaction.test.ts +4 -3
  68. package/src/scenarios/feedback-cross-tenant-isolation.test.ts +4 -3
  69. package/src/scenarios/feedback-fork-not-copied.test.ts +6 -5
  70. package/src/scenarios/feedback-on-terminal-run.test.ts +4 -3
  71. package/src/scenarios/feedback-record-and-list.test.ts +4 -3
  72. package/src/scenarios/feedback-unsupported-501.test.ts +3 -2
  73. package/src/scenarios/fixtures-valid.test.ts +16 -18
  74. package/src/scenarios/heartbeat-capability-shape.test.ts +2 -1
  75. package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +4 -3
  76. package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +6 -5
  77. package/src/scenarios/heartbeat-runtime-bound.test.ts +4 -3
  78. package/src/scenarios/mcp-2026-07-28-discover.test.ts +207 -0
  79. package/src/scenarios/mcp-cache-tenant-scope.test.ts +66 -0
  80. package/src/scenarios/mcp-current-auth-boundary.test.ts +55 -0
  81. package/src/scenarios/mcp-extension-opacity.test.ts +79 -0
  82. package/src/scenarios/mcp-mrtr-roundtrip.test.ts +140 -0
  83. package/src/scenarios/mcp-stateless-request.test.ts +77 -0
  84. package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
  85. package/src/scenarios/mcp-toolcall-redaction.test.ts +2 -1
  86. package/src/scenarios/memory-attribution-emits-on-write.test.ts +6 -5
  87. package/src/scenarios/memory-attribution-no-content.test.ts +4 -3
  88. package/src/scenarios/memory-attribution-replay-stable.test.ts +6 -5
  89. package/src/scenarios/memory-attribution-tenant-scoped.test.ts +4 -3
  90. package/src/scenarios/memory-compaction-event-emitted.test.ts +3 -2
  91. package/src/scenarios/memory-compaction-provenance-tag.test.ts +6 -5
  92. package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +4 -3
  93. package/src/scenarios/memory-consolidation-idempotent.test.ts +4 -3
  94. package/src/scenarios/memory-injection-budget.test.ts +4 -3
  95. package/src/scenarios/metric-emission.test.ts +4 -3
  96. package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
  97. package/src/scenarios/multi-agent-handoff-state-machine.test.ts +4 -3
  98. package/src/scenarios/multi-agent-memory-lifecycle.test.ts +5 -4
  99. package/src/scenarios/multi-party-conversation-behavioral.test.ts +3 -2
  100. package/src/scenarios/multi-region-idempotency-behavior.test.ts +7 -6
  101. package/src/scenarios/multi-region-idempotency.test.ts +4 -3
  102. package/src/scenarios/normative-example-extraction.test.ts +242 -0
  103. package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +4 -3
  104. package/src/scenarios/oauth-capability-shape.test.ts +4 -3
  105. package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
  106. package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +309 -0
  107. package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -1
  108. package/src/scenarios/otel-emission-grpc.test.ts +4 -3
  109. package/src/scenarios/otel-emission.test.ts +4 -3
  110. package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +5 -4
  111. package/src/scenarios/otel-trace-propagation.test.ts +4 -3
  112. package/src/scenarios/pack-registry-isolation.test.ts +4 -3
  113. package/src/scenarios/pack-registry-publish.test.ts +65 -3
  114. package/src/scenarios/pack-registry.test.ts +22 -0
  115. package/src/scenarios/profile-discovery-core-alias.test.ts +137 -0
  116. package/src/scenarios/profileDerivation.test.ts +2 -1
  117. package/src/scenarios/rate-limit-envelope.test.ts +2 -1
  118. package/src/scenarios/registry-public.test.ts +6 -5
  119. package/src/scenarios/replay-divergence-at-refusal.test.ts +9 -8
  120. package/src/scenarios/replay-llm-cache-key-portable.test.ts +19 -13
  121. package/src/scenarios/replay-llm-cache-key.test.ts +120 -47
  122. package/src/scenarios/replay-observable-sequence-determinism.test.ts +5 -4
  123. package/src/scenarios/replay-side-effect-suppression.test.ts +20 -1
  124. package/src/scenarios/requirement-ledger.test.ts +29 -6
  125. package/src/scenarios/runner-ledger.test.ts +279 -0
  126. package/src/scenarios/runtime-requires-install-gate.test.ts +6 -5
  127. package/src/scenarios/sandbox-memory-cap.test.ts +3 -2
  128. package/src/scenarios/sandbox-mvp-behavior.test.ts +11 -10
  129. package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
  130. package/src/scenarios/sandbox-timeout-cap.test.ts +3 -2
  131. package/src/scenarios/stream-text-fixture.test.ts +5 -4
  132. package/src/scenarios/subrun-approval-fail-closed.test.ts +3 -2
  133. package/src/scenarios/subrun-approval-gate.test.ts +4 -3
  134. package/src/scenarios/subrun-attestation-shape.test.ts +2 -1
  135. package/src/scenarios/subrun-checksum-stable.test.ts +4 -3
  136. package/src/scenarios/test-seam-unauthenticated.test.ts +61 -11
  137. package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -2
  138. package/src/scenarios/tool-hooks-content-free.test.ts +3 -2
  139. package/src/scenarios/tool-hooks-rate-limit.test.ts +3 -2
  140. package/src/scenarios/tool-hooks-secret-redaction.test.ts +3 -2
  141. package/src/scenarios/tool-hooks-shape.test.ts +2 -1
  142. package/src/scenarios/trigger-bridge-delivery.test.ts +3 -2
  143. package/src/scenarios/version-fold.test.ts +5 -4
  144. package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +5 -4
  145. package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
  146. package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
  147. package/src/scenarios/wasm-pack-load.test.ts +4 -3
  148. package/src/scenarios/wasm-pack-memory-cap.test.ts +4 -3
  149. package/src/scenarios/wasm-pack-replay-determinism.test.ts +3 -2
  150. package/src/scenarios/webhook-signed-delivery.test.ts +4 -3
  151. package/src/scenarios/workflow-primary-output-annotation.test.ts +13 -23
  152. package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +3 -2
  153. package/src/scenarios/workspace-cross-tenant-isolation.test.ts +3 -2
  154. package/src/setup.ts +95 -1
@@ -19,6 +19,7 @@
19
19
  */
20
20
 
21
21
  import { describe, it, expect } from 'vitest';
22
+ import { softSkip } from '../lib/soft-skip.js';
22
23
  import { driver } from '../lib/driver.js';
23
24
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
24
25
 
@@ -40,11 +41,11 @@ const SEAM_PATH = 'WCT1-SECRET.md';
40
41
 
41
42
  describe('workspace-cross-tenant-isolation: a workspace file MUST NOT leak across owners (RFC 0059 §E WCT-1)', () => {
42
43
  it('a file written under {tenant A, workspace A} is not readable under a different owner', async () => {
43
- if (!(await workspaceSupported())) return; // capability not advertised — skip
44
+ if (!(await workspaceSupported())) return softSkip('inapplicable', 'capability not advertised — skip');
44
45
 
45
46
  // Owner A writes a file.
46
47
  const put = await seam({ tenant: 'wct1-tenant-a', workspace: 'ws-a', op: 'put', path: SEAM_PATH, content: 'A-only secret body' });
47
- if (put.status === 404) return; // seam unwired — soft-skip the behavioral probe
48
+ if (put.status === 404) return softSkip('blocked', 'seam unwired — soft-skip the behavioral probe');
48
49
  expect(put.status, driver.describe('agent-workspace.md §C PUT', 'seam put MUST succeed for the owning workspace')).toBe(200);
49
50
 
50
51
  // A DIFFERENT workspace (same tenant) MUST NOT read it.
package/src/setup.ts CHANGED
@@ -30,6 +30,11 @@ import { setMultiAgentCapabilities } from './lib/multi-agent-capabilities.js';
30
30
  import { OtelCollector, setCollector } from './lib/otel-collector.js';
31
31
  import { McpFakeServer, setMcpFakeServer } from './lib/mcp-fake-server.js';
32
32
  import { A2AFakePeer, setA2AFakePeer } from './lib/a2a-fake-peer.js';
33
+ import { afterAll, afterEach, beforeAll, expect } from 'vitest';
34
+ import { basename } from 'node:path';
35
+ import { recordRequirement, journalLength, journalSince } from './lib/requirement-ledger.js';
36
+ import { requirementIdForFile, resolveFileRecord, type FileTestState } from './lib/scenario-disposition.js';
37
+ import { softSkipDisposition } from './lib/soft-skip.js';
33
38
  import type { DiscoveryPayload } from './lib/profiles.js';
34
39
 
35
40
  const SUITE_INIT_TIMEOUT_MS = 5_000;
@@ -181,7 +186,15 @@ async function maybeStartA2AFakePeer(): Promise<void> {
181
186
  }
182
187
  const portEnv = process.env.OPENWOP_A2A_FAKE_PEER_PORT;
183
188
  const requestedPort = portEnv ? Number(portEnv) : 0;
184
- const peer = new A2AFakePeer();
189
+ // Dual-era since 1.112.0. Order = card shape for a header-less GET (first
190
+ // wins); both eras are spoken on the RPC path. Default keeps the 0.3 card so
191
+ // today's 0.3 clients (which read `card.url`) keep working; set
192
+ // OPENWOP_A2A_FAKE_PEER_VERSIONS=1.0,0.3 to serve the 1.0 card by default.
193
+ const versionsEnv = process.env.OPENWOP_A2A_FAKE_PEER_VERSIONS;
194
+ const versions = versionsEnv
195
+ ? (versionsEnv.split(',').map((v) => v.trim()).filter((v) => v === '1.0' || v === '0.3') as Array<'1.0' | '0.3'>)
196
+ : undefined;
197
+ const peer = new A2AFakePeer(versions && versions.length > 0 ? { protocolVersions: versions } : undefined);
185
198
  await peer.start(requestedPort);
186
199
  setA2AFakePeer(peer);
187
200
  // eslint-disable-next-line no-console
@@ -195,3 +208,84 @@ await loadHostFixtures();
195
208
  await maybeStartOtelCollector();
196
209
  await maybeStartMcpFakeServer();
197
210
  await maybeStartA2AFakePeer();
211
+
212
+ // ─── RFC 0148 §A file-level disposition recording (S6, acceptance item 2) ─────
213
+ //
214
+ // Every scenario FILE records exactly one disposition for its own requirement
215
+ // id when it finishes. `afterEach` collects per-test states; `afterAll` folds
216
+ // them (fileDisposition) and records — `executed-fail` / `executed-pass` /
217
+ // `inapplicable` or `skipped` (when a behaviorGate in the file recorded that
218
+ // reason for the profile it gates on) / else `blocked` (an all-skipped file
219
+ // with no reason is an unclassified return, and §A resolves it to blocked).
220
+ // When the runner set OPENWOP_LEDGER_PATH the recording also lands in the JSONL
221
+ // sink, which is how `--certify` gets requirement-level dispositions instead of
222
+ // inferring them from per-file pass/fail/skip.
223
+ //
224
+ // Setup-file hooks apply to every file in the worker; state is keyed by file.
225
+ const _fileStates = new Map<string, FileTestState[]>();
226
+ const _fileAssertions = new Map<string, number>();
227
+ const _ledgerMarks = new Map<string, number>();
228
+ function _fileOf(task: { file?: { filepath?: string; name?: string } } | undefined): string | null {
229
+ const f = task?.file?.filepath ?? task?.file?.name;
230
+ return typeof f === 'string' && f.length > 0 ? basename(f) : null;
231
+ }
232
+ beforeAll(({}, suite) => {
233
+ // Mark the ledger journal BEFORE the file's tests run, so a behaviorGate
234
+ // decision made by the very first test is inside the file's window.
235
+ const s = suite as unknown as { filepath?: string; name?: string; file?: { filepath?: string; name?: string } };
236
+ const file = _fileOf({ file: s.file ?? s });
237
+ if (file !== null && !_ledgerMarks.has(file)) _ledgerMarks.set(file, journalLength());
238
+ });
239
+ afterEach(({ task }) => {
240
+ const file = _fileOf(task as { file?: { filepath?: string; name?: string } });
241
+ if (file === null) return;
242
+ if (!_ledgerMarks.has(file)) _ledgerMarks.set(file, 0);
243
+ const state = task.result?.state;
244
+ const arr = _fileStates.get(file) ?? [];
245
+ arr.push(state === 'pass' ? 'pass' : state === 'fail' ? 'fail' : 'skip');
246
+ _fileStates.set(file, arr);
247
+ // RFC 0148 §C assertionCount: how many `expect` calls this test actually made.
248
+ // A leg that early-returns from a gate makes zero, and a file of such legs is
249
+ // an `executed-pass` with assertionCount 0 — visible, and unclassified for a
250
+ // claimed floor.
251
+ let calls = 0;
252
+ try {
253
+ calls = (expect.getState() as { assertionCalls?: number }).assertionCalls ?? 0;
254
+ } catch {
255
+ /* no state — count 0 */
256
+ }
257
+ _fileAssertions.set(file, (_fileAssertions.get(file) ?? 0) + calls);
258
+ });
259
+ afterAll(({}, suite) => {
260
+ // vitest 4: the suite/file task is the SECOND argument. For a file-level
261
+ // afterAll registered from a setup file, `suite` is the File task itself.
262
+ const s = suite as unknown as { filepath?: string; name?: string; file?: { filepath?: string; name?: string } };
263
+ const file = _fileOf({ file: s.file ?? s });
264
+ if (file === null || !file.endsWith('.test.ts')) return;
265
+ const states = _fileStates.get(file) ?? [];
266
+ // Did a behaviorGate in this file record inapplicable/skipped for its profile?
267
+ const mark = _ledgerMarks.get(file) ?? 0;
268
+ const since = journalSince(mark);
269
+ const gateReason = since.some((e) => e.disposition === 'inapplicable')
270
+ ? 'inapplicable'
271
+ : since.some((e) => e.disposition === 'skipped')
272
+ ? 'skipped'
273
+ : undefined;
274
+ const assertionCount = _fileAssertions.get(file) ?? 0;
275
+ // RFC 0148 §A: a pass with zero assertions is an unclassified return. If the
276
+ // file said why (softSkip / seamAbsent / behaviorGate), that is its
277
+ // disposition; if it said nothing, the runner resolves it to `blocked` with
278
+ // the marker detail — never to a pass. Floors still REJECT that row, so the
279
+ // honest bundle row and the pressure to say why both survive. The rule is
280
+ // `resolveFileRecord` (pinned by conformance-execution-witness.test.ts).
281
+ const { disposition, detail } = resolveFileRecord(states, gateReason, assertionCount, softSkipDisposition(file));
282
+ try {
283
+ recordRequirement(requirementIdForFile(file), disposition, detail, { assertionCount });
284
+ } catch {
285
+ /* a scenario that recorded its own file id first wins; never fail the file for bookkeeping */
286
+ }
287
+ _fileStates.delete(file);
288
+ _fileAssertions.delete(file);
289
+ _ledgerMarks.delete(file);
290
+ });
291
+