@openwop/openwop-conformance 1.68.0 → 1.68.1

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.68.0",
3
+ "version": "1.68.1",
4
4
  "description": "Production-ready black-box conformance suite for OpenWOP v1.0 compliant servers.",
5
5
  "repository": {
6
6
  "type": "git",
@@ -135,3 +135,26 @@ export function signPayload(
135
135
  algorithmHeader: 'v1',
136
136
  };
137
137
  }
138
+
139
+ /**
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
142
+ * webhook subscriptions by tenant membership (RFC 0093) 403s a `tenantId` the
143
+ * bearer is not a member of — so a hard-coded tenant is wrong; the only portable
144
+ * 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
146
+ * fixture is available — callers then omit `tenantId`, which single-tenant hosts
147
+ * accept. (RFC 0093 / webhooks.md §Register; suite defect fixed 2026-08-09.)
148
+ */
149
+ export async function discoverOwnedTenant(
150
+ driver: { post: (p: string, b: unknown) => Promise<{ status: number; json: unknown }>; get: (p: string) => Promise<{ status: number; json: unknown }> },
151
+ probeWorkflowId = 'conformance-noop',
152
+ ): Promise<string | undefined> {
153
+ const create = await driver.post('/v1/runs', { workflowId: probeWorkflowId });
154
+ if (create.status !== 201) return undefined;
155
+ const runId = (create.json as { runId?: string } | null)?.runId;
156
+ if (typeof runId !== 'string') return undefined;
157
+ 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;
160
+ }
@@ -22,6 +22,7 @@
22
22
 
23
23
  import { describe, it, expect } from 'vitest';
24
24
  import { driver } from '../lib/driver.js';
25
+ import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
25
26
 
26
27
  async function isWebhookSupported(): Promise<boolean> {
27
28
  const disco = await driver.get('/.well-known/openwop');
@@ -40,10 +41,11 @@ describe('webhook-negative: SSRF guard rejects private destinations', () => {
40
41
  // Spec-complete except for the private URL, so validation passes and the
41
42
  // request reaches the SSRF guard. `{ url }` alone 400s `validation_error`
42
43
  // (missing `events`) before the guard runs. (Suite defect, fixed 2026-08-09.)
44
+ const ownedTenant = await discoverOwnedTenant(driver);
43
45
  const reg = await driver.post('/v1/webhooks', {
44
46
  url: 'http://127.0.0.1:65535/',
45
47
  events: ['run.completed'],
46
- tenantId: 'conformance-tenant',
48
+ ...(ownedTenant ? { tenantId: ownedTenant } : {}),
47
49
  });
48
50
  if (reg.status === 201) {
49
51
  // Host accepted — SSRF guard not implemented or bypassed.
@@ -29,6 +29,7 @@ import { createServer, type IncomingMessage, type Server, type ServerResponse }
29
29
  import { driver } from '../lib/driver.js';
30
30
  import { pollUntilTerminal } from '../lib/polling.js';
31
31
  import { isFixtureAdvertised } from '../lib/fixtures.js';
32
+ import { discoverOwnedTenant } from '../lib/webhook-receiver.js';
32
33
 
33
34
  interface DeliveredRequest {
34
35
  readonly headers: Record<string, string>;
@@ -93,10 +94,14 @@ describe('webhook-signed-delivery: end-to-end HMAC v1', () => {
93
94
  // The pre-fix `{ url }`-only body 400s on validation before delivery can occur.
94
95
  // (Suite defect, fixed 2026-08-09.) The delivered run's tenant must match this
95
96
  // subscription's tenantId — verified end-to-end against the openwop-app host.
97
+ // Derive a tenant the bearer OWNS from a probe run's snapshot — a hard-coded
98
+ // tenantId is 403'd by a host that scopes subscriptions by membership
99
+ // (RFC 0093). Single-tenant hosts return undefined ⇒ omit tenantId.
100
+ const ownedTenant = await discoverOwnedTenant(driver);
96
101
  const reg = await driver.post('/v1/webhooks', {
97
102
  url: receiver.url,
98
103
  events: ['run.completed'],
99
- tenantId: 'conformance-tenant',
104
+ ...(ownedTenant ? { tenantId: ownedTenant } : {}),
100
105
  });
101
106
 
102
107
  // SSRF guard skip: if the host rejects loopback destinations,