Skip to content

Commit dde1a5c

Browse files
committed
fix(spec,cli): redirect webhook authorWarn from now-live url to still-dead props (#3490)
Second half of the Test Core lockstep the bound-class + ledger flip require. The bridge (#3489) made `url` live, so the CLI compile lint's per-webhook author warning must move OFF it onto the props that are STILL dead + misleading: retryPolicy/body/payloadFields/includeSession now carry `authorWarn` (folded into definition_json but never read — authored retry/body/field-projection/session do nothing). `tags` stays unmarked (benign display). `lint-liveness-properties.test.ts` asserts the new behavior: a webhook that authors only now-live props is silent, and the showcase-shaped webhook warns exactly once — on its inert `retryPolicy`, not on `url`. isActive test comment updated (it is live now, not "unmarked dead"). Verified: lint-liveness-properties.test.ts (17) + proof-registry.test.ts (23) green; liveness gate still green (webhook 17 classified, proofs resolve).
1 parent 6becad1 commit dde1a5c

2 files changed

Lines changed: 28 additions & 20 deletions

File tree

packages/cli/src/utils/lint-liveness-properties.test.ts

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,27 +159,26 @@ describe('lintLivenessProperties', () => {
159159
expect(findings).toEqual([]);
160160
});
161161

162-
// ── webhook (#3462) ───────────────────────────────────────────────────────
163-
// The ENTIRE WebhookSchema authoring surface is disconnected from the
164-
// sys_webhook dispatcher (#3461): authoring `webhooks:` never materializes a
165-
// dispatchable row. Rather than warn on all 16 dead props, the ledger warns
166-
// once per webhook via the required `url` carrier — one no-op heads-up per
167-
// artifact. These run against the REAL webhook.json ledger.
168-
169-
it('warns once per authored webhook via the url carrier', () => {
162+
// ── webhook (#3461 bridge landed → #3490) ─────────────────────────────────
163+
// The WebhookSchema authoring surface is now materialized into dispatchable
164+
// sys_webhook rows on boot (#3489), so most props are live. The author warning
165+
// moved OFF the now-live `url` carrier onto the props that are STILL dead and
166+
// misleading — retryPolicy/body/payloadFields/includeSession (folded into
167+
// definition_json but never read) — plus experimental `authentication`. These
168+
// run against the REAL webhook.json ledger.
169+
170+
it('does not warn on a webhook that only authors now-live props (#3490)', () => {
170171
const findings = lintLivenessProperties({
171-
webhooks: [{ name: 'w1', url: 'https://hooks.example/x' }],
172+
webhooks: [{ name: 'w1', object: 'task', triggers: ['create'], url: 'https://hooks.example/x', method: 'POST', isActive: true }],
172173
});
173-
const f = findings.find((x) => x.message.includes('`url`'));
174-
expect(f).toBeDefined();
175-
expect(f!.where).toBe("webhook 'w1'");
176-
expect(f!.hint.toLowerCase()).toContain('sys_webhook');
174+
expect(findings).toEqual([]);
177175
});
178176

179-
it('emits exactly one warning for a fully-authored (showcase-shaped) webhook', () => {
180-
// object/triggers/method/retryPolicy/isActive/description are all dead too,
181-
// but only `url` carries authorWarn — so the whole no-op artifact yields ONE
182-
// finding, not one-per-prop. (isActive is default(true), deliberately unmarked.)
177+
it('emits exactly one warning for the showcase webhook — its still-dead retryPolicy', () => {
178+
// object/triggers/url/method/isActive/description are live now; only
179+
// retryPolicy is still dead + misleading (the messaging dispatcher owns a
180+
// fixed retry schedule), so the showcase-shaped webhook yields ONE finding,
181+
// not one-per-prop.
183182
const findings = lintLivenessProperties({
184183
webhooks: [{
185184
name: 'showcase_task_changed',
@@ -193,7 +192,8 @@ describe('lintLivenessProperties', () => {
193192
}],
194193
});
195194
expect(findings.length).toBe(1);
196-
expect(findings[0].message).toContain('`url`');
195+
expect(findings[0].message).toContain('`retryPolicy`');
196+
expect(findings[0].where).toBe("webhook 'showcase_task_changed'");
197197
});
198198

199199
it('also warns on authentication (experimental — HMAC-secret-only)', () => {
@@ -203,7 +203,7 @@ describe('lintLivenessProperties', () => {
203203
expect(paths(findings).some((m) => m.includes('`authentication`'))).toBe(true);
204204
});
205205

206-
it('does NOT warn on isActive (default(true) boolean, deliberately unmarked)', () => {
206+
it('does NOT warn on isActive (live now — remapped to sys_webhook.active by the bridge)', () => {
207207
const findings = lintLivenessProperties({
208208
webhooks: [{ name: 'w1', url: 'https://hooks.example/x', isActive: true }],
209209
});

packages/spec/liveness/webhook.json

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,14 +40,20 @@
4040
},
4141
"body": {
4242
"status": "dead",
43+
"authorWarn": true,
44+
"authorHint": "The dispatcher always sends its own fixed envelope (object/recordId/action/timestamp + full record); a custom `body` is never sent (#1878).",
4345
"note": "No consumer. The bridge folds it into definition_json (bootstrap-declared-webhooks.ts:203), but parseRow never reads a custom body and the dispatcher always sends its own fixed envelope (object/recordId/action/timestamp + full record, auto-enqueuer.ts:336-342). Enforce-or-remove (#1878)."
4446
},
4547
"payloadFields": {
4648
"status": "dead",
49+
"authorWarn": true,
50+
"authorHint": "No field projection — the full record is always sent; `payloadFields` is ignored (#1878).",
4751
"note": "No consumer. In definition_json now, but there is no field projection — the full record is always sent (auto-enqueuer.ts:341). Enforce-or-remove (#1878)."
4852
},
4953
"includeSession": {
5054
"status": "dead",
55+
"authorWarn": true,
56+
"authorHint": "No session is injected into the webhook payload; `includeSession` is ignored (#1878).",
5157
"note": "No consumer. In definition_json now, but no session is injected into the enqueue payload (auto-enqueuer.ts:336-342). Enforce-or-remove (#1878)."
5258
},
5359
"authentication": {
@@ -57,7 +63,9 @@
5763
},
5864
"retryPolicy": {
5965
"status": "dead",
60-
"note": "No consumer. In definition_json now, but retry is not in EnqueueHttpInput (service-messaging/src/http-outbox.ts:87-98) — the messaging HttpDispatcher owns a fixed retry schedule; authored maxRetries/backoffStrategy/initialDelayMs/maxDelayMs are ignored. (The showcase's TaskChangedWebhook still authors a retryPolicy that does nothing.) Enforce-or-remove (#1878)."
66+
"authorWarn": true,
67+
"authorHint": "The retry schedule is owned by the shared messaging HTTP dispatcher — authored maxRetries/backoffStrategy/initialDelayMs/maxDelayMs are ignored (#1878). Remove them until enforcement lands.",
68+
"note": "No consumer. In definition_json now, but retry is not in EnqueueHttpInput (service-messaging/src/http-outbox.ts:87-98) — the messaging HttpDispatcher owns a fixed retry schedule; authored maxRetries/backoffStrategy/initialDelayMs/maxDelayMs are ignored. (The showcase's TaskChangedWebhook still authors a retryPolicy that does nothing.) Enforce-or-remove (#1878). Now that authoring is otherwise live, this is the misleading-dead prop that carries the author warning (previously `url` did, as the whole-artifact-dead heads-up)."
6169
},
6270
"timeoutMs": {
6371
"status": "live",

0 commit comments

Comments
 (0)