@openwop/openwop-conformance 1.135.4 → 1.136.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.
@@ -2,13 +2,16 @@
2
2
  "id": "conformance-mcp-tool-roundtrip",
3
3
  "name": "Conformance: MCP Tool Roundtrip",
4
4
  "version": "1.0",
5
- "description": "Track 6 fixture for `mcp-tool-roundtrip.test.ts`. Workflow invokes a single MCP tool (`echo`) on a host-configured MCP server. The conformance suite stands up a synthetic MCP server at startup; operators configure the host to use that server. The fixture's tool call MUST surface in the run's event log with the trust boundary intact per `threat-model-prompt-injection.md` §UNTRUSTED marker. Hosts that don't wire MCP-aware nodes can stub this fixture as `core.noop` the host-mediated subtest skips when no `tools/call` invocations reach the synthetic server.",
5
+ "description": "Track 6 fixture for `mcp-tool-roundtrip.test.ts`. Workflow invokes a single MCP tool (`echo`) on a host-configured MCP server. The conformance suite stands up a synthetic MCP server at startup; operators configure the host to use that server. The fixture's tool call MUST surface in the run's event log with the trust boundary intact per `threat-model-prompt-injection.md` §UNTRUSTED marker. Node `core.conformance.mcp-invoke` is a conformance-RESERVED typeId (renamed 2026-08-17 from `core.ai.callPrompt` + a `config.mcp` shape that vendor pack-tier node never read): a host that consumes MCP MUST map it to its own MCP invoke bridge node and honour `config.mcp { tool, arguments }`; a host that does not consume MCP MUST NOT advertise this fixture (the host-mediated leg then records inapplicable).",
6
6
  "nodes": [
7
7
  {
8
8
  "id": "mcp-call",
9
- "typeId": "core.ai.callPrompt",
9
+ "typeId": "core.conformance.mcp-invoke",
10
10
  "name": "Invoke MCP echo tool",
11
- "position": { "x": 0, "y": 0 },
11
+ "position": {
12
+ "x": 0,
13
+ "y": 0
14
+ },
12
15
  "config": {
13
16
  "mcp": {
14
17
  "tool": "echo",
@@ -22,11 +25,27 @@
22
25
  ],
23
26
  "edges": [],
24
27
  "triggers": [
25
- { "id": "manual", "type": "manual", "enabled": true }
28
+ {
29
+ "id": "manual",
30
+ "type": "manual",
31
+ "enabled": true
32
+ }
26
33
  ],
27
34
  "variables": [
28
- { "name": "text", "type": "string", "defaultValue": "roundtrip-probe" }
35
+ {
36
+ "name": "text",
37
+ "type": "string",
38
+ "defaultValue": "roundtrip-probe"
39
+ }
29
40
  ],
30
- "metadata": { "tags": ["conformance", "mcp", "roundtrip"] },
31
- "settings": { "timeout": 0 }
41
+ "metadata": {
42
+ "tags": [
43
+ "conformance",
44
+ "mcp",
45
+ "roundtrip"
46
+ ]
47
+ },
48
+ "settings": {
49
+ "timeout": 0
50
+ }
32
51
  }
package/fixtures.md CHANGED
@@ -102,7 +102,7 @@ All fixtures MUST advertise:
102
102
  | Orchestrator Dispatch | `conformance-orchestrator-dispatch` | Phase 5 — supervisor → `next-worker` → dispatch round-trip | `completed` | ≤ 60s |
103
103
  | Orchestrator Terminate | `conformance-orchestrator-terminate` | Phase 5 / CO-3 — terminate decision is final | `completed` | ≤ 30s |
104
104
  | Orchestrator Low-Confidence | `conformance-orchestrator-low-confidence` | Phase 5 / CP-1 — supervisor low-confidence suspend | `waiting-approval` (suspends) | unbounded (suspends) |
105
- | MCP Tool Roundtrip | `conformance-mcp-tool-roundtrip` | Track 6 — host invokes a tool on the conformance suite's synthetic MCP server; trust-boundary visibility in the event log | `completed` | ≤ 30s |
105
+ | MCP Tool Roundtrip | `conformance-mcp-tool-roundtrip` | Track 6 — host invokes a tool on the conformance suite's synthetic MCP server; trust-boundary visibility in the event log. Node `core.conformance.mcp-invoke` is a **conformance-RESERVED** typeId (renamed 2026-08-17 from `core.ai.callPrompt` with a `config.mcp` shape that vendor pack-tier node never read — it requires `promptId` and ignores `config.mcp`, so the fixture could not execute as declared on the first host that shipped the vendor node): a host that consumes MCP MUST map it to its own MCP invoke bridge node and honour `config.mcp { tool, arguments }`; a host that does not consume MCP MUST NOT advertise this fixture (the host-mediated leg then records inapplicable) — the same treatment as `core.conformance.a2a-invoke` | `completed` | ≤ 30s |
106
106
  | A2A Task Roundtrip | `conformance-a2a-task-roundtrip` | Track 6 — host consumes the conformance suite's synthetic A2A peer; covers drift points #3 (`AUTH_REQUIRED`) and #4 (`REJECTED`). Node `core.conformance.a2a-invoke` is a **conformance-RESERVED** typeId (renamed 2026-08-16 from `core.a2a.invoke`, a multi-segment `core.*` id no host shipped and the corpus never defined): a host that consumes A2A peers MUST map it to its A2A bridge node (a host-extension node — `a2a-integration.md` §"State projection"); a host that does not consume A2A MUST NOT advertise this fixture | `failed` or `waiting-input` (per `driftScenario` input) | ≤ 30s |
107
107
  | WASM Pack Roundtrip | `conformance-wasm-pack-roundtrip` | RFC 0008 — invokes `vendor.openwop.rust-hello.greet` (loaded WASM pack); exercises required exports + at least one import | `completed` | ≤ 10s |
108
108
  | WASM Pack Memory-Cap Breach | `conformance-wasm-pack-memory-cap-breach` | RFC 0008 §K — invokes the deliberately-misbehaving `vendor.openwop.misbehaving.memory-bomb` pack (allocates 1 GiB beyond the host's `memoryPagesMax`). Host MUST emit `cap.breached` with `kind: "wasm-memory"` and drive the run to terminal `failed`. Misbehaving pack lives at `openwop-examples:examples/packs/rust-misbehaving-memory/` (repo-qualified per the 2026-06 monorepo split — the `openwop-examples` sibling repo) and is fixture-only (NOT signed for registry publication). | `failed` (with `cap.breached`) | ≤ 10s |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.135.4",
3
+ "version": "1.136.0",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -1,5 +1,5 @@
1
1
  {
2
2
  "_comment": "Provenance of this vendored schemas/ copy. See conformance/README.md \u00a7\"Resolving the contract\". Compare against the stamp in your installed @openwop/openwop-conformance to detect a stale hand-copied contract.",
3
- "suiteVersion": "1.135.4",
4
- "corpusCommit": "d8f9be75925e189b56f9f0da7e3352cfee4d8e84"
3
+ "suiteVersion": "1.136.0",
4
+ "corpusCommit": "6e56e76c2abe74f8c56232f3163aba33bdd3db8c"
5
5
  }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The `capabilities.multiAgent.executionModel.version` ceiling, read from
3
+ * `capabilities.schema.json` so a scenario never restates it. Two scenarios
4
+ * carried a literal `<= 5` past RFC 0090 (version 6, Accepted 2026-06-08) and
5
+ * failed the very host that witnessed version 6 (S31, 2026-08-17).
6
+ */
7
+ import { readFileSync } from 'node:fs';
8
+ import { join } from 'node:path';
9
+ import { SCHEMAS_DIR } from './paths.js';
10
+
11
+ let cached: number | undefined;
12
+
13
+ export function executionModelVersionMax(): number {
14
+ if (cached !== undefined) return cached;
15
+ const schema = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'capabilities.schema.json'), 'utf8')) as {
16
+ properties?: { multiAgent?: { properties?: { executionModel?: { properties?: { version?: { maximum?: number } } } } } };
17
+ };
18
+ const max = schema.properties?.multiAgent?.properties?.executionModel?.properties?.version?.maximum;
19
+ if (typeof max !== 'number') throw new Error('capabilities.schema.json §multiAgent.executionModel.version has no `maximum`');
20
+ cached = max;
21
+ return max;
22
+ }
@@ -14,6 +14,7 @@
14
14
  import { describe, it, expect } from 'vitest';
