@openwop/openwop-conformance 1.72.0 → 1.72.2

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.
@@ -0,0 +1,27 @@
1
+ {
2
+ "id": "conformance-workflow-variable-format-advisory",
3
+ "name": "Conformance: WorkflowVariable.format is advisory (RFC 0136)",
4
+ "version": "1.0",
5
+ "description": "RFC 0136 requirement 3 witness. A single-node workflow whose variable `recipientEmail` declares `format: \"email\"` but carries an off-format `defaultValue` (\"not-an-email\"). `format` is an ADVISORY presentational hint, never a validation constraint — a host MUST NOT reject the definition or fail the run on the value/format mismatch. The scenario runs this fixture via POST /v1/runs (the portable pre-registered-workflow pattern, no host-specific create endpoint) and asserts the run reaches `completed`; a host that validates a variable value against its advisory `format` reds. Vendored (not created via POST /v1/workflows) because registration is 'POST /v1/workflows or equivalent' per capabilities.md — the create path is not a mandated portable surface.",
6
+ "nodes": [
7
+ {
8
+ "id": "n1",
9
+ "typeId": "core.identity",
10
+ "name": "Noop (format-advisory witness)",
11
+ "position": { "x": 0, "y": 0 },
12
+ "config": {},
13
+ "inputs": {}
14
+ }
15
+ ],
16
+ "edges": [],
17
+ "triggers": [
18
+ { "id": "manual", "type": "manual", "enabled": true }
19
+ ],
20
+ "variables": [
21
+ { "name": "recipientEmail", "type": "string", "format": "email", "defaultValue": "not-an-email" }
22
+ ],
23
+ "metadata": {
24
+ "tags": ["conformance", "rfc-0136", "workflow-variable-format"]
25
+ },
26
+ "settings": { "timeout": 5000 }
27
+ }
package/fixtures.md CHANGED
@@ -69,6 +69,7 @@ All fixtures MUST advertise:
69
69
  | Agent Pack Export | `conformance-agent-pack-export` | Phase 2 — workspace agents project to AgentManifest at `GET /v1/packs/export` | `completed` | ≤ 5s |
70
70
  | Agent Pack Provenance | `conformance-agent-pack-provenance` | Phase 2 — `sourceManifestId` provenance round-trip | `completed` | ≤ 10s |
71
71
  | Agent Pack Handoff Schema Validation | `conformance-agent-pack-handoff-schema-validation` | Phase 2 / HV-1 — host validates dispatch payloads against `handoff.taskSchemaRef` AND return payloads against `handoff.returnSchemaRef` per RFC 0003 §D. Three branches: valid-task → `completed`; invalid-task → `failed` with structured violation; mock-return-violation → violation surfaced before persistence. | varies by scenario | ≤ 5s |
72
+ | WorkflowVariable format advisory | `conformance-workflow-variable-format-advisory` | RFC 0136 req 3 — a variable declares `format: "email"` with an off-format `defaultValue`; `format` is advisory, so the run MUST complete (a value/format mismatch MUST NOT fail the run). Run via `POST /v1/runs` (portable pre-registered pattern). `workflow-variable-format.test.ts` leg B2. | `completed` | ≤ 5s |
72
73
  | Dispatch Input Mapping | `conformance-dispatch-input-mapping` | RFC 0022 §A / HVMAP-1a — host honors `inputMapping` on `core.dispatch`. Capability-gated on `capabilities.agents.dispatchMapping`. | `completed` | ≤ 5s |
73
74
  | Dispatch Output Mapping | `conformance-dispatch-output-mapping` | RFC 0022 §A / HVMAP-1b — host harvests child variables via `outputMapping` on `core.dispatch`. Capability-gated on `capabilities.agents.dispatchMapping`. | `completed` | ≤ 5s |
74
75
  | Dispatch Cross-Worker Handoff | `conformance-dispatch-cross-worker-handoff` | RFC 0022 §A / HVMAP-1c — sequential fan-out: child-a writes via `perWorkerOutputMappings`, child-b reads via `perWorkerInputMappings`. Capability-gated on `capabilities.agents.dispatchMapping`. | `completed` | ≤ 10s |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.72.0",
3
+ "version": "1.72.2",
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.72.0",
4
- "corpusCommit": "93d4692eb1e28244b860da6ddcb6521b57a712b3"
3
+ "suiteVersion": "1.72.2",
4
+ "corpusCommit": "e58d3553617279966cb010b68d7ac4caf8110ec7"
5
5
  }
@@ -9,15 +9,29 @@
9
9
  * an unknown hint into a hard `POST /v1/workflows` failure); `format` composes with
10
10
  * `sensitive` on one variable; and `workflow-chain-packs.md` documents the
11
11
  * deferred-mode propagation as a mode-scoped MUST.
