@openwop/openwop-conformance 2.0.3 → 2.0.4
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 +10 -0
- package/dist/spec-artifacts.lock.json +2 -2
- package/fixtures/connection-packs/connection-pack-acme-widgets.json +31 -0
- package/fixtures.md +2 -1
- package/package.json +2 -2
- package/requirements.json +4 -4
- package/schemas/CORPUS-STAMP.json +8 -8
- package/src/scenarios/v2-provider-conflict.test.ts +33 -15
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,15 @@
|
|
|
1
1
|
# `@openwop/openwop-conformance` Changelog
|
|
2
2
|
|
|
3
|
+
## [2.0.4] — 2026-09-06 — a fixture that punished a host for obeying the rule the scenario checks
|
|
4
|
+
|
|
5
|
+
`v2-provider-conflict` drove `connection-pack-github`. On a host shipping a built-in `github`, RFC 0177 §D.1 requires refusing the later registration — so the fixture never installed and the qualified-form leg recorded **`blocked`** permanently, on a host whose only fault was obeying the rule under test. A production host carried that row across a dozen cuts; any blocked row denies certification (RFC 0168 §E.1).
|
|
6
|
+
|
|
7
|
+
New fixture **`connection-pack-acme-widgets`** — same shape, a deliberately fictional provider id so no host ships it built-in and both legs are reachable everywhere.
|
|
8
|
+
|
|
9
|
+
The v1 scenario `connection-provider-resolution` keeps the `github` fixture, checked rather than assumed: v1 settles a collision by **version precedence** (`spec/v1/connection-packs.md:89`) instead of refusing the install, so it is not trapped by the same choice.
|
|
10
|
+
|
|
11
|
+
**An instrument must not require a host to violate the spec in order to be measured.**
|
|
12
|
+
|
|
3
13
|
## [2.0.3] — 2026-09-06 — a selector that asked the wrong question, and a window that convicted a host for its own width
|
|
4
14
|
|
|
5
15
|
**`v2-effect-seam-no-refire` selected on a branch permission to witness a replay obligation.** The filter was `guarded === true && branchReFires === false`. `replay.md:78`: *"A host MAY suppress branch effects and MUST NOT report that as replay suppression."* Replay suppression is unconditional (§Suppression rule 1) and does not vary with `branchReFires`, which states only what a **branch** re-fires by design. Every `guarded: true` row is a valid target; the filter now selects on `guarded` alone. `branchReFires` is also optional, so `=== false` additionally excluded rows merely silent on it. Found by the reference host, whose ten `branchReFires: true` rows are all honest and which was about to build `fireEffectSeam` for a scenario that would have kept recording `inapplicable` afterwards.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "core.openwop.connections.acme-widgets",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"kind": "connection",
|
|
5
|
+
"engines": { "openwop": ">=1.0.0" },
|
|
6
|
+
"provider": {
|
|
7
|
+
"id": "acme-widgets",
|
|
8
|
+
"displayName": "Acme Widgets",
|
|
9
|
+
"category": "dev",
|
|
10
|
+
"auth": {
|
|
11
|
+
"kind": "oauth2",
|
|
12
|
+
"authFlow": "pkce",
|
|
13
|
+
"scopeModel": "groups",
|
|
14
|
+
"endpoints": {
|
|
15
|
+
"authorize": "https://auth.acme-widgets.example.com/oauth/authorize",
|
|
16
|
+
"token": "https://auth.acme-widgets.example.com/oauth/token"
|
|
17
|
+
},
|
|
18
|
+
"scopes": {
|
|
19
|
+
"read": [
|
|
20
|
+
{ "key": "widget.read", "label": "Read widgets", "scopes": ["widget:read"] }
|
|
21
|
+
],
|
|
22
|
+
"write": [
|
|
23
|
+
{ "key": "widget.write", "label": "Write widgets", "scopes": ["widget:write"] }
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"reach": { "mcp": { "server": { "url": "https://mcp.acme-widgets.example.com/mcp/", "transport": "http" } } },
|
|
28
|
+
"consumerNodes": ["core.openwop.mcp.invoke-tool", "core.openwop.mcp.read-resource"],
|
|
29
|
+
"docsUrl": "https://docs.acme-widgets.example.com/oauth"
|
|
30
|
+
}
|
|
31
|
+
}
|
package/fixtures.md
CHANGED
|
@@ -516,7 +516,8 @@ The `fixtures/connection-packs/` sub-directory holds canonical connection-pack m
|
|
|
516
516
|
|
|
517
517
|
| Fixture | `provider.id` | Purpose |
|
|
518
518
|
| ------------------------ | ------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
519
|
-
| `connection-pack-github` | `github` | Canonical positive manifest (oauth2 + pkce, read/write scope groups, MCP reach, the exempt `provider.auth.endpoints.token` endpoint URL). Drives manifest-valid, no-credential-material, reach-exclusive, provider-resolution, and write-reconsent scenarios. |
|
|
519
|
+
| `connection-pack-github` | `github` | Canonical positive manifest (oauth2 + pkce, read/write scope groups, MCP reach, the exempt `provider.auth.endpoints.token` endpoint URL). Drives manifest-valid, no-credential-material, reach-exclusive, provider-resolution, and write-reconsent scenarios. **v1 only for the resolution leg:** v1 settles a collision with a built-in by version precedence (`spec/v1/connection-packs.md:89`), so a host shipping a built-in `github` still installs this pack. |
|
|
520
|
+
| `connection-pack-acme-widgets` | `acme-widgets` | Same shape as the `github` pack over a **deliberately fictional** provider. Drives `v2-provider-conflict` (RFC 0177 §D.1). The id is fictional so that no host ships it built-in: at v2 the later registration of a bare id MUST be refused, so a real-world id made the fixture uninstallable on any host that shipped it — and the qualified-form leg recorded `blocked` on a host whose only fault was obeying §D.1. Suite 2.0.4. |
|
|
520
521
|
| `connection-pack-apihosts-valid` | `meta-ads` | Canonical positive `openapi`-reach manifest declaring a `provider.apiHosts` credential-egress allow-list (`["facebook.com"]`) — RFC 0120. Drives `connection-pack-apihosts` (the `apiHosts` schema + conditional-MUST + egress allow-list scenario). Negatives (IP/wildcard/port/single-label/uppercase entries, openapi-without-apiHosts) are inline mutations per suite convention. |
|
|
521
522
|
|
|
522
523
|
Negative manifests (credential material, mixed kinds, dual reach) are inline test data in the scenario files per suite convention — a deliberately-invalid fixture file would fail the automatic `fixtures-valid.test.ts` sweep.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openwop/openwop-conformance",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -56,6 +56,6 @@
|
|
|
56
56
|
"@openwop/spec-artifacts": "file:../spec-artifacts"
|
|
57
57
|
},
|
|
58
58
|
"peerDependencies": {
|
|
59
|
-
"@openwop/spec-artifacts": "2.0.
|
|
59
|
+
"@openwop/spec-artifacts": "2.0.4"
|
|
60
60
|
}
|
|
61
61
|
}
|
package/requirements.json
CHANGED
|
@@ -27965,7 +27965,7 @@
|
|
|
27965
27965
|
{
|
|
27966
27966
|
"id": "openwop.it.v2-provider-conflict.two-definitions-of-one-bare-provider-id-fail-closed-the-later-registration-is-re",
|
|
27967
27967
|
"file": "v2-provider-conflict.test.ts",
|
|
27968
|
-
"line":
|
|
27968
|
+
"line": 99,
|
|
27969
27969
|
"title": "two definitions of one bare provider id fail closed: the later registration is refused with connection_provider_conflict",
|
|
27970
27970
|
"explicitId": "openwop.requirement.0177.provider-conflict.fail-closed",
|
|
27971
27971
|
"citations": [
|
|
@@ -27987,10 +27987,10 @@
|
|
|
27987
27987
|
]
|
|
27988
27988
|
},
|
|
27989
27989
|
{
|
|
27990
|
-
"id": "openwop.it.v2-provider-conflict.the-qualified-form-packname-
|
|
27990
|
+
"id": "openwop.it.v2-provider-conflict.the-qualified-form-packname-acme-widgets-resolves-to-the-named-pack-s-definition",
|
|
27991
27991
|
"file": "v2-provider-conflict.test.ts",
|
|
27992
|
-
"line":
|
|
27993
|
-
"title": "the qualified form <packName>#
|
|
27992
|
+
"line": 120,
|
|
27993
|
+
"title": "the qualified form <packName>#acme-widgets resolves to the named pack's definition",
|
|
27994
27994
|
"explicitId": "openwop.requirement.0177.provider-conflict.qualified-form",
|
|
27995
27995
|
"citations": [
|
|
27996
27996
|
{
|
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
{
|
|
2
2
|
"_comment": "Provenance of @openwop/spec-artifacts (RFC 0168 §D.2). files: SHA-256 per file; the conformance suite compares the installed peer against dist/spec-artifacts.lock.json at start.",
|
|
3
3
|
"package": "@openwop/spec-artifacts",
|
|
4
|
-
"version": "2.0.
|
|
5
|
-
"corpusTag": "v2.0.
|
|
4
|
+
"version": "2.0.4",
|
|
5
|
+
"corpusTag": "v2.0.4",
|
|
6
6
|
"files": {
|
|
7
7
|
"api/.redocly.lint-ignore.yaml": "bf5a8350b88a72fa43f59605ed8d903ed24b6cfccda5e45509c9f6ed9ee4e712",
|
|
8
8
|
"api/asyncapi.yaml": "d5ecb9ee6114582be3b1f662c84bfac9ae96dae7bacb853e461168f70a8e1c7d",
|
|
9
9
|
"api/grpc/openwop.proto": "c3e72bb17cba514ee98feb6434e6c9b6ea6795bfd086489ec69fd882dd1ad977",
|
|
10
10
|
"api/openapi.yaml": "39081c59fb696159806b0f2f9a42e7e9ff830d622fcf2ad4159b21357580a955",
|
|
11
11
|
"api/redocly.yaml": "b0604c89b2ca6d5076ec25725c539dad44a741a811fe524439ee6daef8baa09f",
|
|
12
|
-
"api/seams-v2.yaml": "
|
|
13
|
-
"api/v2/asyncapi.yaml": "
|
|
14
|
-
"api/v2/openapi.yaml": "
|
|
12
|
+
"api/seams-v2.yaml": "7298eee975d901d9356e8893c39d43261d74a76a07955fffa5b21fb9ffdadf1f",
|
|
13
|
+
"api/v2/asyncapi.yaml": "98f304626ae95d8fef4de49ace6a4422104faf6c5348bff56ce5aafe511c1381",
|
|
14
|
+
"api/v2/openapi.yaml": "a6ace2a7b2f9adce2374a6a76fe8b62c6cb2530be5689b2343d8876733e9a54c",
|
|
15
15
|
"api/v2/redocly.yaml": "1e66b60e6118ad11a823bb620678be464d99dfe50a40e3e6f93ec9429b88b34c",
|
|
16
16
|
"schemas/README.md": "0c0b737ffcf8f30e7d2809cec8a498232de710f41443212922ad8337cdde0b51",
|
|
17
17
|
"schemas/a2a-task-state.schema.json": "c9365918f993f943b4b619d42551eb066a1ed33a08d895d51b395432a5b1f1bc",
|
|
@@ -199,7 +199,7 @@
|
|
|
199
199
|
"schemas/workspace-file.schema.json": "464de85c2a068243084ee9c1d969bc7cd5d8f7948574e58450d6493c38a0e1e4",
|
|
200
200
|
"spec/v1/alias-detectors.json": "fee4594ef49953953ffcd0b3813300067d16b3e65ebff2aac722034ac9b3f545",
|
|
201
201
|
"spec/v1/capability-declaration-classes.json": "e7729aed5c4b4e1dd02abab0530f14cc95f5d4070fe51fb139e7f5cccefa00c6",
|
|
202
|
-
"spec/v1/core-standard-manifest.json": "
|
|
202
|
+
"spec/v1/core-standard-manifest.json": "04f279931a6749086cf24131671a48f9d119eca4c3a611d2c1e19cc909ac4101",
|
|
203
203
|
"spec/v1/deprecations.json": "1d5acb69a9b8ccb57275a95605f74aef1d920685f8407c9d382a46b59dc803bb",
|
|
204
204
|
"spec/v1/deprecations.schema.json": "18c87e78bedc210431f795ae44c5b5d202f2f3317850d5cf86867d4f1fa1cdfb",
|
|
205
205
|
"spec/v1/event-codemap.json": "3da60d884157793a360da532a9fcbbfb5285636db325a74cec94b34622186d97",
|
|
@@ -230,8 +230,8 @@
|
|
|
230
230
|
"spec/v2/path-manifest.json": "034152e09b1458c66810d4050e20a273b2b9b8fe2d92b66e8b819477de58a1be",
|
|
231
231
|
"spec/v2/peer-dependency-aliases.json": "d10299280abee08258502925bc327293ee413e0108cd6e6ec75ff6110653308d",
|
|
232
232
|
"spec/v2/profiles.json": "0636f19fceae625390003a347e70ef4797d84766b5c24ce8a02cea52aadebca4",
|
|
233
|
-
"spec/v2/release.json": "
|
|
233
|
+
"spec/v2/release.json": "2c18f3bb246203a2de3eef9c1e433e10f548ae74d4689c2cd8eb86a2ae14b39b",
|
|
234
234
|
"spec/v2/retention-floors.json": "eaf3722d95c79947af1d4269ef85117e126518c588cfcf1a2b21b97269f51624"
|
|
235
235
|
},
|
|
236
|
-
"corpusCommit": "
|
|
236
|
+
"corpusCommit": "87038a29abb5a19f8eb01bece101fe74af81a7e5"
|
|
237
237
|
}
|
|
@@ -15,11 +15,26 @@
|
|
|
15
15
|
* to `/conformance/seams/sample/…` under target major 2), gated on
|
|
16
16
|
* `connections.packsSupported`.
|
|
17
17
|
*
|
|
18
|
-
* 1. install the `connection-pack-
|
|
19
|
-
* major 2), then a second pack claiming bare `
|
|
20
|
-
* refused with connection_provider_conflict. A host that
|
|
21
|
-
*
|
|
22
|
-
*
|
|
18
|
+
* 1. install the `connection-pack-acme-widgets` fixture (engines rewritten to
|
|
19
|
+
* admit major 2), then a second pack claiming bare `acme-widgets` → the
|
|
20
|
+
* later one is refused with connection_provider_conflict. A host that
|
|
21
|
+
* happens to ship a built-in of the same id witnesses the same rule on the
|
|
22
|
+
* FIRST install.
|
|
23
|
+
* 2. `resolve { provider: "<packName>#acme-widgets" }` → resolved, source: pack.
|
|
24
|
+
*
|
|
25
|
+
* **Why a fictional provider id (suite 2.0.4).** Until 2.0.3 this drove
|
|
26
|
+
* `connection-pack-github`, and a host that ships a built-in `github` could
|
|
27
|
+
* never witness leg 2: §D.1 says the later registration of a bare id MUST be
|
|
28
|
+
* refused, so the fixture did not install, and the qualified-form leg recorded
|
|
29
|
+
* `blocked` — permanently, on a host whose ONLY fault was obeying the rule the
|
|
30
|
+
* scenario exists to check. Measured on a production host, which carried that
|
|
31
|
+
* blocked row across a dozen cuts; a bundle with any blocked row does not
|
|
32
|
+
* certify (RFC 0168 §E.1). `acme-widgets` is fictional precisely so no host
|
|
33
|
+
* ships it built-in and the fixture always installs. The v1 scenario
|
|
34
|
+
* `connection-provider-resolution` keeps the `github` fixture: v1 resolves a
|
|
35
|
+
* collision by VERSION PRECEDENCE (`spec/v1/connection-packs.md:89`) rather
|
|
36
|
+
* than refusing the install, so it is not trapped by the same choice — checked
|
|
37
|
+
* rather than assumed.
|
|
23
38
|
*
|
|
24
39
|
* @see RFCS/0177-v2-registry-packs-and-extension-tail.md §D.1
|
|
25
40
|
* @see spec/v2/core/connection-packs.md
|
|
@@ -38,7 +53,7 @@ import { targetMajor } from '../lib/seams.js';
|
|
|
38
53
|
import { v2Discovery, familyAdvertised } from '../lib/v2.js';
|
|
39
54
|
|
|
40
55
|
const SECTION = 'connection-packs.md §"Provider identity" (RFC 0177 §D.1)';
|
|
41
|
-
const FIXTURE = join(FIXTURES_DIR, 'connection-packs', 'connection-pack-
|
|
56
|
+
const FIXTURE = join(FIXTURES_DIR, 'connection-packs', 'connection-pack-acme-widgets.json');
|
|
42
57
|
const INSTALL = '/v1/host/sample/connection-packs/install';
|
|
43
58
|
const RESOLVE = '/v1/host/sample/connection-packs/resolve';
|
|
44
59
|
|
|
@@ -69,7 +84,7 @@ async function preflight(): Promise<{ kind: SoftSkipKind; reason: string } | nul
|
|
|
69
84
|
if (!doc) return { kind: 'blocked', reason: 'discovery unreachable — /.well-known/openwop (OpenWOP-Version: 2.0) did not answer 200 JSON' };
|
|
70
85
|
const connections = await familyAdvertised('connections');
|
|
71
86
|
if (!behaviorGate('connections.packsSupported', connections?.['packsSupported'] === true)) return { kind: 'inapplicable', reason: 'v2 discovery does not advertise connections.packsSupported (RFC 0095 §C)' };
|
|
72
|
-
if (!existsSync(FIXTURE)) return { kind: 'blocked', reason: 'fixture connection-packs/connection-pack-
|
|
87
|
+
if (!existsSync(FIXTURE)) return { kind: 'blocked', reason: 'fixture connection-packs/connection-pack-acme-widgets.json is absent from this layout' };
|
|
73
88
|
return null;
|
|
74
89
|
}
|
|
75
90
|
|
|
@@ -86,30 +101,33 @@ describe('v2-provider-conflict (RFC 0177 §D.1)', () => {
|
|
|
86
101
|
if (skip) return softSkip(skip.kind, skip.reason);
|
|
87
102
|
const first = await installFixture();
|
|
88
103
|
if (!first) return softSkip('blocked', 'RFC 0095 install seam not mounted (404/403) — connections.packsSupported advertised but /conformance/seams/sample/connection-packs/install is absent');
|
|
89
|
-
//
|
|
90
|
-
//
|
|
91
|
-
//
|
|
104
|
+
// The branch is kept for a host that somehow ships a built-in of this id:
|
|
105
|
+
// the fixture is then the LATER registration and the rule fires on the
|
|
106
|
+
// first install. `acme-widgets` is fictional so the normal path is the
|
|
107
|
+
// second one — the fixture installs, and a second pack claiming the same
|
|
108
|
+
// bare id is the later registration. Keeping the branch costs nothing and
|
|
109
|
+
// means the scenario does not depend on that assumption holding.
|
|
92
110
|
const later = first.res?.installed === false && codes(first.res).includes('connection_provider_conflict')
|
|
93
111
|
? first.res
|
|
94
112
|
: await (async () => {
|
|
95
|
-
expect(first.res?.installed, req('openwop.requirement.0177.provider-conflict.fail-closed', SECTION, `the well-formed fixture MUST install when no other definition of
|
|
113
|
+
expect(first.res?.installed, req('openwop.requirement.0177.provider-conflict.fail-closed', SECTION, `the well-formed fixture MUST install when no other definition of acme-widgets exists (got ${first.status}: ${JSON.stringify(first.res)})`)).toBe(true);
|
|
96
114
|
return (await driver.post(INSTALL, { manifest: conflicting(fixture()) })).json as InstallResult | undefined;
|
|
97
115
|
})();
|
|
98
116
|
expect(later?.installed, req('openwop.requirement.0177.provider-conflict.fail-closed', SECTION, 'the later registration of a bare provider id MUST NOT install (no version precedence)')).toBe(false);
|
|
99
117
|
expect(codes(later), req('openwop.requirement.0177.provider-conflict.fail-closed', SECTION, 'the later registration MUST be refused with connection_provider_conflict')).toContain('connection_provider_conflict');
|
|
100
118
|
});
|
|
101
119
|
|
|
102
|
-
it('the qualified form <packName>#
|
|
120
|
+
it('the qualified form <packName>#acme-widgets resolves to the named pack\'s definition', async () => {
|
|
103
121
|
const skip = await preflight();
|
|
104
122
|
if (skip) return softSkip(skip.kind, skip.reason);
|
|
105
123
|
const first = await installFixture();
|
|
106
124
|
if (!first) return softSkip('blocked', 'RFC 0095 install seam not mounted (404/403) — connections.packsSupported advertised but /conformance/seams/sample/connection-packs/install is absent');
|
|
107
|
-
if (first.res?.installed !== true) return softSkip('blocked', `the fixture did not install (${codes(first.res).join(',') || first.status}) — a host with a built-in
|
|
125
|
+
if (first.res?.installed !== true) return softSkip('blocked', `the fixture did not install (${codes(first.res).join(',') || first.status}) — a host with a built-in acme-widgets cannot exercise the qualified form through the pack`);
|
|
108
126
|
const packName = fixture().name;
|
|
109
|
-
const hit = await driver.post(RESOLVE, { provider: `${packName}#
|
|
127
|
+
const hit = await driver.post(RESOLVE, { provider: `${packName}#acme-widgets` });
|
|
110
128
|
if (hit.status === 404) return softSkip('blocked', 'RFC 0095 resolve seam not mounted (404)');
|
|
111
129
|
const resolved = hit.json as ResolveResult | undefined;
|
|
112
|
-
expect(resolved?.resolved, req('openwop.requirement.0177.provider-conflict.qualified-form', 'connection-packs.md §"The qualified form" (RFC 0177 §D.1)', `${packName}#
|
|
130
|
+
expect(resolved?.resolved, req('openwop.requirement.0177.provider-conflict.qualified-form', 'connection-packs.md §"The qualified form" (RFC 0177 §D.1)', `${packName}#acme-widgets MUST resolve (got ${JSON.stringify(resolved)})`)).toBe(true);
|
|
113
131
|
expect(resolved?.source, req('openwop.requirement.0177.provider-conflict.qualified-form', 'connection-packs.md §"The qualified form" (RFC 0177 §D.1)', 'a qualified reference resolves only to the named pack\'s definition (source: pack)')).toBe('pack');
|
|
114
132
|
});
|
|
115
133
|
});
|