@openwop/openwop-conformance 1.151.0 → 1.152.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/CHANGELOG.md CHANGED
@@ -1,5 +1,12 @@
1
1
  # `@openwop/openwop-conformance` Changelog
2
2
 
3
+ ## [1.152.0] — 2026-09-02 — v2 charter Phase 0: `--certify` defaults to bundle v2; the RFC 0050 reference suite leaves the scenario file
4
+
5
+ No new scenario file. Two packed changes:
6
+
7
+ - **`--certify` emits certification bundle v2 by default** (RFC 0148). `--bundle-version 1` still works through the RFC 0148 migration window (ends 2026-11-10) and prints a deprecation notice; the v1 schema file stays in the package so v1 bundles remain parseable, and is removed at v2.0 (`spec/v1/deprecations.json`).
8
+ - **The RFC 0050 §A synthetic-IdP reference suite moved** from `scenarios/auth-saml-profile.test.ts` to `src/lib/saml-idp.test.ts` (RFC 0163 gap G5): it proves the bundled fixture, not a host, and inside a scenario file it recorded an `executed-pass` in every host bundle (certification gap G8). The scenario keeps its host-facing legs; `coverage.md` grades it `host-pending` on the opt-in host-ACS leg. Hosts whose bundles previously counted this file as a pass will see it become `skipped`/`inapplicable` unless they opt into the seam — that is the honest reading.
9
+
3
10
  ## [1.151.0] — 2026-09-02 — RFC 0164: the subject-link scenarios gate on the profile pair; the opted-out shape fails
4
11
 
5
12
  No new scenario file. `auth-subject-link.test.ts` and `auth-subject-link-key-class.test.ts` re-gate every leg on the host advertising **both** `openwop-auth-saml` and `openwop-auth-scim`, not on `capabilities.auth.subjectLinking`, which RFC 0164 makes a derived advertisement. The RFC 0159 advertisement block gains a leg: both profiles advertised ⇒ `subjectLinking === true` and `subjectLinkKey ∈ {opaque-idp, configured-immutable}`, else `executed-fail` citing RFC 0164 §A.3. That is the shape a combined host that opted out used to present, and it used to read `inapplicable` — a host shipping the leaver bypass was invisible to the suite. `src/lib/capabilities-auth-subject-link.test.ts` pins the new schema conditional (both profiles with the flag absent, `false`, or without a key are rejected; with `true` + a class accepted; profile order irrelevant; single-profile hosts unaffected). `capabilities.schema.json` composes the RFC 0163 and RFC 0164 conditionals under `allOf`; `coverage.md` rows re-worded. No production host advertises both profiles, so no host's recorded evidence changes.
