Skip to content

feat(lint): webhook without triggers fails at author time (ADR-0078 Phase 3) - #4565

Merged
os-zhuang merged 1 commit into
mainfrom
claude/adr-0078-phase3
Aug 2, 2026
Merged

feat(lint): webhook without triggers fails at author time (ADR-0078 Phase 3)#4565
os-zhuang merged 1 commit into
mainfrom
claude/adr-0078-phase3

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Phase 3 of #4544. Builds on the Phase 1 predicate (#4547).

The rule

webhook/without-triggers, error, in the shared @objectstack/spec/kernel predicate beside the Phase 1 rules, walked by validate-functional-completeness over stack.webhooks in both collection spellings.

A webhook with no triggers never fires on any path. It still materializes into sys_webhook and still renders in Setup looking armed — the omission is invisible on every surface an author can see.

The first source argued against this rule

The runtime skip site reads:

if (triggers.size === 0) {
    // No dispatchable triggers (or a manual-only webhook with none) —
    // skip auto-enqueue.
    return null;

That parenthetical blesses the empty case as a deliberate mode. It is structurally identical to the multiselect-without-options NON-rule, where record-validator.ts's // free-form (tags without options) is precisely why we don't flag it. On this evidence alone, the candidate stays unenforced.

The mode it names does not exist. webhook.zod.ts's #3196 note records that the api (manual/programmatic fire) trigger was removed because "no manual fire path exists — the only webhook HTTP surface re-queues already-failed deliveries". Nothing can fire a webhook the auto-enqueuer dropped. Inert on every path → error, not a NON-rule.

The generalization, now in the module doc and pinned by a test: a runtime comment records what its author believed, and beliefs go stale when a sibling feature is deleted. A blessing needs corroboration that the blessed mode is still reachable, or it's a comment about a mode that no longer exists.

The test asserts the finding carries both citations, so nobody demotes this rule on the strength of the comment alone.

triggers: [] is flagged identically to an omitted triggers — unlike an action's locations: [] (the documented headless spelling), an empty array here carries no "I meant it" signal, because a webhook's off switch is isActive. The repo's one real webhook confirms the premise: showcase_task_changed ships inactive via isActive: false with a full trigger list, and produces no finding.

Phase 3's scope was mostly already closed

Verifying each Tier-B candidate before writing it — the discipline that caught four false prescriptions in #4001 — found most of the audit's list already handled or misfiled:

candidate disposition
A2 action without locations already shippedvalidate-action-locations.ts, which already exempts the documented locations: []
approval empty/unresolvable approvers already shippedvalidate-approval-approvers.ts
select/multiselect without options shipped in Phase 1
write-side referential integrity not an authoring-lint item — runtime gap, no metadata omission to detect
unique: true no-op on memory driver not an authoring-lint item — driver gap
composite/repeater sub-field constraints not an authoring-lint item — runtime gap
nav targets: page/report/url/component/action genuine gap, different module — key present but dangling ⇒ reference resolvability (ADR-0072), not completeness
dataset with zero measures unverified — not shipped. No runtime consumer in this repo
schedule trigger with invalid cron unverified — not shipped. normalizeSchedule takes any non-empty string; scheduler behaviour not traced
webhook without triggers this PR

Two candidates are deliberately left unshipped rather than written on the audit's stated confidence, and one is left to the module that owns it. The audit's own lesson stands: it produces candidates, not confirmed bugs.

Verification

  • 24 predicate tests (19 → 24), 8 walk tests (7 → 8)
  • The one real webhook in the repo produces no finding
  • API surface: additive only. i18n ratchet: unchanged
  • Full suite: 132/132

Generated by Claude Code

…hase 3)

A webhook that declares no `triggers` never fires on any path. It still
materializes into `sys_webhook` and still renders in Setup looking armed, so the
omission is invisible on every surface an author can see — the ADR-0078 shape.

## The first source argued AGAINST this rule

