@openwop/openwop-conformance 1.135.1 → 1.135.3

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,6 +1,6 @@
1
1
  {
2
2
  "name": "@openwop/openwop-conformance",
3
- "version": "1.135.1",
3
+ "version": "1.135.3",
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.135.1",
4
- "corpusCommit": "821921ce3b4a6cbbc20fe1aafe81a7413e10c9d9"
3
+ "suiteVersion": "1.135.3",
4
+ "corpusCommit": "ed19aa8aafa8c43e55a174abaa2010b82698f572"
5
5
  }
@@ -138,11 +138,14 @@ export function signPayload(
138
138
 
139
139
  /**
140
140
  * Discover a tenant the calling bearer provably OWNS, by creating a probe run
141
- * and reading `owner.tenantId` off its snapshot (RFC 0048). A host that scopes
141
+ * and reading `owner.tenant` off its snapshot (RFC 0048; `run-snapshot.schema.json`
142
+ * §owner — the triple is `{ tenant, workspace, principal }`, and `tenantId` was
143
+ * never the spec name: S29, 2026-08-17, found by the second sibling host, whose
144
+ * schema-correct `owner.tenant` this helper silently ignored). A host that scopes
142
145
  * webhook subscriptions by tenant membership (RFC 0093) 403s a `tenantId` the
143
146
  * bearer is not a member of — so a hard-coded tenant is wrong; the only portable
144
147
  * owned tenant is the one on a run the bearer just created. Returns `undefined`
145
- * for a single-tenant host (which omits `owner.tenantId`) or when no probe
148
+ * for a single-tenant host (which omits `owner`) or when no probe
146
149
  * fixture is available — callers then omit `tenantId`, which single-tenant hosts
147
150
  * accept. (RFC 0093 / webhooks.md §Register; suite defect fixed 2026-08-09.)
148
151
  */
@@ -155,6 +158,8 @@ export async function discoverOwnedTenant(
155
158
  const runId = (create.json as { runId?: string } | null)?.runId;
156
159
  if (typeof runId !== 'string') return undefined;
157
160
  const snap = await driver.get(`/v1/runs/${encodeURIComponent(runId)}`);
158
- const owner = (snap.json as { owner?: { tenantId?: unknown } } | null)?.owner;
159
- return typeof owner?.tenantId === 'string' ? owner.tenantId : undefined;
161
+ const owner = (snap.json as { owner?: { tenant?: unknown; tenantId?: unknown } } | null)?.owner;
162
+ if (typeof owner?.tenant === 'string' && owner.tenant.length > 0) return owner.tenant;
163
+ // Pre-S29 hosts that copied the suite's misnamed field: tolerated, never asserted.
164
+ return typeof owner?.tenantId === 'string' && owner.tenantId.length > 0 ? owner.tenantId : undefined;
160
165
  }
@@ -28,6 +28,7 @@ import { driver } from '../lib/driver.js';
28
28
  import { behaviorGate } from '../lib/behavior-gate.js';
29
29
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
30
30
  import { getA2AFakePeer } from '../lib/a2a-fake-peer.js';
31
+ import { readErrorCode } from '../lib/error-envelope.js';
31
32
 
32
33
  /**
33
34
  * Callback-shaped: the host issues A2A calls to the suite's fake peer, which records the negotiated version header.
@@ -153,9 +154,10 @@ describe('RFC 0152 §B — A2A version negotiation', () => {
153
154
  });
154
155
  if (drive.status === 404 || drive.status === 403) return;
155
156
  expect(drive.status >= 400, driver.describe('RFCS/0152 §B', 'an unsupported version MUST fail')).toBe(true);
156
- const err = (drive.json as { error?: { code?: string; retriable?: boolean } }).error;
157
+ // S28: canonical envelope is FLAT (`error: "<code>"`, error-envelope.schema.json);
158
+ // the nested `error.code` is tolerated by the lib only through the deprecation window.
157
159
  expect(
158
- err?.code,
160
+ readErrorCode(drive.json),
159
161
  driver.describe(
160
162
  'RFCS/0152 §B',
161
163
  'the upstream version error MUST be projected through the canonical OpenWOP interop error ' +
@@ -26,6 +26,7 @@
26
26
 
27
27
  import { describe, it, expect } from 'vitest';
28
28
  import { driver } from '../lib/driver.js';
29
+ import { readErrorCode } from '../lib/error-envelope.js';
29
30
  import { seamAbsent } from '../lib/soft-skip.js';
30
31
  import { behaviorGate } from '../lib/behavior-gate.js';
31
32
  import { capabilityFamily } from '../lib/discovery-capabilities.js';
@@ -154,8 +155,10 @@ describe('RFC 0153 §A/§B — MCP revision negotiation', () => {
154
155
  drive.status >= 400,
155
156
  driver.describe('RFCS/0153 §B', 'an unsupported revision MUST fail rather than silently proceed'),
156
157
  ).toBe(true);
158
+ // S28: canonical envelope is FLAT (`error: "<code>"`, error-envelope.schema.json);
159
+ // the nested `error.code` is tolerated by the lib only through the deprecation window.
157
160
  expect(
158
- (drive.json as { error?: { code?: string } }).error?.code,
161
+ readErrorCode(drive.json),
159
162
  driver.describe(
160
163
  'RFCS/0153 §B',
161
164
  'the upstream error MUST be projected through the canonical OpenWOP interop envelope — a ' +
@@ -35,6 +35,7 @@ import { driver } from '../lib/driver.js';
35
35
  import { behaviorGate } from '../lib/behavior-gate.js';
36
36
  import { readCapabilityFamily } from '../lib/discovery-capabilities.js';
37
37
  import { SCHEMAS_DIR } from '../lib/paths.js';
38
+ import { readErrorCode, readRetriable } from '../lib/error-envelope.js';
38
39
 
39
40
  const GATE = 'openwop-self-hosted-runner';
40
41
 
@@ -51,23 +52,14 @@ function readSchema(name: string): JsonSchema {
51
52
  return JSON.parse(readFileSync(join(SCHEMAS_DIR, name), 'utf8')) as JsonSchema;
52
53
  }
53
54
 
54
- /** Read the canonical error code from a response body (tolerant of shapes). */
55
- function errCode(json: unknown): string | undefined {
56
- const j = json as { error?: unknown; code?: unknown };
57
- if (typeof j?.code === 'string') return j.code;
58
- if (typeof j?.error === 'string') return j.error;
59
- const e = j?.error as { code?: unknown } | undefined;
60
- if (e && typeof e.code === 'string') return e.code;
61
- return undefined;
62
- }
63
-
64
- /** Read a boolean `retriable` flag from either `{retriable}` or `{error:{retriable}}`. */
65
- function retriable(json: unknown): boolean | undefined {
66
- const j = json as { retriable?: unknown; error?: { retriable?: unknown } };
67
- if (typeof j?.retriable === 'boolean') return j.retriable;
68
- if (j?.error && typeof j.error.retriable === 'boolean') return j.error.retriable;
69
- return undefined;
70
- }
55
+ // S28 (2026-08-17): the HTTP error envelope is READ through lib/error-envelope.ts
56
+ // flat `{ error: "<code>", message, details.retriable }` per error-envelope.schema.json
57
+ // (S22), with the pre-S22 nested `{ error: { code, retriable } }` tolerated only through
58
+ // the deprecation window. The local readers this replaced accepted top-level `retriable`
59
+ // or nested `error.retriable` and never `details.retriable`, so a schema-valid host could
60
+ // not pass the retriable-flag leg at all (openwop-app H27 → S28).
61
+ const errCode = readErrorCode;
62
+ const retriable = readRetriable;
71
63
 
72
64
  interface SelfHostedRunner {
73
65
  supported?: boolean;