@openwop/openwop-conformance 1.101.0 → 1.103.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.
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.
|
|
4
|
-
"corpusCommit": "
|
|
3
|
+
"suiteVersion": "1.103.0",
|
|
4
|
+
"corpusCommit": "c6f779513a26705a5d9fd8958ec1cd75ab8f6fdb"
|
|
5
5
|
}
|
|
@@ -48,6 +48,9 @@ import { describe, it, expect } from 'vitest';
|
|
|
48
48
|
import { driver } from '../lib/driver.js';
|
|
49
49
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
50
50
|
import { isFixtureAdvertised } from '../lib/fixtures.js';
|
|
51
|
+
import { readFileSync } from 'node:fs';
|
|
52
|
+
import { join } from 'node:path';
|
|
53
|
+
import { V1_DIR } from '../lib/paths.js';
|
|
51
54
|
|
|
52
55
|
const WORKFLOW_ID = 'conformance-replay-side-effect';
|
|
53
56
|
const EFFECT_NODE = 'effect';
|
|
@@ -182,3 +185,65 @@ describe.skipIf(SKIP_NO_FIXTURE)('replay-side-effect-suppression: a replay does
|
|
|
182
185
|
).toBe(true);
|
|
183
186
|
});
|
|
184
187
|
});
|
|
188
|
+
|
|
189
|
+
/**
|
|
190
|
+
* `replay.md` requirement 4 — the manifest declaration is a classification FLOOR.
|
|
191
|
+
*
|
|
192
|
+
* A pack manifest may declare `role: "side-effect"`. Until 2026-08-14 the only
|
|
193
|
+
* stated purpose of that field was that it "drives engine scheduling", so a host
|
|
194
|
+
* was free to classify side-effecting nodes from a private list and never read
|
|
195
|
+
* it. A tier-1 host did exactly that and a shipped `core.storage.blob-put` node
|
|
196
|
+
* performed real object-store `PUT`s **during a replay**, past two independent
|
|
197
|
+
* guards, because neither consulted the manifest that had declared the node
|
|
198
|
+
* side-effecting all along.
|
|
199
|
+
*
|
|
200
|
+
* Requirement 4 makes the declaration binding: a host's own classifier is a floor
|
|
201
|
+
* ABOVE it and never a substitute — more nodes may be treated as side-effecting,
|
|
202
|
+
* never fewer.
|
|
203
|
+
*
|
|
204
|
+
* **This is a coherence gate, not a behavioral witness, and the distinction
|
|
205
|
+
* matters.** Proving a host honours requirement 4 needs that host advertising
|
|
206
|
+
* `recorded-outcome`, a pack node declaring the role, and an observable external
|
|
207
|
+
* effect across a replay — none of which the suite can synthesize. What it CAN
|
|
208
|
+
* do is ensure the declaration and the obligation cannot drift apart: if someone
|
|
209
|
+
* renames the role value, or drops the requirement, this fails. RFC 0148 §A
|
|
210
|
+
* resolves the behavioral half to `blocked`, not to a pass.
|
|
211
|
+
*/
|
|
212
|
+
describe.skipIf(V1_DIR === null)('replay.md req 4 — the manifest role is a classification floor', () => {
|
|
213
|
+
const dir = V1_DIR as string;
|
|
214
|
+
const replay = () => readFileSync(join(dir, 'replay.md'), 'utf8');
|
|
215
|
+
const manifest = () =>
|
|
216
|
+
readFileSync(join(dir, '..', '..', 'schemas', 'node-pack-manifest.schema.json'), 'utf8');
|
|
217
|
+
|
|
218
|
+
it('the manifest still offers the role value the requirement binds', () => {
|
|
219
|
+
// If `side-effect` is renamed or dropped from the taxonomy, requirement 4
|
|
220
|
+
// binds a value nothing can declare — a rule with no reachable trigger.
|
|
221
|
+
expect(
|
|
222
|
+
manifest().includes('side-effect'),
|
|
223
|
+
'node-pack-manifest.schema.json §role MUST still describe the `side-effect` value that ' +
|
|
224
|
+
'replay.md requirement 4 makes binding. A requirement whose trigger no longer exists is ' +
|
|
225
|
+
'not enforcement, it is decoration.',
|
|
226
|
+
).toBe(true);
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
it('replay.md binds that declaration, naming the schema it comes from', () => {
|
|
230
|
+
const doc = replay();
|
|
231
|
+
expect(
|
|
232
|
+
/MUST\*{0,2} be treated as side-effecting/.test(doc) && doc.includes('node-pack-manifest.schema.json'),
|
|
233
|
+
'replay.md §"Requirements when a host declares `sideEffectSuppression: \\"recorded-outcome\\"`" ' +
|
|
234
|
+
'MUST bind a manifest-declared `role: "side-effect"` to the suppression obligation, and MUST ' +
|
|
235
|
+
'name the schema the declaration comes from so a reader can find it.',
|
|
236
|
+
).toBe(true);
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('the floor direction is stated, not left to inference', () => {
|
|
240
|
+
// The dangerous reading is symmetric: "my classifier disagrees, so the
|
|
241
|
+
// manifest is wrong." The requirement is one-directional and says so.
|
|
242
|
+
expect(
|
|
243
|
+
replay().includes('MUST NOT classify fewer'),
|
|
244
|
+
'replay.md requirement 4 MUST state the direction: a host classifier may treat MORE nodes as ' +
|
|
245
|
+
'side-effecting and never fewer. Without the direction, a host that trusts its own list over ' +
|
|
246
|
+
'the manifest can read the clause as permission to disagree.',
|
|
247
|
+
).toBe(true);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
@@ -122,11 +122,54 @@ describe.skipIf(SKIP_TIMEOUT)('run-execution-bounds: run-duration breach (RFC 00
|
|
|
122
122
|
'run-event-payloads.schema.json §capBreached.kind',
|
|
123
123
|
'cap.breached payload MUST carry kind="run-duration"',
|
|
124
124
|
)).toBe('run-duration');
|
|
125
|
+
// Three distinct failure modes, asserted separately and WITH THE VALUES.
|
|
126
|
+
//
|
|
127
|
+
// The previous form ANDed all three into one boolean over a message that
|
|
128
|
+
// named none of them, so a failure said only "MUST be strictly greater" —
|
|
129
|
+
// you could not tell whether `observed` was missing, equal, or smaller. A
|
|
130
|
+
// tier-1 host hit this intermittently and had to reason out the mechanism
|
|
131
|
+
// from first principles, because the assertion about observed values did
|
|
132
|
+
// not report the observed values.
|
|
125
133
|
expect(
|
|
126
|
-
typeof payload?.observed
|
|
134
|
+
typeof payload?.observed,
|
|
127
135
|
driver.describe(
|
|
128
136
|
'run-event-payloads.schema.json §capBreached.observed',
|
|
129
|
-
|
|
137
|
+
`cap.breached MUST carry a numeric \`observed\`; got ${JSON.stringify(payload?.observed)}`,
|
|
138
|
+
),
|
|
139
|
+
).toBe('number');
|
|
140
|
+
expect(
|
|
141
|
+
typeof payload?.limit,
|
|
142
|
+
driver.describe(
|
|
143
|
+
'run-event-payloads.schema.json §capBreached.limit',
|
|
144
|
+
`cap.breached MUST carry a numeric \`limit\`; got ${JSON.stringify(payload?.limit)}`,
|
|
145
|
+
),
|
|
146
|
+
).toBe('number');
|
|
147
|
+
|
|
148
|
+
// `capabilities.md` §"Engine-enforced limits": *"Always strictly greater
|
|
149
|
+
// than limit."* This is satisfiable and it constrains the host's comparison:
|
|
150
|
+
// breach when elapsed EXCEEDS the deadline, not when it reaches it. A host
|
|
151
|
+
// testing `elapsed >= limit` emits `observed === limit` exactly when the
|
|
152
|
+
// clock lands on the boundary — which is rare, machine-dependent, and
|
|
153
|
+
// therefore reads as flake rather than as the deterministic defect it is.
|
|
154
|
+
//
|
|
155
|
+
// That asymmetry is why the diagnosis belongs in the message: system load
|
|
156
|
+
// makes elapsed LARGER, so it makes this assertion easier to satisfy, not
|
|
157
|
+
// harder. An `observed === limit` failure is not a loaded box — it is a
|
|
158
|
+
// `>=` comparison in the host.
|
|
159
|
+
const { observed = NaN, limit = NaN } = payload ?? {};
|
|
160
|
+
expect(
|
|
161
|
+
observed > limit,
|
|
162
|
+
driver.describe(
|
|
163
|
+
'run-event-payloads.schema.json §capBreached.observed',
|
|
164
|
+
`observed (elapsedMs) MUST be strictly greater than limit (resolved timeout). ` +
|
|
165
|
+
`Got observed=${observed}, limit=${limit}` +
|
|
166
|
+
(observed === limit
|
|
167
|
+
? '. They are EQUAL, which means the host breached at `elapsed >= limit` rather than ' +
|
|
168
|
+
'`elapsed > limit`. The limit is not breached until it has been passed. This is ' +
|
|
169
|
+
'deterministic in the host and only surfaces when the clock lands exactly on the ' +
|
|
170
|
+
'boundary, so it presents as an intermittent failure — load makes elapsed larger and ' +
|
|
171
|
+
'therefore makes this assertion PASS more often, not less.'
|
|
172
|
+
: '.'),
|
|
130
173
|
),
|
|
131
174
|
).toBe(true);
|
|
132
175
|
});
|