Skip to content

Commit 0715fac

Browse files
liveness(webhook): a declared webhook's label and description are live on the Studio metadata list (#24972)
Closes #24900 Clause-②: no ## What this does Re-grades a declared webhook's `label` and `description` from `dead` to `live` in `packages/spec/liveness/webhook.json`. It records the reader and producer the card asked for, and carries the re-grade to the one test and the two prose sites that restated the old verdict. The 2026-10-19 `dead` verdict measured only the Setup `sys_webhook` list, which lost the declared webhook when the boot seeder retired (ADR-0131 D2). A second surface reads the declared registry item itself and was never cut. - **Reader** (objectui, at the `.objectui-sha` pin `616df6598b42`): `ResourceListPage.tsx#defaultColumns` (`webhook` registers no `listColumns` and no `ListPage`, so `DefaultMetadataList` draws `label` and `description` through `row.item[c.key]`), and `QuickFind.tsx#MetadataQuickFind`. `webhook` has no registered preview and no Setup catalog. - **Producer** (measured by booting): `pnpm dev -- --fresh` on `examples/app-showcase` at `223ae939bb`, with a probe `label` and `description` authored on its declared webhook (reverted, hash-verified), signed in as the dev admin. `GET /api/v1/meta/webhook` answered 200, and its one item carried both probe values with `_packageId: com.example.showcase`. `GET /api/v1/meta/types` lists `webhook`, and that package has `scope: project`. - **Not readers, checked:** in `plugin-webhooks`, `auto-enqueuer.ts#projectDeclared` (delivery props only), `redeliver-guard.ts#judgeDeclared` and `bindDeclaredWebhookNameGuard` (name and switch only). In the runtime, `catalog-activation.ts` `DECLARATION_LOOKUPS.webhook` (name, packageId). In the CLI, the ownership port `declaredWebhooks.list` (name, packageId). `organization-ownership-apply.ts#isSwitchOnlyEdit` compares a seeded row with that row's own stored `definition_json`: a migration read of 18.0 data, not of the authored key. This follows the `hook.label` and `email_template.label` rows, under the designer-previews ruling (liveness README, "Designer previews count as consumers"). For a display key, being shown to a human is the whole of its claimed effect. No enforce-or-retire decision is owed. ## Files - `packages/spec/liveness/webhook.json`: both rows are now `live`, with `evidenceScope: cross-repo`, `evidence`, `producer` and `verifiedAt: 2026-10-20`. The `authorHint` is removed, the old note is kept as HISTORY, and the file `_note` is corrected. - `packages/spec/liveness/state-counts/webhook.md`: regenerated with `gen:liveness-counts`; live/dead moves from 17/2 to 19/0. - `packages/lint/src/lint-liveness-properties.test.ts`: the webhook case reads the real ledger and now pins silence on a declared webhook that authors every prop. - `examples/app-showcase/src/automation/webhooks/index.ts` (comment only) and `docs/qa/platform-checklist/areas/integration-system.json` (source line): both now state the `live` grading and the Studio reader. - `.changeset/24900-webhook-label-description-live.md`: `@objectstack/spec` patch. The spec package ships `liveness/` in `files[]`, and the advisory `os lint` reads it from there, so this change publishes: the `liveness-dead-property` warning on these two keys stops. That is why there is no `skip-changeset`. ## Verification (head `5848b7dd99`) - `pnpm --filter @objectstack/spec check:liveness`: exit 0, all-clear line printed. - `pnpm check:platform-checklist`: exit 0. - Lint test `src/lint-liveness-properties.test.ts`: against the re-graded ledger before the test edit, 1 failed / 102 passed (`warns on a declared webhook's label and description only`, expected an empty list to equal two findings). After the edit, 103 passed. - `node scripts/pm/dispatch-gates.mjs --commands`: 78 families derived. 76 exited 0 and `--ran` reconciles 78 of 78. The two non-zero results, `check:dts-closure` and `check:dual-build-cjs-loads`, are NOT MEASURED. Reason: the local dists were built with `OS_SKIP_DTS=1`, so they hold no `.d.ts` (`@objectstack/types`, triggers, verify), and this diff touches no package source. CI builds with declarations. - Families 1 to 70 ran at `bf94c93377`. The 8 changeset families the changeset commit added ran at `5848b7dd99`. Between those heads only `.changeset/` changed. ## Acceptance notes - The security issue measured on the same boot is filed separately as #24966: `GET /api/v1/meta/webhook` serves a declared webhook's authored `secret` verbatim, because `webhook` has no metadata-type redactor. Nothing in this PR touches it. - `origin/main` was merged once (no rebase). Nothing it brought adds a reader of these keys. --- _Generated by [Claude Code](https://claude.ai/code/session_01D6F9axfoPzR7t8G1J7Ytzz)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent f32d795 commit 0715fac

6 files changed

Lines changed: 35 additions & 26 deletions