package/coverage.md CHANGED
@@ -115,7 +115,7 @@ The scenario groups in the table below (one row per group; count the rows — th
115
115
  | `cross-workspace-isolation.test.ts` | RFC 0048 §C/§D (`auth.md` §Identity claims, `rest-endpoints.md` `run_forbidden`) | A (owner-echo shape if a sample run is readable; §D isolation MUST-NOT via optional `POST /v1/host/sample/identity/cross-workspace-read` seam — cross-workspace read fails closed with `run_forbidden`/`not_found`) | `host-pending` | Behavioral probe soft-skips on 404; no host advertises run ownership yet (RFC 0048 `Draft`). |
116
116
  | `authorization-roles-shape.test.ts` | `capabilities.authorization` (RFC 0049 §A, `auth.md` §"Role-based authorization") | A (advertisement shape always — `supported` boolean; `failClosed` const true; every `roles[].role` non-empty + `scopes` array) | `host-pending` | Always runs; asserts the block is absent or well-formed. |
117
117
  | `authorization-fail-closed.test.ts` | `capabilities.authorization` (RFC 0049 §C) + `SECURITY/invariants.yaml` `authorization-fail-closed` | A (advertisement `failClosed===true` always; fail-closed MUST-NOT via optional `POST /v1/host/sample/authorization/decide` seam — an unseeded-role principal resolves `allowed:false`) | `host-pending` | Capability-gated on `authorization.supported`; behavioral probe soft-skips on 404. Scope-match + denial-audited scenarios deferred to a host. |
118
- | `auth-saml-profile.test.ts` | `openwop-auth-saml` (RFC 0050, `auth-profiles.md` §`openwop-auth-saml`) | A+B (profile-advertisement shape always; **1-positive + 6-negative reference suite runs server-free** via the bundled synthetic IdP `conformance/src/lib/saml-idp.ts` — `alg:none`/unsigned/bad-sig/expired/not-yet-valid/wrapping; host-ACS validation opt-in via `OPENWOP_TEST_SAML_IDP_URL` + the `auth/saml/validate` seam) | host-pass (server-free reference) | Synthetic IdP bundled (`node:crypto`, no deps). Host-ACS pass is the remaining graduation gate. |
118
+ | `auth-saml-profile.test.ts` | `openwop-auth-saml` (RFC 0050, `auth-profiles.md` §`openwop-auth-saml`) | A+B (profile-advertisement shape always; host-ACS validation opt-in via `OPENWOP_TEST_SAML_IDP_URL` + the `auth/saml/validate` seam. The **1-positive + 6-negative reference suite** against the bundled synthetic IdP `conformance/src/lib/saml-idp.ts` — `alg:none`/unsigned/bad-sig/expired/not-yet-valid/wrapping — moved to `src/lib/saml-idp.test.ts` on 2026-09-02 (RFC 0163 G5): it proves the fixture, so it no longer records a scenario ledger row) | host-pending (host-ACS leg, opt-in) | Synthetic IdP bundled (`node:crypto`, no deps); its self-tests live in `src/lib`. Host-ACS pass over the seam is the witness that credits a host. |
119
119
  | `auth-scim-profile.test.ts` | `openwop-auth-scim` (RFC 0050, `auth-profiles.md` §`openwop-auth-scim`) | B (profile-advertisement shape always; SCIM user/group provisioning → principal/role roundtrip opt-in via `OPENWOP_TEST_SCIM_URL` + the `auth/scim/provision` seam) | `host-pending` | Behavior opt-in (operator-supplied SCIM endpoint); deactivate ⇒ subsequent-deny assertion deferred to a host. |
120
120
  | `auth-subject-link.test.ts` | `openwop-auth-saml` + `openwop-auth-scim` advertised together (RFC 0159 + RFC 0164, `auth-profiles.md` §Subject linking; `subjectLinking` is derived) | B (advertisement gate always — `subjectLinking:true` only alongside both profiles, AND both profiles ⇒ `subjectLinking:true` + `subjectLinkKey` (RFC 0164 vulnerable-shape leg, `executed-fail` never `inapplicable`); cross-lane leaver deny + mutable-key negative opt-in via `OPENWOP_TEST_SAML_IDP_URL` + `OPENWOP_TEST_SCIM_URL` and the `auth/scim/provision` + `auth/saml/validate` seams) | host-pass (tier-1 in-process witness, openwop-app #3581) | Invariant `subject-link-leaver-deny`. No production host advertises `subjectLinking`. |
121
121
  | `auth-subject-link-key-class.test.ts` | as above (RFC 0163; gated on the profile pair since RFC 0164) | B (`subjectLinkKey` ∈ `{opaque-idp, configured-immutable}` when `subjectLinking:true`, unaided; same-IdP control + cross-IdP collision negative opt-in via `OPENWOP_TEST_SAML_IDP_URL` + `OPENWOP_TEST_SAML_IDP_URL_B` + `OPENWOP_TEST_SCIM_URL`) | host-pass (tier-1 in-process witness, openwop-app #3614) | Invariants `subject-link-key-class-declared`, `subject-link-same-trust-root`. Fixture self-test lives in `src/lib/saml-idp.test.ts`, not here (G8). |
package/dist/cli.js CHANGED
@@ -53,7 +53,10 @@ function parseArgs(argv) {
53
53
  let impl;
54
54
  let implVersion;
55
55
  let certify;
56
- let bundleVersion = '1';
56
+ // Suite 1.152.0: bundle v2 (RFC 0148) is the default. v1 stays reachable via
57
+ // `--bundle-version 1` through the RFC 0148 migration window (ends
58
+ // 2026-11-10) and is removed at v2.0 (spec/v1/deprecations.json).
59
+ let bundleVersion = '2';
57
60
  let maxWorkers = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
58
61
  for (let i = 0; i < argv.length; i++) {
59
62
  const arg = argv[i] ?? '';
@@ -103,6 +106,9 @@ function parseArgs(argv) {
103
106
  process.exit(2);
104
107
  }
105
108
  bundleVersion = v;
109
+ if (v === '1') {
110
+ process.stderr.write('openwop-conformance: --bundle-version 1 is DEPRECATED (RFC 0148). A v1 bundle ceases to substantiate a new certification after 2026-11-10 and the format is removed at v2.0; omit the flag to emit bundle v2.\n');
111
+ }
106
112
  break;
107
113
  }
108
114
  case '--certify':
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.151.0",
3
+ "version": "1.152.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.151.0",
4
- "corpusCommit": "b1203c4399516184e2ad36aaedfec35331abb175"
3
+ "suiteVersion": "1.152.0",
4
+ "corpusCommit": "8d02d922ac933096f36fc9e649b6ba574b4f609c"
5
5
  }
package/schemas/README.md CHANGED
@@ -46,7 +46,7 @@
46
46
  | `connection-pack-manifest.schema.json` | `connection-packs.md` + RFC 0095 | DRAFT — manifest for `kind: "connection"` registry packs (RFC 0095). Peer to the node/workflow-chain/prompt/artifact-type/chat-card pack manifests; disjoint via the `kind` discriminator. Distributes a portable provider definition — auth endpoints, read/write scope groups, exactly-one reach (`mcp`/`openapi`/`integration`) — that the RFC 0045/0047 `provider` string resolves against. Carries NO credential material (`connection-pack-no-credential-material`). |
47
47
  | `frontend-plugin-manifest.schema.json` | `frontend-plugin-packs.md` + RFC 0117 | DRAFT — manifest for `kind: "frontend-plugin"` registry packs (RFC 0117). Peer to the other pack manifests; disjoint via the `kind` discriminator. Distributes a signed, SANDBOXED UI extension — one or more `uiPlugins[]` (a `pluginId`, a `surface`, an opaque `entry` bundle, a closed `hostApi` allowlist). A backend `runtime` member is FORBIDDEN (`not: {}`). Loaded only in a cross-origin sandbox (`frontend-plugin-isolation`). |
48
48
  | `ui-plugin-message.schema.json` | `frontend-plugin-packs.md` + RFC 0117 | DRAFT — the `ui-plugin/1` `postMessage` host-RPC envelope (RFC 0117) between a sandboxed front-end plugin and its host: a plugin→host `request` (closed `method` allowlist), a host→plugin `response` (`ok`+`result` or `ok:false`+`error`), or a host→plugin `event`. Carries the optimistic-concurrency `version` token and the `artifact_conflict` + `currentVersion` shape (`frontend-plugin-rpc-allowlist` / `frontend-plugin-no-byok`). |
49
- | `conformance-certification-bundle.schema.json` | `conformance-certification.md` + RFC 0089 | DRAFT — machine-readable attestation binding a host's claimed profiles to the reproducible run that substantiates them (suite version + per-scenario pass list + host identity/commit + captured discovery document). Out-of-band; a consumer re-derives each claim via the §B binding rule. |
49
+ | `conformance-certification-bundle.schema.json` | `conformance-certification.md` + RFC 0089 | **DEPRECATED** (RFC 0148; `--certify` defaults to bundle v2 since suite 1.152.0; v1 ceases to substantiate a new certification after 2026-11-10; removal scheduled for v2.0 per `spec/v1/deprecations.json`) — machine-readable attestation binding a host's claimed profiles to the reproducible run that substantiates them (suite version + per-scenario pass list + host identity/commit + captured discovery document). Out-of-band; a consumer re-derives each claim via the §B binding rule. |
50
50
  | `conversation-event.schema.json` | `channels-and-reducers.md` + conversation RFC | Multi-turn conversation event shape for orchestrator-driven HITL flows |
51
51
  | `conversation-turn.schema.json` | `channels-and-reducers.md` + conversation RFC | Conversation turn shape for user/agent/system messages |
52
52
  | `core-conformance-mock-agent-config.schema.json` | `node-packs.md` + RFC 0023 | Config shape for the conformance-only `core.conformance.mock-agent` typeId — drives `agent.*` event emission on cue (`mockReasoning` / `mockToolCalls` / `mockHandoff` / `mockDecision` / `mockConfidence`). Hosts MUST refuse this typeId for production tenants unless `capabilities.conformance.mockAgent` is advertised. |
@@ -2,7 +2,7 @@
2
2
  "$schema": "https://json-schema.org/draft/2020-12/schema",
3
3
  "$id": "https://openwop.dev/spec/v1/conformance-certification-bundle.schema.json",
4
4
  "title": "OpenWOP Conformance Certification Bundle",
5
- "description": "RFC 0089. A machine-readable attestation binding a host's claimed profiles to the reproducible run that substantiates them: suite version, per-scenario pass list, host identity + commit, and the captured discovery document. An out-of-band artifact emitted by the conformance harness (not a runtime wire message). A consumer MUST re-derive each claimed profile from the embedded `discovery.document` rather than trusting `claimedProfiles` verbatim (RFC 0089 §B).",
5
+ "description": "DEPRECATED (RFC 0148, suite 1.114.0): bundle v1 remains parseable but ceases to substantiate a new certification after 2026-11-10; emit `certification-bundle-v2.schema.json` (the `--certify` default since suite 1.152.0). Scheduled for removal at v2.0 — see spec/v1/deprecations.json. RFC 0089. A machine-readable attestation binding a host's claimed profiles to the reproducible run that substantiates them: suite version, per-scenario pass list, host identity + commit, and the captured discovery document. An out-of-band artifact emitted by the conformance harness (not a runtime wire message). A consumer MUST re-derive each claimed profile from the embedded `discovery.document` rather than trusting `claimedProfiles` verbatim (RFC 0089 §B).",
6
6
  "type": "object",
7
7
  "additionalProperties": false,
8
8
  "required": ["bundleVersion", "generatedAt", "generator", "suite", "host", "discovery", "claimedProfiles", "results"],
package/src/cli.ts CHANGED
@@ -81,7 +81,10 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
81
81
  let impl: string | undefined;
82
82
  let implVersion: string | undefined;
83
83
  let certify: string | undefined;
84
- let bundleVersion: '1' | '2' = '1';
84
+ // Suite 1.152.0: bundle v2 (RFC 0148) is the default. v1 stays reachable via
85
+ // `--bundle-version 1` through the RFC 0148 migration window (ends
86
+ // 2026-11-10) and is removed at v2.0 (spec/v1/deprecations.json).
87
+ let bundleVersion: '1' | '2' = '2';
85
88
  let maxWorkers: number | undefined = parseMaxWorkers(process.env.OPENWOP_MAX_WORKERS, 'OPENWOP_MAX_WORKERS');
86
89
 
87
90
  for (let i = 0; i < argv.length; i++) {
@@ -132,6 +135,11 @@ function parseArgs(argv: readonly string[]): ParsedArgs {
132
135
  process.exit(2);
133
136
  }
134
137
  bundleVersion = v;
138
+ if (v === '1') {
139
+ process.stderr.write(
140
+ 'openwop-conformance: --bundle-version 1 is DEPRECATED (RFC 0148). A v1 bundle ceases to substantiate a new certification after 2026-11-10 and the format is removed at v2.0; omit the flag to emit bundle v2.\n',
141
+ );
142
+ }
135
143
  break;
136
144
  }
137
145
  case '--certify':
@@ -19,10 +19,14 @@
19
19
  *
20
20
  * @see conformance/src/lib/saml-idp.ts
21
21
  * @see RFCS/0163-subject-linking-hardening.md §B
22
+ *
23
+ * The RFC 0050 §A reference suite (1 positive + 6 negative variants against the
24
+ * same fixture) moved here from `scenarios/auth-saml-profile.test.ts` on
25
+ * 2026-09-02 (RFC 0163 gap G5) for the same reason.
22
26
  */
23
27
 
24
28
  import { describe, it, expect } from 'vitest';
25
- import { createSyntheticSamlIdp, DEFAULT_SAML_ENTITY_ID } from './saml-idp.js';
29
+ import { createSyntheticSamlIdp, DEFAULT_SAML_ENTITY_ID, type SamlVariant } from './saml-idp.js';
26
30
 
27
31
  const COLLIDING_SUBJECT = 'idp-op-8f3a';
28
32
 
@@ -81,3 +85,43 @@ describe('saml-idp: two-trust-root fixture (RFC 0163 §B)', () => {
81
85
  expect(idpB.verify(forged).valid, 'the original root MUST NOT verify it either').toBe(false);
82
86
  });
83
87
  });
88
+
89
+ describe('saml-idp: synthetic-IdP reference suite (RFC 0050 §A)', () => {
90
+ // Server-free: the bundled synthetic IdP (conformance/src/lib/saml-idp.ts)
91
+ // mints a valid assertion + the 6 negative variants, and its verify()
92
+ // implements the RFC 0050 §A MUST list. This proves each negative is
93
+ // detectably malformed and gives the suite a reference SAML validator.
94
+ // A host's real ACS validates the SAME assertions over the
95
+ // `auth/saml/validate` seam (scenarios/auth-saml-profile.test.ts, gated on
96
+ // OPENWOP_TEST_SAML_IDP_URL).
97
+ const idp = createSyntheticSamlIdp();
98
+
99
+ it('publishes a PEM signing certificate', () => {
100
+ expect(idp.certificatePem).toContain('BEGIN PUBLIC KEY');
101
+ });
102
+
103
+ it('accepts a valid signed, in-window, non-wrapped assertion', () => {
104
+ const r = idp.verify(idp.mint('valid'));
105
+ expect(r.valid, `expected valid; got reason=${r.reason}`).toBe(true);
106
+ });
107
+
108
+ const negatives: ReadonlyArray<[Exclude<SamlVariant, 'valid'>, string]> = [
109
+ ['alg-none', 'alg-none'],
110
+ ['unsigned', 'unsigned'],
111
+ ['bad-signature', 'bad-signature'],
112
+ ['expired', 'expired'],
113
+ ['not-yet-valid', 'not-yet-valid'],
114
+ ['signature-wrapping', 'signature-wrapping'],
115
+ ];
116
+
117
+ for (const [variant, expectedReason] of negatives) {
118
+ it(`rejects the ${variant} assertion (RFC 0050 §A MUST)`, () => {
119
+ const r = idp.verify(idp.mint(variant));
120
+ expect(r.valid, `${variant} MUST be rejected`).toBe(false);
121
+ expect(
122
+ r.reason,
123
+ `${variant} MUST be rejected for the ${expectedReason} reason`,
124
+ ).toBe(expectedReason);
125
+ });
126
+ }
127
+ });
@@ -23,7 +23,7 @@
23
23
 
24
24
  import { describe, it, expect } from 'vitest';
25
25
  import { driver } from '../lib/driver.js';
26
- import { createSyntheticSamlIdp, type SamlVariant } from '../lib/saml-idp.js';
26
+ import { type SamlVariant } from '../lib/saml-idp.js';
27
27
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
28
28
 
29
29
  const SAML_PROFILE = 'openwop-auth-saml';
@@ -117,41 +117,9 @@ describe('auth-saml-profile: assertion validation (RFC 0050 §A — opt-in)', ()
117
117
  }
118
118
  });
