docs(plugin-webhooks): bootstrapDeclaredWebhooks names only the half it delivers, and a pin test holds the other half open - #19193
Merged
Conversation
…elivers, and a pin test holds the other half open `bootstrapDeclaredWebhooks` opened with "materialize stack/connector-declared `webhooks` into `sys_webhook` rows so the dispatcher can actually see them". The connector half was false, and false in the one sentence a reader trusts when deciding whether a connector's nested `webhooks[]` reaches the dispatcher. Measured against the real registration path, not grepped: `ObjectQL.registerApp` over a manifest declaring BOTH shapes registers `webhook` items for the top-level `webhooks:` collection only. The connector's nested `webhooks[]` stays inside the connector document — every registrar of the `webhook` type (`registerMetadataCollections`'s `METADATA_ARRAY_KEYS`, the artifact loader's collection map, the shared `PLURAL_TO_SINGULAR` spelling table) is keyed on a TOP-LEVEL collection name and none walks into a `connectors:` entry. The repair is the prose, not the code: `packages/spec` declares the connector surface unenforced twice (`automation/webhook.zod.ts`, and `WebhookConfigSchema` in `integration/connector.zod.ts`), so the docblock is the text out of step. A corrected sentence is still prose, so the claim it makes is now asserted by `bootstrap-declared-webhooks.connector-nested.test.ts` against a real ObjectQL boot, with an anti-vacuity control in the same corpus: a top-level `webhooks:` entry that IS registered and IS seeded. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF
…'s path stops `check:engine-double-contract` pinned the new fake's `update()`: a double looser than `ObjectQL.update` is how a dead route ships with a green suite. The verb is not stubbed-and-asserted but removed — `find` always answers empty in this file, so the seeder always takes the INSERT branch and never dispatches an update. A fake that stops where the path stops has nothing to be looser than, and a future change that DID reach the update branch now fails loudly here instead of passing against a permissive stub. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01AhQASwqJr2Z7XfGWUdvnbF
Contributor
📓 Docs Drift Check
What this run could not see
Coarse fallback — 4 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
huangyiirene
marked this pull request as ready for review
September 19, 2026 10:20
huangyiirene
deleted the
claude/issue-18613-connector-nested-webhooks
branch
September 19, 2026 10:49
This was referenced Sep 19, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #18613
Clause-②: no
What was false, and what this changes
bootstrapDeclaredWebhooksopened with:The connector half was false, and false in exactly the sentence a reader trusts when deciding whether a connector's nested
webhooks[]reaches the dispatcher. This PR corrects the prose and pins the corrected claim as behaviour. It does not build the connector bridge — that is #3197's card.Direction is ruled by
packages/spec, which declares the connector surface unenforced in two places, so the docblock is the text out of step:packages/spec/src/automation/webhook.zod.ts:70—(Connectorwebhooksremain NOT-yet-enforced — see #3197.)packages/spec/src/integration/connector.zod.ts(WebhookConfigSchema) — "NOT YET ENFORCED — declared but ignored at registration (Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197) ... a connector'swebhooks(includingevents) parse and are stored, but no runtime dispatches, emits, or filters on them."⛔ Zero
packages/specin this diff.The premise, measured first-hand (not grepped)
The card's load-bearing premise is "a connector's nested
webhooks[]never becomes awebhookmetadata item". A token grep answers "does this string appear", which is the instrument that cannot tell prose from behaviour — the exact defect this card is about. So it was measured by running the realObjectQLboot registration over a manifest declaring both shapes at once:Anti-vacuity control, same corpus and same spelling convention: the same manifest's top-level
webhooks:entry IS registered. That is what would have made the zero not-zero; without it the reading would also pass on an engine that registered nothing, on a misspelled metadata type, or on a manifest the engine rejected.Why the zero holds — the registrar census. Every registrar of the
webhookmetadata type keys on a TOP-LEVEL collection name, and none walks into aconnectors:entry:registerMetadataCollections(packages/objectql/src/engine.ts)METADATA_ARRAY_KEYSover the manifest / nested-plugin document itselfpackages/metadata/src/plugin.ts)webhooks: 'webhook'packages/spec/src/meta-spelling/manifest-collection-spelling.ts)PLURAL_TO_SINGULARreadDeclared's two sources reduce to the same store:MetadataFacade.list(type)delegates toregistry.listItems(canonicalMetadataServiceType(type)).A1 and A2 from the dispatch also hold on
origin/main@eeaa882459: the docblock still carried the clause, and:141is still the function's only source.The #18582 liveness ledger row is the one that was RIGHT
packages/spec/liveness/connector.json→props.webhooksreadsstatus: "dead",verifiedAt: 2026-09-17, with a note naming the same mechanism measured above. The ledger and the docblock disagreed because the docblock was wrong. ⇒ nothing to file about the ledger, and after this PR the two agree.Prose alone would rot the same way, so the claim is pinned
New file
bootstrap-declared-webhooks.connector-nested.test.tsasserts, against a realObjectQLboot, that the connector-nested webhook is neither awebhookmetadata item nor asys_webhookrow — with the top-level control asserted green in both legs.Ablation (one-off, reverted — not a committed file). A scratch copy of the pin simulated the bridge by registering the connector-nested webhook as a
webhookmetadata item, exactly as a hoist insideregisterMetadataCollectionswould produce it. Both legs went red on the SUBJECT while the CONTROL stayed green:Restore proof: the scratch file was removed and
git status --porcelain/git diff HEADboth print nothing. Observed direction was RED, as predicted.packages/objectql/src/engine.ts, because a write to that path was refused by this container's permission classifier ("Modify Shared Resources") and that path is outside this card's declared file surface. So the ablation proves the pin fires on the observable a hoist would produce; it does not exercise a hoist branch written into the engine.Also corrected: the test file's copy of the same false clause
bootstrap-declared-webhooks.test.ts's header carried the identical "stack/connector-declared" claim. Measured before editing: the wordconnectorappeared in that file only in that sentence — every fixture seedsdeclared.webhookdirectly, so the suite never made the claim its header did.Gates
Re-derived for the actual diff with
node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack(no hand-written path list), exit codes landed to a file before any pipe, then reconciled:check:engine-double-contractrepaired below.pnpm check:engine-double-contract— exit 1 on the first run, a real finding on this PR's new fake, now exit 0. The remedy taken is the in-surface one: the fake'supdate()was removed, not stubbed-and-asserted.findalways answers empty here, so the seeder always takes the INSERT branch and never dispatches an update; a fake that stops where the path stops has nothing to be looser than. ⛔ The gate's other offer —--writetoscripts/engine-double-contract.pinned.json— was declined because that path is outside this card's declared file surface.PREREQUISITE NOT MET, i.e. NOT MEASURED, not green:check:dual-build-cjs-loads,check:i18n,check:type-check-debt. All three read built output of the whole tree; a fullpnpm builddoes not fit this container's foreground cap, so they are declared to CI.Package legs, both exit 0 through
scripts/pm/os-verify-lock.sh:Repo-wide lint was run in full rather than narrowed —
pnpm lint(eslint . --no-inline-config) exit 0 atd75d57ae2, the final commit.Changeset:
skip-changeset, measuredThe diff is comments plus one test file.
@objectstack/plugin-webhooks'sfiles[]isdist,README.md,CHANGELOG.md; afterpnpm --filter @objectstack/plugin-webhooks build:dist/,README.md,CHANGELOG.md. The new test file produces nodist/artifact.bootstrapDeclaredWebhooks, a symbol that IS published: 8 files indist/. The corpus and the grep are live.⇒ nothing this PR touches is shipped. ⛔ No label was written by this run — label writes belong to the PM seat on this dispatch, which is where the
skip-changesetapplication is requested.Acceptance notes
Observations made in passing, ⛔ not filed and not in this PR:
WebhookConfigSchemaextendsWebhookSchema, so a connector's nested entries and a stack's top-level ones are near-identical documents in two different places with opposite runtime fates. Not a defect under the three filing classes — the spec says so out loud in both files. Successor: whoever takes Audit: several event/subscription/connector enums are schema-only (declared, no runtime consumer) #3197.check:commit-card-trailersrefuses at pre-push. Rewritten to the model-free pair before pushing; no history was rewritten because nothing was published. Successor: none — it is a per-run collision, reported to the PM seat rather than filed.Generated by Claude Code