15
15
  import { softSkip } from '../lib/soft-skip.js';
16
16
  import { driver } from '../lib/driver.js';
17
+ import { executionModelVersionMax } from '../lib/execution-model-version.js';
17
18
  import { readExecutionModelCap } from '../lib/agentLoop.js';
18
19
 
19
20
  describe('agent-loop-version5-shape: advertisement (RFC 0061 §A)', () => {
@@ -33,9 +34,13 @@ describe('agent-loop-version5-shape: advertisement (RFC 0061 §A)', () => {
33
34
  ).toBe(true);
34
35
  }
35
36
  if (typeof em.version === 'number') {
37
+ // S31 (2026-08-17): the ceiling is the schema's `maximum` (6 since RFC 0090,
38
+ // Accepted 2026-06-08 — MyndHyve was the version-6 witness and this leg failed
39
+ // it against a stale literal 5 for ten weeks). Read, never restate.
40
+ const max = executionModelVersionMax();
36
41
  expect(
37
- (em.version as number) >= 1 && (em.version as number) <= 5,
38
- driver.describe('capabilities.schema.json §multiAgent.executionModel', 'version MUST be within the 1–5 ladder'),
42
+ (em.version as number) >= 1 && (em.version as number) <= max,
43
+ driver.describe('capabilities.schema.json §multiAgent.executionModel', `version MUST be within the 1–${max} ladder (schema maximum)`),
39
44
  ).toBe(true);
40
45
  }
41
46
  });