119
119
 
120
- describe('category: auth-saml synthetic-IdP reference suite (RFC 0050 §A)', () => {
121
- // Server-free: the bundled synthetic IdP (conformance/src/lib/saml-idp.ts)
122
- // mints a valid assertion + the 6 negative variants, and its verify()
123
- // implements the RFC 0050 §A MUST list. This proves each negative is
124
- // detectably malformed and gives the suite a reference SAML validator.
125
- // A host's real ACS validates the SAME assertions over the
126
- // `auth/saml/validate` seam (gated above on OPENWOP_TEST_SAML_IDP_URL).
127
- const idp = createSyntheticSamlIdp();
128
-
129
- it('publishes a PEM signing certificate', () => {
130
- expect(idp.certificatePem).toContain('BEGIN PUBLIC KEY');
131
- });
132
-
133
- it('accepts a valid signed, in-window, non-wrapped assertion', () => {
134
- const r = idp.verify(idp.mint('valid'));
135
- expect(r.valid, `expected valid; got reason=${r.reason}`).toBe(true);
136
- });
137
-
138
- const negatives: ReadonlyArray<[Exclude<SamlVariant, 'valid'>, string]> = [
139
- ['alg-none', 'alg-none'],
140
- ['unsigned', 'unsigned'],
141
- ['bad-signature', 'bad-signature'],
142
- ['expired', 'expired'],
143
- ['not-yet-valid', 'not-yet-valid'],
144
- ['signature-wrapping', 'signature-wrapping'],
145
- ];
146
-
147
- for (const [variant, expectedReason] of negatives) {
148
- it(`rejects the ${variant} assertion (RFC 0050 §A MUST)`, () => {
149
- const r = idp.verify(idp.mint(variant));
150
- expect(r.valid, `${variant} MUST be rejected`).toBe(false);
151
- expect(
152
- r.reason,
153
- `${variant} MUST be rejected for the ${expectedReason} reason`,
154
- ).toBe(expectedReason);
155
- });
156
- }
157
- });
120
+ // The RFC 0050 §A synthetic-IdP reference suite (1 positive + 6 negatives against
121
+ // the bundled fixture) moved to `conformance/src/lib/saml-idp.test.ts` on
122
+ // 2026-09-02 (RFC 0163 gap G5): it proves the FIXTURE, not a host, and a
123
+ // server-free block inside a scenario file records `executed-pass` in every
124
+ // host bundle (conformance-certification.md gap G8). This file keeps only the
125
+ // host-facing legs above.