The runtime skip site reads:

    if (triggers.size === 0) {
        // No dispatchable triggers (or a manual-only webhook with none) —
        // skip auto-enqueue.
        return null;

That parenthetical blesses the empty case as a deliberate mode. It is
structurally identical to the `multiselect`-without-options NON-rule, where
`record-validator.ts`'s `// free-form (tags without options)` is precisely why
we do NOT flag it. On this evidence alone the candidate stays unenforced.

The mode it names does not exist. `webhook.zod.ts`'s #3196 note records that the
`api` (manual/programmatic fire) trigger was REMOVED because "no manual fire
path exists — the only webhook HTTP surface re-queues already-failed
deliveries". Nothing can fire a webhook the auto-enqueuer dropped. Inert on
every path, so: error, not a NON-rule.

The generalization is now in the module doc and pinned by a test: a runtime
comment records what its author BELIEVED, and beliefs go stale when a sibling
feature is deleted. A blessing needs corroboration that the blessed mode is
still reachable, or it is a comment about a mode that no longer exists. The test
asserts the finding carries both citations so nobody demotes the rule on the
comment alone.

`triggers: []` is flagged identically to an omitted `triggers`: unlike an
action's `locations: []` (the documented headless spelling), an empty array here
carries no "I meant it" signal, because a webhook's off switch is `isActive`.
The repo's one real webhook confirms the premise — `showcase_task_changed`
ships inactive via `isActive: false` with a full trigger list, and is clean.

## Phase 3's scope was mostly already closed

Verifying each Tier-B candidate before writing it — the discipline that caught
four false prescriptions in #4001 — found most of the audit's list already
handled or misfiled:

  A2 action without `locations`     already shipped (validate-action-locations,
                                    which already exempts `locations: []`)
  approval empty approvers          already shipped (validate-approval-approvers)
  select/multiselect options        shipped in Phase 1
  write-side FK integrity           not an authoring-lint item (runtime gap)
  `unique` on memory driver         not an authoring-lint item (driver gap)
  composite sub-field constraints   not an authoring-lint item (runtime gap)
  nav page/report/url/component     genuine gap, but reference resolvability
                                    (ADR-0072), not completeness — wrong module
  dataset with zero measures        UNVERIFIED, not shipped: no runtime consumer
                                    in this repo
  schedule trigger invalid cron     UNVERIFIED, not shipped: normalizeSchedule
                                    takes any non-empty string, scheduler
                                    behaviour not traced
  webhook without triggers          this change

Two candidates are left unshipped rather than written on the audit's stated
confidence, and one is left to the module that owns it.

Verification: 24 predicate tests (19 -> 24) and 8 walk tests (7 -> 8); the one
real webhook in the repo produces no finding; full suite 132/132.

Tracked in #4544.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WnqGjQFQMqd5k81LYV8SCY
@vercel

vercel Bot commented Aug 2, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 2, 2026 3:23am

Request Review

@os-zhuang
os-zhuang marked this pull request as ready for review August 2, 2026 03:23
@os-zhuang
os-zhuang enabled auto-merge August 2, 2026 03:23
@github-actions github-actions Bot added the size/m label Aug 2, 2026
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/lint, @objectstack/spec.

107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/agents.mdx (via @objectstack/spec)
  • content/docs/ai/skills-reference.mdx (via @objectstack/spec)
  • content/docs/ai/skills.mdx (via @objectstack/spec)
  • content/docs/api/client-sdk.mdx (via @objectstack/spec)
  • content/docs/api/environment-routing.mdx (via @objectstack/spec)
  • content/docs/api/error-catalog.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-client.mdx (via @objectstack/spec)
  • content/docs/api/error-handling-server.mdx (via @objectstack/spec)
  • content/docs/api/index.mdx (via @objectstack/spec)
  • content/docs/automation/approvals.mdx (via @objectstack/spec)
  • content/docs/automation/connectors.mdx (via @objectstack/spec)
  • content/docs/automation/flows.mdx (via @objectstack/spec)
  • content/docs/automation/hook-bodies.mdx (via @objectstack/lint, packages/spec)
  • content/docs/automation/hooks.mdx (via @objectstack/spec)
  • content/docs/automation/index.mdx (via @objectstack/spec)
  • content/docs/automation/webhooks.mdx (via @objectstack/spec)
  • content/docs/automation/workflows.mdx (via @objectstack/spec)
  • content/docs/concepts/architecture.mdx (via @objectstack/spec)
  • content/docs/concepts/design-principles.mdx (via packages/spec)
  • content/docs/concepts/index.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-driven.mdx (via @objectstack/spec)
  • content/docs/concepts/metadata-lifecycle.mdx (via packages/spec)
  • content/docs/concepts/north-star.mdx (via packages/spec)
  • content/docs/data-modeling/analytics.mdx (via @objectstack/spec)
  • content/docs/data-modeling/drivers.mdx (via @objectstack/spec)
  • content/docs/data-modeling/external-datasources.mdx (via @objectstack/spec)
  • content/docs/data-modeling/field-types.mdx (via @objectstack/spec)
  • content/docs/data-modeling/fields.mdx (via @objectstack/spec)
  • content/docs/data-modeling/formulas.mdx (via @objectstack/spec)
  • content/docs/data-modeling/index.mdx (via @objectstack/spec)
  • content/docs/data-modeling/objects.mdx (via @objectstack/spec)
  • content/docs/data-modeling/queries.mdx (via @objectstack/spec)
  • content/docs/data-modeling/schema-design.mdx (via @objectstack/spec)
  • content/docs/data-modeling/seed-data.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation-rules.mdx (via @objectstack/spec)
  • content/docs/data-modeling/validation.mdx (via @objectstack/spec)
  • content/docs/deployment/cli.mdx (via @objectstack/spec)
  • content/docs/deployment/troubleshooting.mdx (via @objectstack/spec)
  • content/docs/deployment/validating-metadata.mdx (via @objectstack/spec)
  • content/docs/getting-started/build-with-claude-code.mdx (via @objectstack/spec)
  • content/docs/getting-started/common-patterns.mdx (via @objectstack/spec)
  • content/docs/getting-started/examples.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-reference.mdx (via @objectstack/spec)
  • content/docs/getting-started/quick-start.mdx (via @objectstack/spec)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/spec)
  • content/docs/kernel/cluster.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/auth-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/cache-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/data-engine.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/index.mdx (via @objectstack/spec)
  • content/docs/kernel/contracts/metadata-service.mdx (via packages/spec)
  • content/docs/kernel/contracts/storage-service.mdx (via packages/spec)
  • content/docs/kernel/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/email-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/index.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/queue-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sharing-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/sms-service.mdx (via packages/spec)
  • content/docs/kernel/runtime-services/storage-service.mdx (via packages/spec)
  • content/docs/kernel/services-checklist.mdx (via @objectstack/spec)
  • content/docs/kernel/services.mdx (via @objectstack/spec)
  • content/docs/permissions/authorization.mdx (via @objectstack/lint, @objectstack/spec)
  • content/docs/permissions/permission-sets.mdx (via @objectstack/spec)
  • content/docs/permissions/permissions-matrix.mdx (via @objectstack/spec)
  • content/docs/permissions/positions.mdx (via @objectstack/spec)
  • content/docs/permissions/rls.mdx (via @objectstack/spec)
  • content/docs/permissions/sharing-rules.mdx (via @objectstack/spec)
  • content/docs/plugins/adding-a-metadata-type.mdx (via @objectstack/spec)
  • content/docs/plugins/development.mdx (via @objectstack/spec)
  • content/docs/plugins/index.mdx (via @objectstack/spec)
  • content/docs/plugins/packages.mdx (via @objectstack/spec)
  • content/docs/protocol/backward-compatibility.mdx (via @objectstack/spec)
  • content/docs/protocol/diagram.mdx (via packages/spec)
  • content/docs/protocol/kernel/config-resolution.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/i18n-standard.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/index.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/lifecycle.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/spec)
  • content/docs/protocol/kernel/runtime-capabilities.mdx (via @objectstack/spec)
  • content/docs/protocol/knowledge.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/query-syntax.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/schema.mdx (via @objectstack/spec)
  • content/docs/protocol/objectql/security.mdx (via packages/spec)
  • content/docs/protocol/objectql/state-machine.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/actions.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/concept.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/index.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/layout-dsl.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/record-alert.mdx (via @objectstack/spec)
  • content/docs/protocol/objectui/widget-contract.mdx (via @objectstack/spec)
  • content/docs/releases/implementation-status.mdx (via @objectstack/spec)
  • content/docs/releases/index.mdx (via @objectstack/spec)
  • content/docs/releases/v12.mdx (via @objectstack/spec)
  • content/docs/releases/v13.mdx (via @objectstack/spec)
  • content/docs/releases/v16.mdx (via @objectstack/spec)
  • content/docs/releases/v17.mdx (via @objectstack/spec)
  • content/docs/releases/v9.mdx (via @objectstack/spec)
  • content/docs/ui/actions.mdx (via @objectstack/spec)
  • content/docs/ui/create-vs-edit-form.mdx (via @objectstack/spec)
  • content/docs/ui/dashboards.mdx (via @objectstack/spec)
  • content/docs/ui/forms.mdx (via @objectstack/spec)
  • content/docs/ui/index.mdx (via @objectstack/spec)
  • content/docs/ui/public-data-collection.mdx (via @objectstack/spec)
  • content/docs/ui/setup-app.mdx (via @objectstack/spec)
  • content/docs/ui/translations.mdx (via @objectstack/spec)
  • content/docs/ui/views.mdx (via @objectstack/spec)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 2, 2026
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 2, 2026
Merged via the queue into main with commit e6e9379 Aug 2, 2026
21 checks passed
@os-zhuang
os-zhuang deleted the claude/adr-0078-phase3 branch August 2, 2026 03:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants