@openwop/openwop-conformance 1.72.0 → 1.72.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/package.json
CHANGED
|
@@ -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.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.72.1",
|
|
4
|
+
"corpusCommit": "bec8356db2c093d3e7cfbb540870815e69b20ebc"
|
|
5
5
|
}
|
|
@@ -9,15 +9,25 @@
|
|
|
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
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
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 is seam-free: it registers a workflow whose variable declares `format:"email"`
|
|
22
|
+
* with an off-format `defaultValue`, runs it, and asserts the run COMPLETES — a
|
|
23
|
+
* value mismatch MUST NOT fail the run (requirement 3, `format` advisory not
|
|
24
|
+
* validating). A format-validating host reds.
|
|
17
25
|
*
|
|
18
26
|
* NON-VACUITY: leg A2 is the one that would silently pass if `format` were declared as an
|
|
19
27
|
* 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.
|
|
28
|
+
* `"enum": [...]` to the schema property reds A2 alone and leaves A1 green. B1 reds on a
|
|
29
|
+
* host that mints the variable but drops `format` (step-4-unwired); B2 reds on a host that
|
|
30
|
+
* validates a variable value against its advisory `format`.
|
|
21
31
|
*
|
|
22
32
|
* @see schemas/workflow-definition.schema.json §WorkflowVariable
|
|
23
33
|
* @see spec/v1/workflow-chain-packs.md §"Deferred-parameter expansion (RFC 0124)" step 1
|
|
@@ -32,6 +42,7 @@ import addFormats from 'ajv-formats';
|
|
|
32
42
|
import { SCHEMAS_DIR } from '../lib/paths.js';
|
|
33
43
|
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
34
44
|
import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
|
|
45
|
+
import { driver } from '../lib/driver.js';
|
|
35
46
|
|
|
36
47
|
const cite = (section: string, requirement: string): string => `${section} — ${requirement}`;
|
|
37
48
|
const WORKFLOW_DEF = join(SCHEMAS_DIR, 'workflow-definition.schema.json');
|
|
@@ -132,24 +143,99 @@ describe('workflow-variable-format §A: corpus (RFC 0136, always-on)', () => {
|
|
|
132
143
|
});
|
|
133
144
|
|
|
134
145
|
describe('workflow-variable-format §B: host behaviour (RFC 0136, capability-gated)', () => {
|
|
135
|
-
|
|
136
|
-
|
|
146
|
+
// RFC 0124's deferred-expand witness seam (referenced from capabilities.md §deferredParameters).
|
|
147
|
+
const DEFERRED_EXPAND_SEAM = '/v1/host/sample/chain/deferred-expand';
|
|
148
|
+
|
|
149
|
+
// Gate open only when a reachable host advertises deferredParameters. A missing base URL
|
|
150
|
+
// (server-free) makes the discovery fetch throw — treat that as "no host" and skip, so
|
|
151
|
+
// these host legs never error the full-suite run; the server-free gate covers §A.
|
|
152
|
+
async function deferredParamsGateOpen(): Promise<boolean> {
|
|
153
|
+
let wcp: { deferredParameters?: { supported?: boolean } } | undefined;
|
|
154
|
+
try {
|
|
155
|
+
wcp = await readCapabilityFamily<{ deferredParameters?: { supported?: boolean } }>('workflowChainPacks');
|
|
156
|
+
} catch {
|
|
157
|
+
return false;
|
|
158
|
+
}
|
|
137
159
|
const deferred = wcp?.deferredParameters?.supported === true;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
160
|
+
return behaviorGate('workflowChainPacks.deferredParameters.supported', deferred);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
it('B1 — deferred expansion mints `format` onto the WorkflowVariable: string copied (req 7), unknown copied (req 2), non-string omits it (req 1)', async () => {
|
|
164
|
+
if (!(await deferredParamsGateOpen())) return;
|
|
165
|
+
|
|
166
|
+
// Drive the RFC 0124 deferred-expand seam with a chain whose parameters exercise all
|
|
167
|
+
// three propagation rules in one expansion; assert the minted variables[] carry (or omit)
|
|
168
|
+
// `format` per the "present iff minted" contract.
|
|
169
|
+
const res = await driver.post(DEFERRED_EXPAND_SEAM, {
|
|
170
|
+
chain: {
|
|
171
|
+
parameters: {
|
|
172
|
+
type: 'object',
|
|
173
|
+
properties: {
|
|
174
|
+
email: { type: 'string', format: 'email' },
|
|
175
|
+
note: { type: 'string', format: 'vendor.acme.freeform' },
|
|
176
|
+
count: { type: 'number' },
|
|
177
|
+
},
|
|
178
|
+
},
|
|
179
|
+
},
|
|
180
|
+
});
|
|
181
|
+
// A host advertising deferredParameters but not yet serving the variables[]-returning
|
|
182
|
+
// seam extension soft-skips (404), as does a host with the seam disabled in this boot.
|
|
183
|
+
if (res.status === 404) return;
|
|
184
|
+
expect(res.status, cite('§Deferred-parameter expansion', 'the deferred-expand seam returns 200')).toBe(200);
|
|
185
|
+
const variables = (res.json as { variables?: Array<{ name: string; type?: string; format?: string }> }).variables;
|
|
186
|
+
if (!Array.isArray(variables)) return; // seam present but not returning variables[] yet — soft-skip
|
|
187
|
+
const fmt = (n: string): string | undefined => variables.find((v) => v.name === n)?.format;
|
|
188
|
+
|
|
189
|
+
expect(fmt('email'), cite('§WorkflowVariable', 'req 7: a string param\'s recognised `format` is minted verbatim')).toBe('email');
|
|
190
|
+
expect(fmt('note'), cite('§WorkflowVariable', 'req 2: an UNRECOGNISED `format` is minted verbatim, never dropped')).toBe('vendor.acme.freeform');
|
|
191
|
+
expect(fmt('count'), cite('§WorkflowVariable', 'req 1: a NON-STRING param mints no `format`')).toBeUndefined();
|
|
144
192
|
});
|
|
145
193
|
|
|
146
|
-
it('B2 — a value
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
//
|
|
151
|
-
//
|
|
152
|
-
//
|
|
153
|
-
|
|
194
|
+
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 () => {
|
|
195
|
+
if (!(await deferredParamsGateOpen())) return;
|
|
196
|
+
|
|
197
|
+
// Seam-free: a fully-valid workflow whose variable declares `format: "email"` but carries
|
|
198
|
+
// an off-format `defaultValue`. `format` is advisory (open string, RFC 0136 req 3) — the
|
|
199
|
+
// host MUST NOT reject the definition or fail the run on the mismatch. A format-validating
|
|
200
|
+
// host reds here.
|
|
201
|
+
const wf = {
|
|
202
|
+
id: 'conformance-0136-format-advisory',
|
|
203
|
+
name: 'RFC 0136 format-advisory witness',
|
|
204
|
+
version: '1.0',
|
|
205
|
+
nodes: [{ id: 'n1', typeId: 'core.identity', name: 'noop', position: { x: 0, y: 0 }, config: {}, inputs: {} }],
|
|
206
|
+
edges: [],
|
|
207
|
+
triggers: [{ id: 'manual', type: 'manual', enabled: true }],
|
|
208
|
+
variables: [{ name: 'recipientEmail', type: 'string', format: 'email', defaultValue: 'not-an-email' }],
|
|
209
|
+
metadata: { tags: ['conformance', 'rfc-0136'] },
|
|
210
|
+
settings: { timeout: 5000 },
|
|
211
|
+
};
|
|
212
|
+
const create = await driver.post('/v1/workflows', wf);
|
|
213
|
+
if (create.status === 404) return; // host exposes no workflow-registration surface — soft-skip
|
|
214
|
+
expect(
|
|
215
|
+
[200, 201],
|
|
216
|
+
cite('POST /v1/workflows', 'req 3: a definition whose variable value violates its advisory `format` is accepted, not rejected'),
|
|
217
|
+
).toContain(create.status);
|
|
218
|
+
const created = create.json as { workflowId?: string; id?: string };
|
|
219
|
+
const workflowId = created.workflowId ?? created.id ?? wf.id;
|
|
220
|
+
|
|
221
|
+
const run = await driver.post('/v1/runs', { workflowId });
|
|
222
|
+
if (run.status === 404) return;
|
|
223
|
+
expect([200, 201], cite('POST /v1/runs', 'the run is accepted despite the off-format variable value')).toContain(run.status);
|
|
224
|
+
const runId = (run.json as { runId: string }).runId;
|
|
225
|
+
|
|
226
|
+
let snap: { status: string } | undefined;
|
|
227
|
+
for (let i = 0; i < 40; i++) {
|
|
228
|
+
const r = await driver.get(`/v1/runs/${encodeURIComponent(runId)}`);
|
|
229
|
+
const body = r.json as { status: string };
|
|
230
|
+
if (['completed', 'failed', 'waiting-approval'].includes(body.status)) {
|
|
231
|
+
snap = body;
|
|
232
|
+
break;
|
|
233
|
+
}
|
|
234
|
+
await new Promise((resolve) => setTimeout(resolve, 100));
|
|
235
|
+
}
|
|
236
|
+
expect(
|
|
237
|
+
snap?.status,
|
|
238
|
+
cite('§WorkflowVariable', 'req 3: the run COMPLETES — `format` is advisory, a value mismatch MUST NOT fail the run'),
|
|
239
|
+
).toBe('completed');
|
|
154
240
|
});
|
|
155
241
|
});
|