@@ -75,6 +75,10 @@ import { pollUntilTerminal } from '../lib/polling.js';
75
75
  */
76
76
  export const REQUIRES_HOST_CALLBACK = "the host issues MCP JSON-RPC calls to the suite's fake server";
77
77
 
78
+ // The fixture's node is the conformance-RESERVED `core.conformance.mcp-invoke`
79
+ // (S33, 2026-08-17 — was `core.ai.callPrompt` + `config.mcp`, a vendor pack-tier
80
+ // node that never read that config): a host that consumes MCP maps it to its own
81
+ // bridge; one that does not MUST NOT advertise the fixture.
78
82
  const ROUNDTRIP_FIXTURE = 'conformance-mcp-tool-roundtrip';
79
83
 
80
84
  /**
@@ -188,13 +188,31 @@ describe.skipIf(BEHAVIORAL_SKIP)('multi-agent-confidence-escalation: behavioral
188
188
  'payload.escalationKind ∈ {clarify, escalate}',
189
189
  ).toBe(true);
190
190
 
191
- // Causation chain: escalation event causes back to the runOrchestrator.decided
192
- // that named the worker.
193
- const decidedEvent = events.find((e) => e.eventId === ev.causationId);
191
+ // Causation (S32, 2026-08-17): this leg used to REQUIRE `causationId` → a
192
+ // `runOrchestrator.decided`, which contradicts RFC 0011 §F CP-1 as asserted by
193
+ // orchestratorConservativePath.test.ts a low-confidence decision is HELD, so
194
+ // no `runOrchestrator.decided` exists on the log before ratification; the
195
+ // escalation event carries the decision verbatim in `originalDecision` for
196
+ // exactly that reason (multi-agent-execution.md §"Confidence escalation").
197
+ // Rule now: `causationId` MAY be absent; when present it MUST resolve to an
198
+ // event already on this run's log (a host that does emit a pre-ratification
199
+ // decided event may point at it; one that honours CP-1 points at nothing or at
200
+ // the preceding node event).
201
+ if (typeof ev.causationId === 'string' && ev.causationId.length > 0) {
202
+ const cause = events.find((e) => e.eventId === ev.causationId);
203
+ expect(
204
+ cause !== undefined,
205
+ driver.describe(
206
+ 'spec/v1/multi-agent-execution.md §"Confidence escalation"',
207
+ `confidence-escalated causationId (${ev.causationId}) MUST resolve to an event on this run's log when present`,
208
+ ),
209
+ ).toBe(true);
210
+ }
211
+ const original = (ev.payload as { originalDecision?: unknown } | undefined)?.originalDecision;
194
212
  expect(
195
- decidedEvent?.type,
196
- 'confidence-escalated causationId MUST point at the runOrchestrator.decided that surfaced the low-confidence decision',
197
- ).toBe('runOrchestrator.decided');
213
+ original !== null && typeof original === 'object',
214
+ driver.describe('run-event-payloads.schema.json §confidence-escalated', 'payload.originalDecision carries the escalated OrchestratorDecision verbatim — the decision is on the log HERE, not on a prior decided event'),
215
+ ).toBe(true);
198
216
 
199
217
  // Load-bearing: NO dispatch event fired. RFC 0039 gates BEFORE the loop.
200
218
  const chainEvents = events.filter((e) => e.type === 'core.workflowChain.event');
@@ -33,6 +33,7 @@
33
33
  import { describe, it, expect } from 'vitest';
34
34
  import { softSkip } from '../lib/soft-skip.js';
35
35
  import { driver } from '../lib/driver.js';
36
+ import { executionModelVersionMax } from '../lib/execution-model-version.js';
36
37
  import { isFixtureAdvertised } from '../lib/fixtures.js';
37
38
  import { pollUntilTerminal } from '../lib/polling.js';
38
39
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
@@ -81,11 +82,14 @@ describe.skipIf(HTTP_SKIP)('multi-agent-handoff-state-machine: advertisement sha
81
82
  ),
82
83
  ).toBe('number');
83
84
  const v = executionModel.version as number;
85
+ // S31 (2026-08-17): ceiling read from capabilities.schema.json (`maximum`, 6 since
86
+ // RFC 0090) instead of a literal that went stale the day version 6 was Accepted.
87
+ const max = executionModelVersionMax();
84
88
  expect(
85
- Number.isInteger(v) && v >= 1 && v <= 5,
89
+ Number.isInteger(v) && v >= 1 && v <= max,
86
90
  driver.describe(
87
91
  'RFCS/0037-multi-agent-execution-model.md §C',
88
- 'version MUST be an integer in [1, 5] (1 = Phase 1 only; Phases 2-5 lift the ceiling additively — Phase 5 = RFC 0061 stateful agent-loop lifecycle, matching `capabilities.schema.json` §multiAgent.executionModel.version maximum)',
92
+ `version MUST be an integer in [1, ${max}] (1 = Phase 1 only; later phases lift the ceiling additively — 5 = RFC 0061 stateful agent-loop lifecycle, 6 = RFC 0090 verifier; the ceiling is \`capabilities.schema.json\` §multiAgent.executionModel.version maximum)`,
89
93
  ),
90
94
  ).toBe(true);
91
95
  });
@@ -0,0 +1,72 @@
1
+ /**
2
+ * `node.failed` payload shape — `run-event-payloads.schema.json` §nodeFailed:
3
+ * `{ nodeId, error: { code, message, details?, retryable? }, attempts? }`. The
4
+ * error is an OBJECT (`_errorObject`), not a bare string, and it is REQUIRED.
5
+ *
6
+ * Why this exists (S34, 2026-08-17): nothing on the wire checked it. The second
7
+ * sibling host measured that it — and the reference hosts — emitted
8
+ * `node.failed { error: "<string>", code }` / `{ data: { code } }` for years
9
+ * while the schema said otherwise, and only noticed because a replay scenario
10
+ * happens to read `payload.error.code`. A schema nobody drives is a wish.
11
+ *
12
+ * Fixture-gated on `conformance-failure` (the run that fails at a node); a host
13
+ * that does not advertise the fixture records inapplicable. Every `node.failed`
14
+ * on that run's log MUST validate against §nodeFailed.
15
+ *
16
+ * @see spec/v1/rest-endpoints.md · schemas/run-event-payloads.schema.json
17
+ */
18
+
19
+ import { describe, it, expect } from 'vitest';
20
+ import { readFileSync } from 'node:fs';
21
+ import { join } from 'node:path';
22
+ import Ajv2020 from 'ajv/dist/2020.js';
23
+ import addFormats from 'ajv-formats';
24
+ import { driver } from '../lib/driver.js';
25
+ import { pollUntilTerminal } from '../lib/polling.js';
26
+ import { isFixtureAdvertised } from '../lib/fixtures.js';
27
+ import { SCHEMAS_DIR } from '../lib/paths.js';
28
+
29
+ const WORKFLOW_ID = 'conformance-failure';
30
+ const SKIP_NO_FIXTURE = !isFixtureAdvertised(WORKFLOW_ID);
31
+
32
+ function nodeFailedValidator() {
33
+ const payloads = JSON.parse(readFileSync(join(SCHEMAS_DIR, 'run-event-payloads.schema.json'), 'utf8')) as {
34
+ $id?: string;
35
+ $defs: Record<string, unknown>;
36
+ };
37
+ const ajv = new Ajv2020({ strict: false, allErrors: true });
38
+ addFormats(ajv);
39
+ const schema = { $schema: 'https://json-schema.org/draft/2020-12/schema', $id: 'urn:openwop:node-failed-leg', $defs: payloads.$defs, $ref: '#/$defs/nodeFailed' };
40
+ return ajv.compile(schema);
41
+ }
42
+
43
+ describe.skipIf(SKIP_NO_FIXTURE)('run-event-payloads.schema.json §nodeFailed — node.failed carries a structured error object', () => {
44
+ it('every node.failed on the conformance-failure run validates against §nodeFailed (error: { code, message } is an object, not a string)', async () => {
45
+ const create = await driver.post('/v1/runs', { workflowId: WORKFLOW_ID });
46
+ expect(create.status, driver.describe('rest-endpoints.md', 'POST /v1/runs MUST return 201 for the failing fixture')).toBe(201);
47
+ const runId = (create.json as { runId: string }).runId;
48
+ const terminal = await pollUntilTerminal(runId);
49
+ expect(terminal.status, driver.describe('fixtures.md conformance-failure §Terminal status', 'fixture MUST reach terminal `failed`')).toBe('failed');
50
+
51
+ const res = await driver.get(`/v1/runs/${encodeURIComponent(runId)}/events`);
52
+ expect(res.status).toBe(200);
53
+ const events = ((res.json as { events?: Array<{ type: string; payload?: unknown }> } | undefined)?.events ?? []);
54
+ const failed = events.filter((e) => e.type === 'node.failed');
55
+ expect(
56
+ failed.length,
57
+ driver.describe('rest-endpoints.md §Run events', 'a run that fails at a node MUST carry at least one node.failed event on its log'),
58
+ ).toBeGreaterThan(0);
59
+
60
+ const validate = nodeFailedValidator();
61
+ for (const ev of failed) {
62
+ const ok = validate(ev.payload);
63
+ expect(
64
+ ok,
65
+ driver.describe(
66
+ 'run-event-payloads.schema.json §nodeFailed',
67
+ `node.failed payload MUST validate — error is a REQUIRED object { code, message } (_errorObject), not a bare string or a data.code: ${JSON.stringify(validate.errors)} — payload: ${JSON.stringify(ev.payload).slice(0, 300)}`,
68
+ ),
69
+ ).toBe(true);
70
+ }
71
+ });
72
+ });