@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.
- package/README.md +3 -3
- package/coverage.md +6 -0
- package/dist/cli.js +89 -45
- package/dist/lib/certification-bundle-verify.js +274 -0
- package/dist/lib/profiles.js +102 -19
- package/dist/lib/requirement-ledger.js +182 -0
- package/dist/lib/requirement-registry.js +110 -0
- package/dist/lib/scenario-disposition.js +249 -0
- package/dist/lib/soft-skip.js +82 -0
- package/fixtures/conformance-a2a-task-roundtrip.json +1 -1
- package/fixtures.md +1 -1
- package/package.json +1 -1
- package/schemas/CORPUS-STAMP.json +2 -2
- package/schemas/README.md +1 -0
- package/schemas/capabilities.schema.json +2 -2
- package/schemas/compensation-policy.schema.json +64 -0
- package/schemas/run-snapshot.schema.json +5 -0
- package/schemas/workflow-chain-pack-manifest.schema.json +125 -0
- package/schemas/workflow-definition.schema.json +4 -0
- package/src/cli.ts +99 -45
- package/src/lib/a2a-fake-peer.ts +290 -110
- package/src/lib/certification-bundle-verify.ts +356 -0
- package/src/lib/llm-cache-key-recipe.ts +6 -5
- package/src/lib/mcp-fake-server.ts +235 -72
- package/src/lib/profiles.ts +132 -19
- package/src/lib/requirement-ledger.ts +98 -1
- package/src/lib/requirement-registry.ts +47 -4
- package/src/lib/scenario-disposition.ts +291 -0
- package/src/lib/soft-skip.ts +89 -0
- package/src/lib/workflow-chain-expansion.ts +189 -0
- package/src/scenarios/a2a-1-0-agent-card.test.ts +179 -0
- package/src/scenarios/a2a-1-0-task-roundtrip.test.ts +109 -0
- package/src/scenarios/a2a-card-runtime-consistency.test.ts +167 -0
- package/src/scenarios/a2a-peer-authority.test.ts +99 -0
- package/src/scenarios/a2a-task-roundtrip.test.ts +18 -6
- package/src/scenarios/agent-loop-iteration-monotonic.test.ts +3 -2
- package/src/scenarios/agent-loop-stateful-resume.test.ts +3 -2
- package/src/scenarios/agent-loop-version5-shape.test.ts +2 -1
- package/src/scenarios/agent-loop-workspace-snapshot.test.ts +4 -3
- package/src/scenarios/approval-gate-flow.test.ts +5 -4
- package/src/scenarios/artifact-type-store-without-render.test.ts +2 -1
- package/src/scenarios/auth-scim-profile.test.ts +5 -4
- package/src/scenarios/authorization-fail-closed.test.ts +4 -3
- package/src/scenarios/certification-bundle-non-vacuous.test.ts +249 -0
- package/src/scenarios/certification-bundle-redaction.test.ts +182 -0
- package/src/scenarios/certification-floor-enforcement.test.ts +30 -6
- package/src/scenarios/chain-compensation-expansion.test.ts +243 -0
- package/src/scenarios/commitment-fired.test.ts +3 -2
- package/src/scenarios/compensation-behavior.test.ts +63 -0
- package/src/scenarios/compensation-profile.test.ts +144 -1
- package/src/scenarios/conformance-advertised-seam-required.test.ts +118 -0
- package/src/scenarios/conformance-execution-witness.test.ts +104 -0
- package/src/scenarios/core-manifest-and-extension-registry.test.ts +26 -0
- package/src/scenarios/credential-payload-redaction.test.ts +4 -3
- package/src/scenarios/credentials-capability-shape.test.ts +4 -3
- package/src/scenarios/cross-engine-append-behavior.test.ts +5 -4
- package/src/scenarios/cross-engine-append-ordering.test.ts +3 -2
- package/src/scenarios/cross-host-ancestry-endpoint.test.ts +5 -4
- package/src/scenarios/cross-host-causation-shape.test.ts +3 -2
- package/src/scenarios/cross-workspace-isolation.test.ts +4 -3
- package/src/scenarios/deadletter-retry-exhaustion.test.ts +5 -4
- package/src/scenarios/distillation-index-roundtrip.test.ts +3 -2
- package/src/scenarios/distillation-secret-carryforward.test.ts +3 -2
- package/src/scenarios/distillation-shape.test.ts +2 -1
- package/src/scenarios/distillation-stable-archive.test.ts +4 -3
- package/src/scenarios/distillation-token-budget.test.ts +4 -3
- package/src/scenarios/feedback-correction-redaction.test.ts +4 -3
- package/src/scenarios/feedback-cross-tenant-isolation.test.ts +4 -3
- package/src/scenarios/feedback-fork-not-copied.test.ts +6 -5
- package/src/scenarios/feedback-on-terminal-run.test.ts +4 -3
- package/src/scenarios/feedback-record-and-list.test.ts +4 -3
- package/src/scenarios/feedback-unsupported-501.test.ts +3 -2
- package/src/scenarios/fixtures-valid.test.ts +16 -18
- package/src/scenarios/heartbeat-capability-shape.test.ts +2 -1
- package/src/scenarios/heartbeat-fires-once-per-tick.test.ts +4 -3
- package/src/scenarios/heartbeat-idempotent-no-spam.test.ts +6 -5
- package/src/scenarios/heartbeat-runtime-bound.test.ts +4 -3
- package/src/scenarios/mcp-2026-07-28-discover.test.ts +207 -0
- package/src/scenarios/mcp-cache-tenant-scope.test.ts +66 -0
- package/src/scenarios/mcp-current-auth-boundary.test.ts +55 -0
- package/src/scenarios/mcp-extension-opacity.test.ts +79 -0
- package/src/scenarios/mcp-mrtr-roundtrip.test.ts +140 -0
- package/src/scenarios/mcp-stateless-request.test.ts +77 -0
- package/src/scenarios/mcp-tool-roundtrip.test.ts +4 -3
- package/src/scenarios/mcp-toolcall-redaction.test.ts +2 -1
- package/src/scenarios/memory-attribution-emits-on-write.test.ts +6 -5
- package/src/scenarios/memory-attribution-no-content.test.ts +4 -3
- package/src/scenarios/memory-attribution-replay-stable.test.ts +6 -5
- package/src/scenarios/memory-attribution-tenant-scoped.test.ts +4 -3
- package/src/scenarios/memory-compaction-event-emitted.test.ts +3 -2
- package/src/scenarios/memory-compaction-provenance-tag.test.ts +6 -5
- package/src/scenarios/memory-compaction-sr1-carry-forward.test.ts +4 -3
- package/src/scenarios/memory-consolidation-idempotent.test.ts +4 -3
- package/src/scenarios/memory-injection-budget.test.ts +4 -3
- package/src/scenarios/metric-emission.test.ts +4 -3
- package/src/scenarios/multi-agent-confidence-escalation.test.ts +5 -4
- package/src/scenarios/multi-agent-handoff-state-machine.test.ts +4 -3
- package/src/scenarios/multi-agent-memory-lifecycle.test.ts +5 -4
- package/src/scenarios/multi-party-conversation-behavioral.test.ts +3 -2
- package/src/scenarios/multi-region-idempotency-behavior.test.ts +7 -6
- package/src/scenarios/multi-region-idempotency.test.ts +4 -3
- package/src/scenarios/normative-example-extraction.test.ts +242 -0
- package/src/scenarios/oauth-authorization-code-roundtrip.test.ts +4 -3
- package/src/scenarios/oauth-capability-shape.test.ts +4 -3
- package/src/scenarios/oauth-connector-redaction.test.ts +4 -3
- package/src/scenarios/openapi-asyncapi-sdk-parity.test.ts +309 -0
- package/src/scenarios/org-position-no-authority-escalation.test.ts +2 -1
- package/src/scenarios/otel-emission-grpc.test.ts +4 -3
- package/src/scenarios/otel-emission.test.ts +4 -3
- package/src/scenarios/otel-trace-propagation-subworkflow.test.ts +5 -4
- package/src/scenarios/otel-trace-propagation.test.ts +4 -3
- package/src/scenarios/pack-registry-isolation.test.ts +4 -3
- package/src/scenarios/pack-registry-publish.test.ts +65 -3
- package/src/scenarios/pack-registry.test.ts +22 -0
- package/src/scenarios/profile-discovery-core-alias.test.ts +137 -0
- package/src/scenarios/profileDerivation.test.ts +2 -1
- package/src/scenarios/rate-limit-envelope.test.ts +2 -1
- package/src/scenarios/registry-public.test.ts +6 -5
- package/src/scenarios/replay-divergence-at-refusal.test.ts +9 -8
- package/src/scenarios/replay-llm-cache-key-portable.test.ts +19 -13
- package/src/scenarios/replay-llm-cache-key.test.ts +120 -47
- package/src/scenarios/replay-observable-sequence-determinism.test.ts +5 -4
- package/src/scenarios/replay-side-effect-suppression.test.ts +20 -1
- package/src/scenarios/requirement-ledger.test.ts +29 -6
- package/src/scenarios/runner-ledger.test.ts +279 -0
- package/src/scenarios/runtime-requires-install-gate.test.ts +6 -5
- package/src/scenarios/sandbox-memory-cap.test.ts +3 -2
- package/src/scenarios/sandbox-mvp-behavior.test.ts +11 -10
- package/src/scenarios/sandbox-no-host-fs-escape.test.ts +2 -1
- package/src/scenarios/sandbox-timeout-cap.test.ts +3 -2
- package/src/scenarios/stream-text-fixture.test.ts +5 -4
- package/src/scenarios/subrun-approval-fail-closed.test.ts +3 -2
- package/src/scenarios/subrun-approval-gate.test.ts +4 -3
- package/src/scenarios/subrun-attestation-shape.test.ts +2 -1
- package/src/scenarios/subrun-checksum-stable.test.ts +4 -3
- package/src/scenarios/test-seam-unauthenticated.test.ts +61 -11
- package/src/scenarios/tool-hooks-authorization-fail-closed.test.ts +3 -2
- package/src/scenarios/tool-hooks-content-free.test.ts +3 -2
- package/src/scenarios/tool-hooks-rate-limit.test.ts +3 -2
- package/src/scenarios/tool-hooks-secret-redaction.test.ts +3 -2
- package/src/scenarios/tool-hooks-shape.test.ts +2 -1
- package/src/scenarios/trigger-bridge-delivery.test.ts +3 -2
- package/src/scenarios/version-fold.test.ts +5 -4
- package/src/scenarios/wasm-pack-abi-version-rejection.test.ts +5 -4
- package/src/scenarios/wasm-pack-invoke-completed.test.ts +3 -2
- package/src/scenarios/wasm-pack-invoke-suspended.test.ts +3 -2
- package/src/scenarios/wasm-pack-load.test.ts +4 -3
- package/src/scenarios/wasm-pack-memory-cap.test.ts +4 -3
- package/src/scenarios/wasm-pack-replay-determinism.test.ts +3 -2
- package/src/scenarios/webhook-signed-delivery.test.ts +4 -3
- package/src/scenarios/workflow-primary-output-annotation.test.ts +13 -23
- package/src/scenarios/workspace-cross-tenant-isolation-blackbox.test.ts +3 -2
- package/src/scenarios/workspace-cross-tenant-isolation.test.ts +3 -2
- package/src/setup.ts +95 -1
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
*/
|
|
23
23
|
|
|
24
24
|
import { describe, it, expect } from 'vitest';
|
|
25
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
25
26
|
import { driver } from '../lib/driver.js';
|
|
26
27
|
import { capabilityFamily } from '../lib/discovery-capabilities.js';
|
|
27
28
|
|
|
@@ -36,9 +37,9 @@ async function deadLetterSupported(): Promise<boolean> {
|
|
|
36
37
|
|
|
37
38
|
describe('deadletter-retry-exhaustion: retry exhaustion → dead-lettered + fork-eligible (RFC 0053 §C)', () => {
|
|
38
39
|
it('a retry-exhausted run emits run.dead_lettered with attempts', async () => {
|
|
39
|
-
if (!(await deadLetterSupported())) return
|
|
40
|
+
if (!(await deadLetterSupported())) return softSkip('inapplicable', 'capability-gated');
|
|
40
41
|
const res = await driver.post('/v1/host/sample/deadletter/exhaust', { scenario: 'exhaust-retries' });
|
|
41
|
-
if (res.status === 404) return
|
|
42
|
+
if (res.status === 404) return softSkip('blocked', 'seam unwired — soft-skip');
|
|
42
43
|
const body = res.json as { event?: { type?: string; payload?: { attempts?: number; runId?: string } } } | undefined;
|
|
43
44
|
expect(
|
|
44
45
|
body?.event?.type,
|
|
@@ -51,9 +52,9 @@ describe('deadletter-retry-exhaustion: retry exhaustion → dead-lettered + fork
|
|
|
51
52
|
});
|
|
52
53
|
|
|
53
54
|
it('the dead-lettered run is fork-eligible (RFC 0011)', async () => {
|
|
54
|
-
if (!(await deadLetterSupported())) return
|
|
55
|
+
if (!(await deadLetterSupported())) return softSkip('inapplicable', 'capability-gated');
|
|
55
56
|
const res = await driver.post('/v1/host/sample/deadletter/exhaust', { scenario: 'fork-after-dead-letter' });
|
|
56
|
-
if (res.status === 404) return
|
|
57
|
+
if (res.status === 404) return softSkip('blocked', 'seam unwired — soft-skip');
|
|
57
58
|
const body = res.json as { forkEligible?: boolean } | undefined;
|
|
58
59
|
expect(
|
|
59
60
|
body?.forkEligible,
|
|
@@ -14,15 +14,16 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import { describe, it, expect } from 'vitest';
|
|
17
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
17
18
|
import { driver } from '../lib/driver.js';
|
|
18
19
|
import { readDistillationCap, invokeDistill } from '../lib/distillation.js';
|
|
19
20
|
|
|
20
21
|
describe('distillation-index-roundtrip (RFC 0062 §B)', () => {
|
|
21
22
|
it('an indexEmitted run updates a retrievable memory-index manifest', async () => {
|
|
22
23
|
const cap = await readDistillationCap();
|
|
23
|
-
if (cap?.supported !== true || cap?.indexEmitted !== true) return;
|
|
24
|
+
if (cap?.supported !== true || cap?.indexEmitted !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true || cap?.indexEmitted !== true` returned early');
|
|
24
25
|
const res = await invokeDistill({ memoryRef: 'conformance-distill', tokenBudget: 8000, indexEmitted: true });
|
|
25
|
-
if (res === null) return
|
|
26
|
+
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
26
27
|
expect(
|
|
27
28
|
res.body.indexUpdated === true || res.body.event?.distillation?.indexUpdated === true,
|
|
28
29
|
driver.describe('RFC 0062 §B', 'an indexEmitted distillation MUST report updating the memory index'),
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect } from 'vitest';
|
|
15
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
15
16
|
import { driver } from '../lib/driver.js';
|
|
16
17
|
import { readDistillationCap, invokeDistill } from '../lib/distillation.js';
|
|
17
18
|
|
|
@@ -19,14 +20,14 @@ const CANARY = 'sk-canary-rfc0062-do-not-leak-qrs456';
|
|
|
19
20
|
|
|
20
21
|
describe('distillation-secret-carryforward (RFC 0062 §B)', () => {
|
|
21
22
|
it('a redacted secret in source memory never appears in the distilled output', async () => {
|
|
22
|
-
if ((await readDistillationCap())?.supported !== true) return;
|
|
23
|
+
if ((await readDistillationCap())?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `(await readDistillationCap())?.supported !== true` returned early');
|
|
23
24
|
const res = await invokeDistill({
|
|
24
25
|
memoryRef: 'conformance-distill',
|
|
25
26
|
tokenBudget: 8000,
|
|
26
27
|
includeSecretCanary: true,
|
|
27
28
|
sources: [{ content: `notes with embedded secret ${CANARY}` }],
|
|
28
29
|
});
|
|
29
|
-
if (res === null) return
|
|
30
|
+
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
30
31
|
expect(
|
|
31
32
|
JSON.stringify(res.body).includes(CANARY),
|
|
32
33
|
driver.describe('RFC 0062 §B', 'SR-1 carry-forward: a redacted secret MUST NOT re-appear in the archive or memory.compacted event'),
|
|
@@ -12,13 +12,14 @@
|
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
14
|
import { describe, it, expect } from 'vitest';
|
|
15
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
15
16
|
import { driver } from '../lib/driver.js';
|
|
16
17
|
import { readDistillationCap } from '../lib/distillation.js';
|
|
17
18
|
|
|
18
19
|
describe('distillation-shape: advertisement (RFC 0062 §A)', () => {
|
|
19
20
|
it('capabilities.memory.distillation is absent or a well-formed object', async () => {
|
|
20
21
|
const cap = await readDistillationCap();
|
|
21
|
-
if (cap === null) return
|
|
22
|
+
if (cap === null) return softSkip('inapplicable', 'not advertised — valid');
|
|
22
23
|
expect(
|
|
23
24
|
typeof cap.supported,
|
|
24
25
|
driver.describe('capabilities.schema.json §memory.distillation', 'distillation.supported MUST be a boolean when the block is present'),
|
|
@@ -10,21 +10,22 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
13
14
|
import { driver } from '../lib/driver.js';
|
|
14
15
|
import { readDistillationCap, invokeDistill } from '../lib/distillation.js';
|
|
15
16
|
|
|
16
17
|
describe('distillation-stable-archive (RFC 0062 §B)', () => {
|
|
17
18
|
it('identical sources + budget produce an identical archive checksum', async () => {
|
|
18
|
-
if ((await readDistillationCap())?.supported !== true) return;
|
|
19
|
+
if ((await readDistillationCap())?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `(await readDistillationCap())?.supported !== true` returned early');
|
|
19
20
|
const req = {
|
|
20
21
|
memoryRef: 'conformance-distill',
|
|
21
22
|
tokenBudget: 8000,
|
|
22
23
|
sources: ['s1', 's2', 's3'],
|
|
23
24
|
};
|
|
24
25
|
const a = await invokeDistill(req);
|
|
25
|
-
if (a === null) return
|
|
26
|
+
if (a === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
26
27
|
const b = await invokeDistill(req);
|
|
27
|
-
if (b === null) return;
|
|
28
|
+
if (b === null) return softSkip('blocked', 'precondition not met — `b === null` returned early (seam, prior step, or fixture unavailable)');
|
|
28
29
|
expect(
|
|
29
30
|
typeof a.body.archiveChecksum === 'string' && (a.body.archiveChecksum as string).length > 0,
|
|
30
31
|
driver.describe('RFC 0062 §B', 'a distillation run MUST produce a non-empty archive checksum'),
|
|
@@ -11,15 +11,16 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
14
15
|
import { driver } from '../lib/driver.js';
|
|
15
16
|
import { readDistillationCap, invokeDistill } from '../lib/distillation.js';
|
|
16
17
|
|
|
17
18
|
describe('distillation-token-budget (RFC 0062 §B)', () => {
|
|
18
19
|
it('within budget tokensUsed ≤ tokenBudget; an un-meetable budget fails atomically', async () => {
|
|
19
|
-
if ((await readDistillationCap())?.supported !== true) return;
|
|
20
|
+
if ((await readDistillationCap())?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `(await readDistillationCap())?.supported !== true` returned early');
|
|
20
21
|
|
|
21
22
|
const ok = await invokeDistill({ memoryRef: 'conformance-distill', tokenBudget: 8000 });
|
|
22
|
-
if (ok === null) return
|
|
23
|
+
if (ok === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
23
24
|
const dist = ok.body.event?.distillation ?? {};
|
|
24
25
|
expect(
|
|
25
26
|
typeof dist.tokenBudget === 'number' && typeof dist.tokensUsed === 'number',
|
|
@@ -32,7 +33,7 @@ describe('distillation-token-budget (RFC 0062 §B)', () => {
|
|
|
32
33
|
|
|
33
34
|
// A budget too small to distill the corpus MUST fail closed, no partial archive.
|
|
34
35
|
const tooSmall = await invokeDistill({ memoryRef: 'conformance-distill', tokenBudget: 1 });
|
|
35
|
-
if (tooSmall === null) return;
|
|
36
|
+
if (tooSmall === null) return softSkip('blocked', 'precondition not met — `tooSmall === null` returned early (seam, prior step, or fixture unavailable)');
|
|
36
37
|
expect(
|
|
37
38
|
tooSmall.status >= 400 && tooSmall.body.error === 'token_budget_exceeded',
|
|
38
39
|
driver.describe('RFC 0062 §B', 'an un-meetable budget MUST fail with token_budget_exceeded'),
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { describe, it, expect } from 'vitest';
|
|
12
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
12
13
|
import { driver } from '../lib/driver.js';
|
|
13
14
|
import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
14
15
|
|
|
@@ -17,14 +18,14 @@ const CANARY = 'sk-canary-rfc0056-do-not-leak-abc123';
|
|
|
17
18
|
describe('feedback-correction-redaction (RFC 0056 §E)', () => {
|
|
18
19
|
it('secret-shaped material in correction/note is redacted in the annotation list', async () => {
|
|
19
20
|
const cap = await readFeedbackCap();
|
|
20
|
-
if (cap?.supported !== true) return;
|
|
21
|
+
if (cap?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true` returned early');
|
|
21
22
|
const runId = await seedRun('feedback-redact');
|
|
22
|
-
if (!runId) return;
|
|
23
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
23
24
|
const post = await driver.post(`/v1/runs/${runId}/annotations`, {
|
|
24
25
|
signal: { kind: 'correction', correction: `please use ${CANARY}` },
|
|
25
26
|
note: CANARY,
|
|
26
27
|
});
|
|
27
|
-
if (post.status === 501 || post.status === 404) return;
|
|
28
|
+
if (post.status === 501 || post.status === 404) return softSkip('blocked', 'precondition not met — `post.status === 501 || post.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
28
29
|
expect(post.status).toBe(201);
|
|
29
30
|
const list = await driver.get(`/v1/runs/${runId}/annotations`);
|
|
30
31
|
expect(
|
|
@@ -13,17 +13,18 @@
|
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
15
|
import { describe, it, expect } from 'vitest';
|
|
16
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
16
17
|
import { driver } from '../lib/driver.js';
|
|
17
18
|
import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
18
19
|
|
|
19
20
|
describe('feedback-cross-tenant-isolation (RFC 0056 §E)', () => {
|
|
20
21
|
it('a run\'s annotation list contains only that run\'s annotations', async () => {
|
|
21
22
|
const cap = await readFeedbackCap();
|
|
22
|
-
if (cap?.supported !== true) return;
|
|
23
|
+
if (cap?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true` returned early');
|
|
23
24
|
const runId = await seedRun('feedback-cti');
|
|
24
|
-
if (!runId) return;
|
|
25
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
25
26
|
const post = await driver.post(`/v1/runs/${runId}/annotations`, { signal: { kind: 'label', label: 'cti-probe' } });
|
|
26
|
-
if (post.status === 501 || post.status === 404) return;
|
|
27
|
+
if (post.status === 501 || post.status === 404) return softSkip('blocked', 'precondition not met — `post.status === 501 || post.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
27
28
|
expect(post.status).toBe(201);
|
|
28
29
|
const list = await driver.get(`/v1/runs/${runId}/annotations`);
|
|
29
30
|
const ann = (list.json as { annotations?: Array<{ target?: { runId?: string } }> } | undefined)?.annotations ?? [];
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
10
|
import { describe, it, expect } from 'vitest';
|
|
11
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
11
12
|
import { driver } from '../lib/driver.js';
|
|
12
13
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
13
14
|
import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
@@ -15,11 +16,11 @@ import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
|
15
16
|
describe('feedback-fork-not-copied (RFC 0056 §D)', () => {
|
|
16
17
|
it('a fork of an annotated run starts with zero annotations', async () => {
|
|
17
18
|
const cap = await readFeedbackCap();
|
|
18
|
-
if (cap?.supported !== true) return;
|
|
19
|
+
if (cap?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true` returned early');
|
|
19
20
|
const runId = await seedRun('feedback-fork');
|
|
20
|
-
if (!runId) return;
|
|
21
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
21
22
|
const post = await driver.post(`/v1/runs/${runId}/annotations`, { signal: { kind: 'flag' } });
|
|
22
|
-
if (post.status === 501 || post.status === 404) return;
|
|
23
|
+
if (post.status === 501 || post.status === 404) return softSkip('blocked', 'precondition not met — `post.status === 501 || post.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
23
24
|
expect(post.status).toBe(201);
|
|
24
25
|
try {
|
|
25
26
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
@@ -27,9 +28,9 @@ describe('feedback-fork-not-copied (RFC 0056 §D)', () => {
|
|
|
27
28
|
return;
|
|
28
29
|
}
|
|
29
30
|
const fork = await driver.post(`/v1/runs/${runId}:fork`, { fromSeq: 0, mode: 'branch' });
|
|
30
|
-
if (fork.status !== 200 && fork.status !== 201) return
|
|
31
|
+
if (fork.status !== 200 && fork.status !== 201) return softSkip('inapplicable', 'fork unsupported — soft-skip (fork.status !== 200 && fork.status !== 201)');
|
|
31
32
|
const forkId = (fork.json as { runId?: string } | undefined)?.runId;
|
|
32
|
-
if (!forkId) return;
|
|
33
|
+
if (!forkId) return softSkip('blocked', 'precondition not met — `!forkId` returned early (seam, prior step, or fixture unavailable)');
|
|
33
34
|
const list = await driver.get(`/v1/runs/${forkId}/annotations`);
|
|
34
35
|
const ann = (list.json as { annotations?: unknown[] } | undefined)?.annotations ?? [];
|
|
35
36
|
expect(
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
10
11
|
import { driver } from '../lib/driver.js';
|
|
11
12
|
import { pollUntilTerminal } from '../lib/polling.js';
|
|
12
13
|
import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
@@ -14,16 +15,16 @@ import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
|
14
15
|
describe('feedback-on-terminal-run (RFC 0056 §C)', () => {
|
|
15
16
|
it('annotating a terminal run is accepted', async () => {
|
|
16
17
|
const cap = await readFeedbackCap();
|
|
17
|
-
if (cap?.supported !== true) return;
|
|
18
|
+
if (cap?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true` returned early');
|
|
18
19
|
const runId = await seedRun('feedback-terminal');
|
|
19
|
-
if (!runId) return;
|
|
20
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
20
21
|
try {
|
|
21
22
|
await pollUntilTerminal(runId, { timeoutMs: 10_000 });
|
|
22
23
|
} catch {
|
|
23
24
|
return; // run didn't reach terminal in time — soft-skip
|
|
24
25
|
}
|
|
25
26
|
const post = await driver.post(`/v1/runs/${runId}/annotations`, { signal: { kind: 'flag' }, note: 'post-hoc review' });
|
|
26
|
-
if (post.status === 501 || post.status === 404) return;
|
|
27
|
+
if (post.status === 501 || post.status === 404) return softSkip('blocked', 'precondition not met — `post.status === 501 || post.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
27
28
|
expect(
|
|
28
29
|
post.status,
|
|
29
30
|
driver.describe('RFC 0056 §C', 'a host MUST accept an annotation on a terminal run'),
|
|
@@ -7,17 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import { describe, it, expect } from 'vitest';
|
|
10
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
10
11
|
import { driver } from '../lib/driver.js';
|
|
11
12
|
import { readFeedbackCap, seedRun } from '../lib/feedback.js';
|
|
12
13
|
|
|
13
14
|
describe('feedback-record-and-list (RFC 0056 §C)', () => {
|
|
14
15
|
it('POST an annotation then GET returns it', async () => {
|
|
15
16
|
const cap = await readFeedbackCap();
|
|
16
|
-
if (cap?.supported !== true) return;
|
|
17
|
+
if (cap?.supported !== true) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `cap?.supported !== true` returned early');
|
|
17
18
|
const runId = await seedRun('feedback-rl');
|
|
18
|
-
if (!runId) return;
|
|
19
|
+
if (!runId) return softSkip('blocked', 'precondition not met — `!runId` returned early (seam, prior step, or fixture unavailable)');
|
|
19
20
|
const post = await driver.post(`/v1/runs/${runId}/annotations`, { signal: { kind: 'rating', rating: 5 } });
|
|
20
|
-
if (post.status === 501 || post.status === 404) return;
|
|
21
|
+
if (post.status === 501 || post.status === 404) return softSkip('blocked', 'precondition not met — `post.status === 501 || post.status === 404` returned early (seam, prior step, or fixture unavailable)');
|
|
21
22
|
expect(
|
|
22
23
|
post.status,
|
|
23
24
|
driver.describe('RFC 0056 §C', 'POST annotation returns 201 with the persisted annotation'),
|
|
@@ -11,17 +11,18 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
14
15
|
import { driver } from '../lib/driver.js';
|
|
15
16
|
import { readFeedbackCap } from '../lib/feedback.js';
|
|
16
17
|
|
|
17
18
|
describe('feedback-unsupported-501 (RFC 0056 §C)', () => {
|
|
18
19
|
it('POST annotations returns 501 capability_not_provided when feedback is unadvertised', async () => {
|
|
19
20
|
const cap = await readFeedbackCap();
|
|
20
|
-
if (cap?.supported === true) return
|
|
21
|
+
if (cap?.supported === true) return softSkip('inapplicable', 'host supports feedback — 501 N/A (cap?.supported === true)');
|
|
21
22
|
const res = await driver.post('/v1/runs/probe-run-rfc0056/annotations', {
|
|
22
23
|
signal: { kind: 'flag' },
|
|
23
24
|
});
|
|
24
|
-
if (res.status === 404 || res.status === 405) return
|
|
25
|
+
if (res.status === 404 || res.status === 405) return softSkip('blocked', 'route absent — host predates RFC 0056');
|
|
25
26
|
expect(
|
|
26
27
|
res.status,
|
|
27
28
|
driver.describe('rest-endpoints.md / RFC 0056 §C', 'unadvertised feedback MUST return 501, not 404'),
|
|
@@ -24,24 +24,22 @@ const PROMPT_TEMPLATE_SCHEMA_PATH = join(SCHEMAS_DIR, 'prompt-template.schema.js
|
|
|
24
24
|
describe('fixtures: workflow-definition schema validity', () => {
|
|
25
25
|
const ajv = new Ajv2020({ allErrors: true, strict: false });
|
|
26
26
|
addFormats(ajv);
|
|
27
|
-
// Pre-load
|
|
28
|
-
//
|
|
29
|
-
//
|
|
30
|
-
//
|
|
31
|
-
//
|
|
32
|
-
//
|
|
33
|
-
//
|
|
34
|
-
//
|
|
35
|
-
|
|
36
|
-
const
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
ajv.
|
|
43
|
-
const schema = JSON.parse(readFileSync(SCHEMA_PATH, 'utf8'));
|
|
44
|
-
const validate = ajv.compile(schema);
|
|
27
|
+
// Pre-load EVERY schema in the corpus (S14, 2026-08-16). This used to be a
|
|
28
|
+
// fixed list of the peer schemas workflow-definition cross-`$ref`s (agent-ref,
|
|
29
|
+
// prompt-ref, prompt-kind) — and when `compensation-policy.schema.json` became
|
|
30
|
+
// the fourth cross-file `$ref` (#1009), `ajv.compile` threw at describe time
|
|
31
|
+
// here and in every downstream runner that resolves the sibling corpus with a
|
|
32
|
+
// pinned suite. A fixed list is a claim about the schema graph that nothing
|
|
33
|
+
// keeps true; enumerating the directory is not. Each schema is registered
|
|
34
|
+
// under both its bare file name and `./<name>` so Ajv resolves either spelling
|
|
35
|
+
// of a relative ref (canonical `$id`s resolve on their own).
|
|
36
|
+
for (const file of readdirSync(SCHEMAS_DIR).filter((f) => f.endsWith('.schema.json'))) {
|
|
37
|
+
const s = JSON.parse(readFileSync(join(SCHEMAS_DIR, file), 'utf8')) as { $id?: string };
|
|
38
|
+
ajv.addSchema(s, file);
|
|
39
|
+
ajv.addSchema(s, `./${file}`);
|
|
40
|
+
}
|
|
41
|
+
// The root schema is already registered under its $id above; compile via getSchema.
|
|
42
|
+
const validate = ajv.getSchema('workflow-definition.schema.json') ?? ajv.compile(JSON.parse(readFileSync(SCHEMA_PATH, 'utf8')));
|
|
45
43
|
|
|
46
44
|
const files = readdirSync(FIXTURES_DIR)
|
|
47
45
|
.filter((f) => f.endsWith('.json')) // top-level only — sub-dirs hold non-workflow fixtures
|
|
@@ -11,13 +11,14 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
14
15
|
import { driver } from '../lib/driver.js';
|
|
15
16
|
import { readHeartbeatCap } from '../lib/heartbeat.js';
|
|
16
17
|
|
|
17
18
|
describe('heartbeat-capability-shape: advertisement (RFC 0060 §A)', () => {
|
|
18
19
|
it('capabilities.heartbeat is absent or a well-formed object', async () => {
|
|
19
20
|
const cap = await readHeartbeatCap();
|
|
20
|
-
if (cap === null) return
|
|
21
|
+
if (cap === null) return softSkip('inapplicable', 'not advertised — valid');
|
|
21
22
|
expect(
|
|
22
23
|
typeof cap.supported,
|
|
23
24
|
driver.describe('capabilities.schema.json §heartbeat', 'heartbeat.supported MUST be a boolean when the block is present'),
|
|
@@ -10,16 +10,17 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
13
14
|
import { driver } from '../lib/driver.js';
|
|
14
15
|
import { readHeartbeatCap, heartbeatSupported, tickHeartbeat } from '../lib/heartbeat.js';
|
|
15
16
|
|
|
16
17
|
describe('heartbeat-fires-once-per-tick (RFC 0060 §B.1)', () => {
|
|
17
18
|
it('one tick emits exactly one heartbeat.evaluated', async () => {
|
|
18
|
-
if (!heartbeatSupported(await readHeartbeatCap())) return;
|
|
19
|
+
if (!heartbeatSupported(await readHeartbeatCap())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!heartbeatSupported(await readHeartbeatCap())` returned early');
|
|
19
20
|
const res = await tickHeartbeat({ heartbeatId: 'conformance-hb', observedState: { n: 0 } });
|
|
20
|
-
if (res === null) return
|
|
21
|
+
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
21
22
|
const evaluated = (res.json as { evaluated?: unknown[] } | undefined)?.evaluated;
|
|
22
|
-
if (!Array.isArray(evaluated)) return
|
|
23
|
+
if (!Array.isArray(evaluated)) return softSkip('blocked', 'host doesn\'t surface per-tick events on the seam');
|
|
23
24
|
expect(
|
|
24
25
|
evaluated.length,
|
|
25
26
|
driver.describe('RFC 0060 §B.1', 'a single tick MUST emit exactly one heartbeat.evaluated'),
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
13
|
import { describe, it, expect } from 'vitest';
|
|
14
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
14
15
|
import { driver } from '../lib/driver.js';
|
|
15
16
|
import { readHeartbeatCap, heartbeatSupported, tickHeartbeat } from '../lib/heartbeat.js';
|
|
16
17
|
|
|
@@ -21,20 +22,20 @@ function changedCount(json: unknown): number | null {
|
|
|
21
22
|
|
|
22
23
|
describe('heartbeat-idempotent-no-spam (RFC 0060 §B.5)', () => {
|
|
23
24
|
it('an unchanged tick enqueues nothing; only a transition does', async () => {
|
|
24
|
-
if (!heartbeatSupported(await readHeartbeatCap())) return;
|
|
25
|
+
if (!heartbeatSupported(await readHeartbeatCap())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!heartbeatSupported(await readHeartbeatCap())` returned early');
|
|
25
26
|
const hb = 'conformance-hb-spam';
|
|
26
27
|
const first = await tickHeartbeat({ heartbeatId: hb, observedState: { unread: 0 } });
|
|
27
|
-
if (first === null) return
|
|
28
|
+
if (first === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
28
29
|
const second = await tickHeartbeat({ heartbeatId: hb, observedState: { unread: 0 } });
|
|
29
|
-
if (second === null) return;
|
|
30
|
+
if (second === null) return softSkip('blocked', 'precondition not met — `second === null` returned early (seam, prior step, or fixture unavailable)');
|
|
30
31
|
const unchanged = changedCount(second.json);
|
|
31
|
-
if (unchanged === null) return
|
|
32
|
+
if (unchanged === null) return softSkip('blocked', 'host doesn\'t surface stateChanged on the seam');
|
|
32
33
|
expect(
|
|
33
34
|
unchanged,
|
|
34
35
|
driver.describe('RFC 0060 §B.5', 'an unchanged tick MUST NOT emit heartbeat.stateChanged'),
|
|
35
36
|
).toBe(0);
|
|
36
37
|
const transition = await tickHeartbeat({ heartbeatId: hb, observedState: { unread: 3 } });
|
|
37
|
-
if (transition === null) return;
|
|
38
|
+
if (transition === null) return softSkip('blocked', 'precondition not met — `transition === null` returned early (seam, prior step, or fixture unavailable)');
|
|
38
39
|
expect(
|
|
39
40
|
changedCount(transition.json),
|
|
40
41
|
driver.describe('RFC 0060 §B.5', 'a transitioning tick MUST emit exactly one heartbeat.stateChanged'),
|
|
@@ -10,18 +10,19 @@
|
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
12
|
import { describe, it, expect } from 'vitest';
|
|
13
|
+
import { softSkip } from '../lib/soft-skip.js';
|
|
13
14
|
import { driver } from '../lib/driver.js';
|
|
14
15
|
import { readHeartbeatCap, heartbeatSupported, tickHeartbeat } from '../lib/heartbeat.js';
|
|
15
16
|
|
|
16
17
|
describe('heartbeat-runtime-bound (RFC 0060 §B.2)', () => {
|
|
17
18
|
it('an over-budget predicate is reported as timeout', async () => {
|
|
18
|
-
if (!heartbeatSupported(await readHeartbeatCap())) return;
|
|
19
|
+
if (!heartbeatSupported(await readHeartbeatCap())) return softSkip('inapplicable', 'capability or profile not advertised by this host — gate `!heartbeatSupported(await readHeartbeatCap())` returned early');
|
|
19
20
|
// `simulateSlowMs` is a host-seam hint asking the predicate to overrun
|
|
20
21
|
// its maxRuntimeMs budget; hosts not honoring it surface no `timeout`.
|
|
21
22
|
const res = await tickHeartbeat({ heartbeatId: 'conformance-hb-slow', observedState: {}, simulateSlowMs: 60_000 });
|
|
22
|
-
if (res === null) return
|
|
23
|
+
if (res === null) return softSkip('blocked', 'seam absent — soft-skip');
|
|
23
24
|
const evaluated = (res.json as { evaluated?: Array<{ status?: unknown }> } | undefined)?.evaluated;
|
|
24
|
-
if (!Array.isArray(evaluated) || evaluated.length === 0) return
|
|
25
|
+
if (!Array.isArray(evaluated) || evaluated.length === 0) return softSkip('blocked', 'host doesn\'t surface per-tick events (!Array.isArray(evaluated) || evaluated.length === 0)');
|
|
25
26
|
expect(
|
|
26
27
|
evaluated.every((e) => e.status === 'timeout'),
|
|
27
28
|
driver.describe('RFC 0060 §B.2', 'an over-budget predicate MUST be terminated and reported status:"timeout"'),
|