@openwop/openwop-conformance 1.11.0 → 1.13.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 +26 -0
- package/README.md +2 -2
- package/coverage.md +11 -6
- package/package.json +1 -1
- package/src/lib/agentDeployment.ts +117 -0
- package/src/lib/agentEval.ts +83 -0
- package/src/lib/agentOrgChart.ts +82 -0
- package/src/lib/triggerBridge.ts +74 -0
- package/src/scenarios/agent-deployment-lifecycle.test.ts +147 -0
- package/src/scenarios/agent-eval-run.test.ts +145 -0
- package/src/scenarios/agent-org-chart-scoping.test.ts +137 -0
- package/src/scenarios/org-position-no-authority-escalation.test.ts +78 -0
- package/src/scenarios/trigger-bridge-delivery.test.ts +126 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Durable trigger bridge — delivery model (RFC 0083 §C) — behavioral.
|
|
3
|
+
*
|
|
4
|
+
* Profile-gated on `openwop-trigger-bridge` (derived from the live discovery
|
|
5
|
+
* doc per RFC 0083 §D: the bridge advertised + a dead-letter sink + a durable
|
|
6
|
+
* source). Soft-skips when the profile isn't derived (default) / hard-fails
|
|
7
|
+
* under `OPENWOP_REQUIRE_BEHAVIOR=true`. The always-on wire-shape coverage
|
|
8
|
+
* lives in `trigger-bridge-shape.test.ts`; this asserts host BEHAVIOR via the
|
|
9
|
+
* `POST /v1/host/sample/trigger-bridge/deliver` seam + the test event-log seam:
|
|
10
|
+
*
|
|
11
|
+
* 1. DEDUP (§C-1) — the same `dedupKey` delivered twice is effectively-once:
|
|
12
|
+
* exactly one `trigger.delivery.attempted { outcome:"delivered" }` for that
|
|
13
|
+
* key (at-least-once collapses to once within the retention window).
|
|
14
|
+
* 2. RETRY → DEAD-LETTER (§C-2 + RFC 0053) — an exhausted retry policy lands a
|
|
15
|
+
* terminal `trigger.delivery.attempted { outcome:"dead-lettered" }` and a
|
|
16
|
+
* `trigger.subscription.state.changed { toState:"dead-lettered" }`; both
|
|
17
|
+
* content-free (SR-1: ids/states/counters only).
|
|
18
|
+
* 3. CAUSATION (§C / RFC 0040) — a successful delivery's resulting run carries
|
|
19
|
+
* `run.started.causationId` == the delivery id (trigger → run is resolvable
|
|
20
|
+
* via `/ancestry`).
|
|
21
|
+
*
|
|
22
|
+
* Each leg soft-skips independently (seam absent / event-log seam absent).
|
|
23
|
+
*
|
|
24
|
+
* Spec references:
|
|
25
|
+
* - https://github.com/openwop/openwop/blob/main/spec/v1/trigger-bridge.md (§C)
|
|
26
|
+
* - https://github.com/openwop/openwop/blob/main/RFCS/0083-durable-trigger-and-channel-bridge-profile.md
|
|
27
|
+
* - https://github.com/openwop/openwop/blob/main/spec/v1/profiles.md (§openwop-trigger-bridge)
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import { describe, it, expect } from 'vitest';
|
|
31
|
+
import { driver } from '../lib/driver.js';
|
|
32
|
+
import { behaviorGate } from '../lib/behavior-gate.js';
|
|
33
|
+
import {
|
|
34
|
+
isTriggerBridgeProfileAdvertised,
|
|
35
|
+
driveDelivery,
|
|
36
|
+
DELIVERY_OUTCOMES,
|
|
37
|
+
SUBSCRIPTION_STATES,
|
|
38
|
+
} from '../lib/triggerBridge.js';
|
|
39
|
+
import { queryTestEvents, isEventLogSeamAvailable, resetTestSeam } from '../lib/event-log-query.js';
|
|
40
|
+
|
|
41
|
+
const CONTENT_FREE_FORBIDDEN = ['body', 'headers', 'payload', 'secret', 'credentials', 'token', 'apiKey'];
|
|
42
|
+
|
|
43
|
+
function expectContentFree(payload: Record<string, unknown>, where: string): void {
|
|
44
|
+
for (const f of CONTENT_FREE_FORBIDDEN) {
|
|
45
|
+
expect(
|
|
46
|
+
!(f in payload),
|
|
47
|
+
driver.describe('RFC 0083 §C (SR-1)', `${where} MUST be content-free (no ${f})`),
|
|
48
|
+
).toBe(true);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe('trigger-bridge-delivery (RFC 0083 §C)', () => {
|
|
53
|
+
it('de-dups by dedupKey, retries to dead-letter, and links delivery→run causation', async () => {
|
|
54
|
+
if (!behaviorGate('openwop-trigger-bridge', await isTriggerBridgeProfileAdvertised())) return;
|
|
55
|
+
if (!(await isEventLogSeamAvailable())) return; // event-log seam absent — soft-skip
|
|
56
|
+
|
|
57
|
+
// ---- Leg 1: dedup → effectively-once (§C-1) ---------------------------
|
|
58
|
+
const dedup = await driveDelivery({ scenario: 'dedup', dedupKey: 'conformance-dedup-key', source: 'queue' });
|
|
59
|
+
if (dedup === null) return; // delivery seam unwired — soft-skip the whole behavioral suite
|
|
60
|
+
if (dedup.runId || dedup.subscriptionId) {
|
|
61
|
+
const subId = dedup.subscriptionId;
|
|
62
|
+
const q = await queryTestEvents(dedup.runId ?? '__dedup__', { type: 'trigger.delivery.attempted' });
|
|
63
|
+
if (q.ok) {
|
|
64
|
+
const deliveredForKey = q.events.filter(
|
|
65
|
+
(e) => e.payload.dedupKey === 'conformance-dedup-key' && e.payload.outcome === 'delivered',
|
|
66
|
+
);
|
|
67
|
+
// Effectively-once: a repeated dedupKey MUST NOT produce two 'delivered' attempts.
|
|
68
|
+
expect(
|
|
69
|
+
deliveredForKey.length <= 1,
|
|
70
|
+
driver.describe('trigger-bridge.md §C-1', 'a repeated dedupKey MUST be effectively-once (≤1 delivered attempt)'),
|
|
71
|
+
).toBe(true);
|
|
72
|
+
for (const e of q.events) {
|
|
73
|
+
expect(
|
|
74
|
+
typeof e.payload.outcome === 'string' && DELIVERY_OUTCOMES.includes(e.payload.outcome as string),
|
|
75
|
+
driver.describe('run-event-payloads.schema.json#triggerDeliveryAttempted', 'outcome MUST be delivered|retrying|dead-lettered'),
|
|
76
|
+
).toBe(true);
|
|
77
|
+
expectContentFree(e.payload, 'trigger.delivery.attempted');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
void subId;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ---- Leg 2: retry → dead-letter (§C-2 + RFC 0053) --------------------
|
|
84
|
+
const exhaust = await driveDelivery({ scenario: 'exhaust', source: 'webhook' });
|
|
85
|
+
if (exhaust && (exhaust.runId || exhaust.subscriptionId)) {
|
|
86
|
+
const key = exhaust.runId ?? '__exhaust__';
|
|
87
|
+
const dq = await queryTestEvents(key, { type: 'trigger.delivery.attempted' });
|
|
88
|
+
if (dq.ok && dq.events.length > 0) {
|
|
89
|
+
const terminal = dq.events.sort((a, b) => a.sequence - b.sequence)[dq.events.length - 1]!;
|
|
90
|
+
expect(
|
|
91
|
+
terminal.payload.outcome === 'dead-lettered',
|
|
92
|
+
driver.describe('trigger-bridge.md §C-2', 'an exhausted retry policy MUST terminate in a dead-lettered delivery'),
|
|
93
|
+
).toBe(true);
|
|
94
|
+
}
|
|
95
|
+
const sq = await queryTestEvents(key, { type: 'trigger.subscription.state.changed' });
|
|
96
|
+
if (sq.ok && sq.events.length > 0) {
|
|
97
|
+
const toDeadLetter = sq.events.some((e) => e.payload.toState === 'dead-lettered');
|
|
98
|
+
expect(
|
|
99
|
+
toDeadLetter,
|
|
100
|
+
driver.describe('trigger-bridge.md §B', 'the subscription MUST transition to dead-lettered on exhaustion'),
|
|
101
|
+
).toBe(true);
|
|
102
|
+
for (const e of sq.events) {
|
|
103
|
+
expect(
|
|
104
|
+
typeof e.payload.toState === 'string' && SUBSCRIPTION_STATES.includes(e.payload.toState as string),
|
|
105
|
+
driver.describe('trigger-bridge.md §B', 'toState MUST be in the four-state vocabulary'),
|
|
106
|
+
).toBe(true);
|
|
107
|
+
expectContentFree(e.payload, 'trigger.subscription.state.changed');
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---- Leg 3: delivery → run causation (§C / RFC 0040) -----------------
|
|
113
|
+
const delivered = await driveDelivery({ scenario: 'deliver', source: 'schedule' });
|
|
114
|
+
if (delivered?.runId) {
|
|
115
|
+
const rq = await queryTestEvents(delivered.runId, { type: 'run.started' });
|
|
116
|
+
if (rq.ok && rq.events[0]) {
|
|
117
|
+
expect(
|
|
118
|
+
typeof rq.events[0].causationId === 'string' && (rq.events[0].causationId as string).length > 0,
|
|
119
|
+
driver.describe('trigger-bridge.md §C / RFC 0040', 'the delivered run.started MUST carry the delivery causationId (resolvable via /ancestry)'),
|
|
120
|
+
).toBe(true);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await resetTestSeam();
|
|
125
|
+
});
|
|
126
|
+
});
|