Uh oh!
There was an error while loading. Please reload this page.
chore(plugin-webhooks): declare sys_webhook's data-API exposure explicitly — and record that it narrows nothing (#9756) - #9927
Conversation
…itly (#9756) Three cards (#7799, #7986, #8025 option 2) each observed that `sys_webhook` declared no `enable` block and named narrowing its read surface as the next step; none owned the line, so the full default API held by omission rather than by judgement. The census #9756 mandated (measured before writing anything) derives all six primitives: the Setup/Studio console needs get/list/create/update/delete (`userActions` opens all three writes, four list views, `nav_webhooks`), a predicate deactivate/delete over sys_webhook is a supported operator gesture (#4639, with a self-heal branch built for it) and gates on `bulk`, and every other consumer — AutoEnqueuer, bootstrapDeclaredWebhooks, provenance stamp, redeliver-guard, the secret sweep — reaches the rows through `engine.*`, which never consults `enable.apiMethods`. So the declaration records the posture; it does NOT narrow the surface. The six primitives resolve to the closure the absent block already produced, and that equality is pinned rather than left for a later reader to rediscover. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
…9756) Published package (plugin-webhooks 17.0.0, not private) whose shipped object metadata changed ⇒ patch changeset. Not declared-breaking: nothing authorable is removed or renamed, and the effective operation closure is unchanged, so no ADR-0087 disposition marker is required. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PnJHU45vPJj5UQrxe946Bx
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 3 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 335a16b1e145b9988d29742ca6d76864b36a5dfe && git checkout 335a16b1e145b9988d29742ca6d76864b36a5dfe
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d7c42405517bd7ffbe46d570fc2848b4b1cc597e d9653e87bf87ba927c3e12dbe9008526b7fe0fbb && git checkout -B drift-repro d7c42405517bd7ffbe46d570fc2848b4b1cc597e && git merge --no-ff d9653e87bf87ba927c3e12dbe9008526b7fe0fbb
node scripts/docs-audit/affected-docs.mjs --json d7c42405517bd7ffbe46d570fc2848b4b1cc597e
|
Uh oh!
There was an error while loading. Please reload this page.
Part of #9756
Part of, not a closing reference.#9756 asked for a read-surface narrowing. The census it mandated shows that narrowing does not exist as anenable.apiMethodschange, so what landed here is the other half — the posture written down — and the card should stay open for triage to re-grade with the measurement in hand. Merging this must not close it.The census — the deliverable that authorizes the edit
Taken on
origin/main@b0300556dbefore anything was edited. Each row is a consumer that reachessys_webhook, and which API it arrives through is the load-bearing column:enable.apiMethodsgates only REST (rest-server.ts), thecallDatadispatcher (runtime/api-exposure.ts) and the MCP data bridge (mcp/stdio-data-bridge.ts). ObjectQL itself never consults it.nav_webhookspackages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts:125/api/v1/data/sys_webhooklistactive,inactive,by_object,all_webhooks)sys-webhook.object.ts:58-98listsys-webhook.object.ts:51(userActions)getsys-webhook.object.ts:51(userActions: { create, edit, delete })createupdatedeleteauto-enqueuer.ts:952-963(handleSelfHealEvent, adata.records.*branch built for exactly this gesture, #4639)updateMany/deleteMany→rest-server.ts:10564,10619bulkAutoEnqueuersubscription-cache refresh — the card's citeddefn.headersreaderauto-enqueuer.ts:320(engine.find)bootstrapDeclaredWebhooks— materializes code-declared webhooksbootstrap-declared-webhooks.ts:165,201,225(engine.find/update/insert,SYSTEM_CTX)stampWebhookProvenancewebhook-provenance.ts:59(engine.find)redeliver-guardredeliver-guard.ts:82(engine.findOne)migrateLegacyWebhookSecretsboot sweepmigrate-webhook-secrets.ts:91,115headers_secretwrite-shape gatewebhook-headers-gate.ts(lifecycle hooks)packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts:50(engine.find)Escalation fork (triage): not triggered. No SDK caller, example app,
objectuicomponent or any other consumer readssys_webhookthrough the data API — the only gated reader is the admin/operator console.objectuinames the object in two tests only, both as a fixture for themanagedBy: 'config'bucket, neither issuing a read.⇒ every one of the six primitives is required by a real, measured consumer.
What landed
The condition #9756 measured is real and was re-verified at the branch point: the object declared no
enableblock, so it held the full default data API — not because anyone judged that correct, but because three cards each pointed at the next one. This is that decision, written down.⛔ The finding: this narrows nothing, and
apiMethodscannot narrow itresolveEffectiveApiMethods(packages/spec/src/data/api-derivation.ts) seeds itsunrestrictedbranch with the very sameAPI_PRIMITIVESset, so all six primitives resolve to the operation closure the absent block already produced. Measured consequences:allowedarray and/me/permissionsapiOperations— is byte-identical;callDataaction reaches an operation whose answer differs (history/restore/purgeare the only operations whose gate answer moves, and no route gates on any of them);modechanges,unrestricted→restricted.Why no narrowing exists here. The reachable cleartext #9756 is about —
url, and a legacy row's un-migrateddefinition_json.headers(still read and warned about byreadLegacyHeaders,auto-enqueuer.ts:579) — is served byget/list, which is exactly what the console requires. Any set that removes them removes the admin surface with it. Andbulk, the one primitive that could be dropped without touching reads, gates a deliberately supported operator gesture (row 5) — dropping it would also require an exemption entry inpackages/spec's conformance ratchet, outside this card's declared file surface.The sibling
sys_http_deliveryholds['get','list']because it is engine-owned — written only bySqlHttpOutboxthrough context-less raw-engine writes, never authored.sys_webhookis a first-class admin authoring surface. That difference is why the sibling's shape could not simply be copied, and it is recorded in the object's docblock so the next reader does not re-derive it.grep -A8 '^\s*enable:' sys-webhook.object.ts, and that grep flips from empty to a hit here while the exposure is unchanged. The docblock, the changeset and the pin below all say so explicitly, so a future survey cannot read the block's presence as an all-clear.Masking
urlstays out of scope — #8025 settled that on engineering grounds and that ruling stands; nothing here reopens it.Pins —
src/sys-webhook-api-exposure.test.ts(7 tests)CENSUS.length >= 10);effectiveOperationsArray(declared)equalseffectiveOperationsArray(absent)and the primitive sets match, withmodeasserted as the one and only difference — so a later change that really does move the surface has to confront this test;checkManagedApiMethodAffordances(SysWebhook)is empty, soreconcileManagedApiMethodsstrips nothing at boot (closinguserActions.deletewould otherwise takedeleteoff the API with only aconsole.warn);['get','list']block answers'method-not-allowed'forcreate/update/delete/bulk, with reads staying open as the control that makes it an oracle rather than a helper that refuses everything;{ apiEnabled: false }answers'api-disabled'. Both envelope codes are pinned as ADR-0112-registered vocabulary. The{ status, code }envelopes themselves belong to@objectstack/restand the MCP bridge and are pinned there — this package does not grow a dependency to assert someone else's envelope.Verification — all at
d9653e87b, the final commitpnpm --filter @objectstack/plugin-webhooks testTest Files 9 passed (9)·Tests 119 passed (119)pnpm --filter @objectstack/plugin-webhooks typechecktsc --noEmitechoed — not a zero-match pass)check:slot-lookup,check:test-source-alias,check:type-source-resolutioncheck:i18nplugins/plugin-webhooks in sync (4 bundle(s))— first run reportedPREREQUISITE NOT MET — the workspace CLI is not builtand checked nothing; re-run green after building the CLIcheck:type-check-debt(--re-measure)33 ledger entr(ies) re-measured … none above its recorded number— needed the full workspace closure built; it refuses loudly rather than measuring a different worldcheck:nul-bytes,check:engine-double-contract,check:where-matcher,check:query-options-erasure,check:type-check-coveragecheck:changeset-gate-self-tests,check:objectui-changeset,check-adr-0087-registration,check-changeset-no-major,check-empty-changesetscripts/docs-audit/check-affected-docs.mjsGates re-derived against the actual diff with
node scripts/pm/dispatch-gates.mjs(no hand-built path list); it addedcheck:i18n, the changeset family and the convention-triggered ratchets on top of the dispatched list. Every exit code was captured by redirecting first, never through a pipe.Ablation — direction predicted before running, and it is not "everything goes red": removing the
enableblock gives 2 failed | 5 passed, signaturesexpected undefined to deeply equal [ 'get', 'list', 'create', …(3) ]andexpected 'unrestricted' to be 'restricted'. The five that stay green — all 10 census admissions, registration survival, and the reachable-cleartext assertions — are the mechanical proof of the finding: every consumer-facing property is indifferent to whether this block exists. No rebuild leg was needed and none could hide a false green: the subject is imported relatively (./sys-webhook.object.js), andpackages/plugins/plugin-webhooks/distdoes not exist at all while the suite runs, so resolution is provably through source. Restored byte-identically —git hash-objecte2c6e61fdd9c4ceb33eed17c473ecde959e0cf3dbefore and after,git status --porcelainclean (no staged/unstaged split) — and the restore leg re-verified green, 7 passed.Changeset
patchon@objectstack/plugin-webhooks. Rule applied: the package is published (17.0.0, notprivate) and its shipped object metadata changed ⇒ a changeset is owed. It is not declared-breaking — nothing authorable is removed or renamed and the effective closure is unchanged — so no ADR-0087 disposition marker is required, andcheck-adr-0087-registrationagrees. The changeset body leads with the fact that the effective surface is unchanged, so an upgrading consumer is not told a narrowing happened.Generated by Claude Code