File tree

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os lint` no longer tells authors to remove a declared webhook's `label` or `description`
6+
7+
Clause-②: no (a liveness-ledger re-grade: no schema, accept set, served body or error code moves; only the advisory `liveness-dead-property` warning on these two keys stops)
8+
9+
The shipped liveness ledger (`liveness/webhook.json`) graded a declared webhook's `label` and `description` `dead` once the boot seeder that copied them into a `sys_webhook` row retired, and the advisory lint warned "Remove it" on every webhook that authored them. They are read: the Studio metadata list and quick-find show both keys, read off `GET /api/v1/meta/webhook`. Both rows are now `live`, so a declared webhook may keep its `label` and `description` and no warning is printed for them. Nothing else changes: the dispatcher still delivers on the same props as before.

‎docs/qa/platform-checklist/areas/integration-system.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -642,7 +642,7 @@
642642
],
643643
"source": [
644644
"packages/spec/src/automation/webhook.zod.ts#WebhookTriggerType (WebhookTriggerType enum + why undelete/api are absent; materialization contract; strict shape #4001)",
645-
"packages/spec/liveness/webhook.json (9 props live on the registry read; label/description dead since the boot seeder retired)",
645+
"packages/spec/liveness/webhook.json (9 props live on the registry read; label/description live on the Studio metadata list and quick-find, which read the declared item over GET /api/v1/meta/webhook)",
646646
"packages/plugins/plugin-webhooks/src/declared-webhooks.ts + auto-enqueuer.ts (the registry read and its remaps; trigger→event mapping incl. the #4639 bulk pair; #3196 unknown-trigger warn; the activation-ledger switch; an 18.0 database's seeded rows read beside the declaration)",
647647
"packages/services/service-messaging/src/http-outbox.ts#HttpRedeliverError (delivery statuses, attempts; HttpRedeliverError + assertHttpRedeliverable/assertRedeliverAllowed — terminal-only, the attempts===0 parked-row refusal, the fail-closed guard) + packages/services/service-messaging/src/http-signature-at-rest.integration.test.ts (redelivery replays the stored signature byte-for-byte)",
648648
"packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts#session (POST /api/v1/webhooks/redeliver — session auth with NO capability gate by design ('every authenticated user counts'), #10740 activeOrganizationId threading, the 401/400/404/409/500 arms) + (the #8069 redeliver-guard install; its ABSENCE is an error-level log, not a silent pass)",

‎examples/app-showcase/src/automation/webhooks/index.ts‎

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import { defineWebhook } from '@objectstack/spec/automation';
44

55
/**
66
* Outbound webhook — fans out task lifecycle events (create/update/delete)
7-
* to an external endpoint. No `label` / `description`: a declared webhook has
8-
* no Setup row to show them on, and the liveness ledger marks both dead.
7+
* to an external endpoint. A declared webhook has no Setup row, but a `label` /
8+
* `description` authored here is not inert: the liveness ledger grades both
9+
* `live`, because the Studio metadata list and quick-find show them, read off
10+
* `GET /api/v1/meta/webhook`.
911
* Authored via `defineStack({ webhooks })`; the webhooks plugin's dispatcher
1012
* reads this declaration from the registry (ADR-0131 D2) — no `sys_webhook`
1113
* row is written for it.

‎packages/lint/src/lint-liveness-properties.test.ts‎

Lines changed: 7 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -251,12 +251,13 @@ describe('lintLivenessProperties', () => {
251251
// #3494 PRUNED the aspirational dead props from the schema and the #3489
252252
// materializer bridge made every remaining prop live. ADR-0131 D2 then
253253
// retired that bridge: a declared webhook is read from the registry and has
254-
// no Setup row, so the two display-only props, `label` and `description`,
255-
// lost their only reader and the ledger marks them dead. Every delivery-
256-
// bearing prop stays silent. Runs against the REAL webhook.json, so a ledger
257-
// flip that is not carried to authors fails here.
254+
// no Setup row. The two display-only props, `label` and `description`, stay
255+
// live all the same: the Studio metadata list and quick-find draw them off
256+
// `GET /api/v1/meta/webhook`, so the ledger grades them live and nothing
257+
// warns. Runs against the REAL webhook.json, so a ledger flip that is not
258+
// carried to authors fails here.
258259

259-
it('warns on a declared webhook\'s label and description only — every delivery-bearing prop is live', () => {
260+
it('stays silent on a declared webhook that authors every prop, label and description included', () => {
260261
const findings = lintLivenessProperties({
261262
webhooks: [{
262263
name: 'showcase_task_changed',
@@ -272,14 +273,7 @@ describe('lintLivenessProperties', () => {
272273
description: 'Sends task lifecycle events to an external system.',
273274
}],
274275
});
275-
expect(findings.map((f) => [f.rule, f.message.match(/`(\w+)`/)?.[1]])).toEqual([
276-
['liveness-dead-property', 'label'],
277-
['liveness-dead-property', 'description'],
278-
]);
279-
for (const f of findings) {
280-
expect(f.where).toBe("webhook 'showcase_task_changed'");
281-
expect(f.hint).toContain('no Setup row');
282-
}
276+
expect(findings).toEqual([]);
283277
});
284278

285279
it('stays silent on a declared webhook that authors only what is delivered on', () => {

‎packages/spec/liveness/state-counts/webhook.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,4 +12,4 @@ committed anywhere: `check:liveness` sums the shards when it reads them.
1212

1313
| Type | live | exp | elsewhere | dead | planned | classified |
1414
|---|---|---|---|---|---|---|
15-
| `webhook` | 17 | 0 | 0 | 2 | 0 | 19 |
15+
| `webhook` | 19 | 0 | 0 | 0 | 0 | 19 |

0 commit comments

Comments
 (0)