12
- * B. Capability-gated host leg a host that mints variables from chain parameters
13
- * (`workflowChainPacks.deferredParameters`) copies a parameter's `format` verbatim
14
- * onto the materialized `WorkflowVariable`, and a run whose value does not match its
15
- * declared `format` is still accepted (requirement 3 — the assertion that keeps
16
- * `format` advisory rather than validating).
12
+ * B. Capability-gated host legs (`workflowChainPacks.deferredParameters.supported`)
13
+ * real behavioral drives, not advert re-checks:
14
+ * B1 drives the RFC 0124 deferred-expand seam
15
+ * (`POST /v1/host/sample/chain/deferred-expand`) and asserts the minted
16
+ * `variables[]` carry `format` per the "present iff minted" contract — a string
17
+ * param's `format` copied verbatim (req 7), an unrecognised value copied too
18
+ * (req 2), a non-string param minting no `format` (req 1). Soft-skips (404 /
19
+ * absent `variables`) on a host that advertises the flag but hasn't wired the
20
+ * seam's `variables[]` extension.
21
+ * B2 runs the vendored fixture `conformance-workflow-variable-format-advisory`
22
+ * (a workflow whose variable declares `format:"email"` with an off-format
23
+ * `defaultValue`) via `POST /v1/runs` — the portable pre-registered pattern, NOT a
24
+ * `POST /v1/workflows` create (registration is "POST /v1/workflows or equivalent",
25
+ * capabilities.md, so the create path is not a mandated portable surface). It asserts
26
+ * the run COMPLETES — a value/format mismatch MUST NOT fail the run (requirement 3,
27
+ * `format` advisory not validating). Gated on fixture advertisement; a
28
+ * format-validating host reds.
17
29
  *
18
30
  * NON-VACUITY: leg A2 is the one that would silently pass if `format` were declared as an
19
31
  * enum — it asserts that a value OUTSIDE the recognised table validates. Sabotage: adding
20
- * `"enum": [...]` to the schema property reds A2 alone and leaves A1 green.
32
+ * `"enum": [...]` to the schema property reds A2 alone and leaves A1 green. B1 reds on a
33
+ * host that mints the variable but drops `format` (step-4-unwired); B2 reds on a host that
34
+ * validates a variable value against its advisory `format`.
21
35
  *
22
36
  * @see schemas/workflow-definition.schema.json §WorkflowVariable
23
37
  * @see spec/v1/workflow-chain-packs.md §"Deferred-parameter expansion (RFC 0124)" step 1
@@ -32,6 +46,8 @@ import addFormats from 'ajv-formats';
32
46
  import { SCHEMAS_DIR } from '../lib/paths.js';
33
47
  import { behaviorGate } from '../lib/behavior-gate.js';
34
48
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
49
+ import { isFixtureAdvertised } from '../lib/fixtures.js';
50
+ import { driver } from '../lib/driver.js';
35
51
 
36
52
  const cite = (section: string, requirement: string): string => `${section} — ${requirement}`;
37
53
  const WORKFLOW_DEF = join(SCHEMAS_DIR, 'workflow-definition.schema.json');
@@ -132,24 +148,86 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
132
148
  });
133
149
 
134
150
  describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gated)', () => {
135
- it('B1 a host minting variables from chain parameters copies `format` verbatim', async () => {
136
- const wcp = await readCapabilityFamily<{ deferredParameters?: { supported?: boolean } }>('workflowChainPacks');
151
+ // RFC 0124's deferred-expand witness seam (referenced from capabilities.md §deferredParameters).
152
+ const DEFERRED_EXPAND_SEAM = '/v1/host/sample/chain/deferred-expand';
153
+
154
+ // Gate open only when a reachable host advertises deferredParameters. A missing base URL
155
+ // (server-free) makes the discovery fetch throw — treat that as "no host" and skip, so
156
+ // these host legs never error the full-suite run; the server-free gate covers §A.
157
+ async function deferredParamsGateOpen(): Promise<boolean> {
158
+ let wcp: { deferredParameters?: { supported?: boolean } } | undefined;
159
+ try {
160
+ wcp = await readCapabilityFamily<{ deferredParameters?: { supported?: boolean } }>('workflowChainPacks');
161
+ } catch {
162
+ return false;
163
+ }
137
164
  const deferred = wcp?.deferredParameters?.supported === true;
138
- if (!behaviorGate('workflowChainPacks.deferredParameters.supported', deferred)) return;
139
- // Behavioral leg — exercised once a host implements RFC 0136 step 4: a chain whose
140
- // `parameters` declares `format: "email"` on a string parameter expands in deferred
141
- // mode, and the materialized WorkflowVariable carries `format: "email"` verbatim.
142
- // Propagation is a MUST only here — the expansion-time floor mints no variable.
143
- expect(deferred, 'host advertising deferredParameters propagates RFC 0136 `format`').toBe(true);
165
+ return behaviorGate('workflowChainPacks.deferredParameters.supported', deferred);
166
+ }
167
+
168
+ it('B1 deferred expansion mints `format` onto the WorkflowVariable: string copied (req 7), unknown copied (req 2), non-string omits it (req 1)', async () => {
169
+ if (!(await deferredParamsGateOpen())) return;
170
+
171
+ // Drive the RFC 0124 deferred-expand seam with a chain whose parameters exercise all
172
+ // three propagation rules in one expansion; assert the minted variables[] carry (or omit)
173
+ // `format` per the "present iff minted" contract.
174
+ const res = await driver.post(DEFERRED_EXPAND_SEAM, {
175
+ chain: {
176
+ parameters: {
177
+ type: 'object',
178
+ properties: {
179
+ email: { type: 'string', format: 'email' },
180
+ note: { type: 'string', format: 'vendor.acme.freeform' },
181
+ count: { type: 'number' },
182
+ },
183
+ },
184
+ },
185
+ });
186
+ // A host advertising deferredParameters but not yet serving the variables[]-returning
187
+ // seam extension soft-skips (404), as does a host with the seam disabled in this boot.
188
+ if (res.status === 404) return;
189
+ expect(res.status, cite('§Deferred-parameter expansion', 'the deferred-expand seam returns 200')).toBe(200);
190
+ const variables = (res.json as { variables?: Array<{ name: string; type?: string; format?: string }> }).variables;
191
+ if (!Array.isArray(variables)) return; // seam present but not returning variables[] yet — soft-skip
192
+ const fmt = (n: string): string | undefined => variables.find((v) => v.name === n)?.format;
193
+
194
+ expect(fmt('email'), cite('§WorkflowVariable', 'req 7: a string param\'s recognised `format` is minted verbatim')).toBe('email');
195
+ expect(fmt('note'), cite('§WorkflowVariable', 'req 2: an UNRECOGNISED `format` is minted verbatim, never dropped')).toBe('vendor.acme.freeform');
196
+ expect(fmt('count'), cite('§WorkflowVariable', 'req 1: a NON-STRING param mints no `format`')).toBeUndefined();
144
197
  });
145
198
 
146
- it('B2 a value that does not match its declared `format` is still accepted (requirement 3)', async () => {
147
- const wcp = await readCapabilityFamily<{ deferredParameters?: { supported?: boolean } }>('workflowChainPacks');
148
- const deferred = wcp?.deferredParameters?.supported === true;
149
- if (!behaviorGate('workflowChainPacks.deferredParameters.supported', deferred)) return;
150
- // The assertion that keeps `format` advisory: a run supplying "not-an-email" for a
151
- // variable declared `format: "email"` MUST be accepted and complete. A host that
152
- // validates against `format` reds here which is the point.
153
- expect(deferred, 'host MUST NOT reject a run on a `format` mismatch').toBe(true);
199
+ const B2_FIXTURE = 'conformance-workflow-variable-format-advisory';
200
+
201
+ it('B2 a run whose variable value does not match its declared `format` is accepted and completes (req 3: `format` is advisory, not validating)', async () => {
202
+ // Portable pre-registered-workflow pattern (cf. agentPackHandoffSchemaValidation): the
203
+ // fixture `conformance-workflow-variable-format-advisory` carries a variable declaring
204
+ // `format:"email"` with an off-format `defaultValue`, run via POST /v1/runs — NO create
205
+ // endpoint, since registration is "POST /v1/workflows or equivalent" (capabilities.md)
206
+ // and the create path is not a mandated portable surface. Gate on fixture advertisement:
207
+ // req 3 is universal (not deferred-mode-specific), so it rides the host loading the fixture.
208
+ if (!isFixtureAdvertised(B2_FIXTURE)) return;
209
+
210
+ const run = await driver.post('/v1/runs', { workflowId: B2_FIXTURE });
211
+ if (run.status === 404) return; // no run surface — soft-skip
212
+ expect(
213
+ [200, 201],
214
+ cite('POST /v1/runs', 'req 3: a run whose variable value violates its advisory `format` is accepted, not rejected'),
215
+ ).toContain(run.status);
216
+ const runId = (run.json as { runId: string }).runId;
217
+
218
+ let snap: { status: string } | undefined;
219
+ for (let i = 0; i < 40; i++) {
220
+ const r = await driver.get(`/v1/runs/${encodeURIComponent(runId)}`);
221
+ const body = r.json as { status: string };
222
+ if (['completed', 'failed', 'waiting-approval'].includes(body.status)) {
223
+ snap = body;
224
+ break;
225
+ }
226
+ await new Promise((resolve) => setTimeout(resolve, 100));
227
+ }
228
+ expect(
229
+ snap?.status,
230
+ cite('§WorkflowVariable', 'req 3: the run COMPLETES — `format` is advisory, a value mismatch MUST NOT fail the run'),
231
+ ).toBe('completed');
154
232
  });
155
233
  });