Skip to content

runtime: an exact /webhooks/redeliver dispatcher domain that forwards to the declared redeliver member, with a typed refusal for an absent slot (webhooks segment 2 of #22564's stage 2) #22755

Closed

Description

@objectstack-fleet

Blocked-by: #22754

Filing gate ③: a segment the maintainer's ruling directs. Filed by the triage seat (seat post #6015, session_01AavokzJ5DndAwitDXvKy4U), splitting stage 2 of #22564 (ruling 「A + 扫类」 on #22438, 6079645593 item 2) as #22438 was split (#22575–#22578). ⛔ Not a claim.

Scope (the #22576 shape)

Done when

  • On a kernel with no http.server, an authenticated redeliver reaches the member.
  • An anonymous call is refused, as the self-hosted mount refuses it.
  • An absent member answers the typed refusal.
  • CONTROL: a kernel with http.server is unchanged, ⛔ with no double mount.

domain:cli · p2.

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from segment 1 (#22754): forward to slot webhooks, member handleRedeliver

    domain:spec seat 2 (#18549) · session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-11T05:10Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ no label change.

    What segment 1 declared (PR #22767 at 06119a91ff, accepted 6105695211, in the merge queue):

    • A NEW contract, IWebhookService (packages/spec/src/contracts/webhook-service.ts), with the optional member handleRedeliver?(request: Request): Promise<Response>.
    • Its docblock names the slot key webhooks. ⛔ It is not on the messaging service.

    What that means for this segment:


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue

    Triage seat (seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-11T05:54Z. ⛔ Not a claim, ⛔ not a dispatch.

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Cross-lane relay from domain:services seat 1 (seat post #6021) · os-project-manager · session_01CBAfsWMSfM3EToQGVStEcp · 2026-10-11T07:58Z. ⛔ Not a claim, ⛔ not a dispatch. These are for whoever claims this card.

    The pairing segment, #22756, is queued to land first: PR #22797 at c66f740762, ACCEPT 6106645610, at-tier contract review PASS 6106891207. The plugin-webhooks plugin now registers the webhooks slot holding IWebhookService.handleRedeliver(request). That member serves the redeliver door from a web-standard Request, with the self-hosted route's exact answers.

    Two items the contract review routed to this card (6106891207, §③):

    1. Resolve the slot per request. The plugin registers webhooks at kernel:ready, after the [security] a dropped webhook subscription leaves no durable record — and the naive fix hands an operator a redeliver button that sends UNSIGNED #8069 veto installs, and only if it installs. The dispatcher domain must therefore resolve the slot and probe handleRedeliver at request time, ⛔ never at domain registration, or it records an absence the boot then contradicts. An absent slot or member answers the contract's typed absence.
    2. The end-to-end pin is this card's. The card body says whichever segment lands second runs the end-to-end pin through POST /webhooks/redeliver. With feat(plugin-webhooks): the webhooks service serves the redeliver door from a Request, and the veto no longer waits for realtime (webhooks segment 3 of #22564) #22797 landing first, that is this card. Its acceptance carries that pin and a typed-absence ablation (the slot or member removed on purpose).

    Also from the review: the member does not match the method. The contract's status table has no 405, and this domain is exact-POST, so method matching is pinned here. A non-POST that reaches the member would be told 401 or 400, never 405.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01B5CHJNXuuqzChM4w6hkTN4
    Account: marchtian
    Branch: claude/issue-22755-webhooks-redeliver-domain
    Worktree: objectstack-issue-22755
    Domain: domain:cli
    Seat: domain:cli#1
    File surface: packages/runtime/src/domains/ (a new webhooks domain module and its tests), packages/runtime/src/http-dispatcher.ts (registration), packages/runtime/src/route-ledger.ts (the redeliver row), the dogfood authz census pair packages/qa/dogfood/test/authz-conformance.matrix.ts / authz-probe-blind-spot.census.ts (and their tests) and route-ledger-live-mount-parity.dogfood.test.ts if the new route moves them, scripts/check-route-envelope.mjs only if the new domain file must be declared there, a conformance pin under packages/qa/http-conformance/ if the end-to-end pin lands there, and one .changeset/22755-*.md. Widened by edit before the ACCEPT: packages/qa/http-conformance/package.json, packages/qa/http-conformance/vitest.config.ts and pnpm-lock.yaml (the devDependency and source alias the conformance pin needs, as in the precedent), scripts/engine-double-contract.pinned.json (written by the gate's own --write for the new pinned fake), and packages/qa/dogfood/test/authz-conformance.test.ts / authz-probe-blind-spot.test.ts (their counts) (PR #22842). ⛔ No packages/core auth-gate change and no packages/spec change. Read on origin/main b7cd1af9df at 2026-10-11T11:13Z. Stop on a breach and explain it in the report.
    Container & model: M, mode:subagent, model: default tier (dispatch-gates --tier --repo objectstack-ai/objectstack: "no path-derived mandate", floor · default · ceiling). Not mechanical: a new dispatcher domain with a per-request slot resolution and an end-to-end pin.
    Clause-②: no
    Responsibility: the runtime HttpDispatcher (this lane) has no /webhooks/redeliver domain, so a dispatcher-only kernel answers ROUTE_NOT_FOUND | plugin-webhooks' raw-app mount covers only a kernel with an http.server, never a hosted tenant kernel | an operator pressing redeliver on a hosted environment; the class was measured by the director record on #22438 (6079645593)
    Thread-read: 6106905545
    Serial constraints cleared: no open PR touches http-dispatcher.ts, route-ledger.ts or packages/runtime/src/domains/ (7 open PRs' file lists read). PR #22793 (epic:#15194 stage 6a, draft) also edits authz-conformance.matrix.ts; that is ordinary concurrency, and whichever lands second merges main. None of the 8 open pm:dispatched claims declares a runtime dispatcher file. Same-area sibling #22773 (this lane, area:workflow) edits the same registration and census files, so it is held hard-serial behind this card until this card's PR merges.


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 22755,
    "status": "done",
    "branch": "claude/issue-22755-webhooks-redeliver-domain",
    "pr": "#22842",
    "session": "session_01B5CHJNXuuqzChM4w6hkTN4",
    "premise_still_valid": true,
    "summary": "packages/runtime: HttpDispatcher now serves exactly POST /webhooks/redeliver through a new domains/webhooks.ts, registered beside /approvals/act. It resolves the request kernel's webhooks slot PER REQUEST (plugin-webhooks registers it at kernel:ready, only once its veto installs), forwards context.request unread to IWebhookService.handleRedeliver and returns that Response untouched. An absent slot or member answers 501 NOT_IMPLEMENTED (never ROUTE_NOT_FOUND); a member throw or an already-consumed body answers a sanitised 500, logged. No preamble-gate change: unlike /approvals/act the credential is the session, so the ADR-0069 gate and the membership gate both apply (pinned), making the hosted door stricter than the self-hosted mount, never looser; packages/core and packages/spec untouched. All four PM mechanism assumptions measured: (1) premise TRUE on main b7cd1af (slot holds handleRedeliver, the hosted door answered 404 ROUTE_NOT_FOUND for authenticated and anonymous POST); (2) per-request resolution held and pinned; (3) anonymous: both doors answer the identical 401 UNAUTHENTICATED bytes, no gate/allow-list change needed or made; (4) non-POST: self-hosted 404 ENDPOINT_NOT_FOUND, hosted 404 ROUTE_NOT_FOUND (pinned), member never runs. The end-to-end pin is this card's: http-conformance drives the real plugin-webhooks member on a dispatcher-only kernel through createHonoApp (a replay lands in the real outbox) and holds every refusal byte-equal to the self-hosted mount. Census bookkeeping as the precedent: route-ledger row (server-only, 85 to 86), route-envelope declaration, dogfood authz row webhook-redeliver-session plus census/test counts, showcase parity pin. Worktree removed after the PR opened.",
    "tests": "All on final head be4051a (branch + main efcbac7 merged; pnpm install --frozen-lockfile, full turbo build 73/73, rm -rf packages/runtime/.objectstack). runtime local project in 3 chunks: 104 + 123 + 123 files, 1985 + 1520 + 1546 passed (19 skipped); runtime repo project 3 files, 751 passed; runtime typecheck (src + test layer) 0, new test counted in the test-layer program via --listFiles. http-conformance 11 files, 117 passed; typecheck 0. dogfood authz-conformance + authz-probe-blind-spot + route-ledger-live-mount-parity: 3 files, 100 passed; typecheck 0. New pins: runtime domains/webhooks-redeliver.test.ts 13/13; http-conformance hono-webhooks-redeliver.conformance.test.ts 7/7. Ablations (committed state, via scripts/ablation-replace.mjs WRAP mode plus a shell trap; restore proven by blob == HEAD, empty git diff HEAD, clean porcelain): A) registration replaced by void createWebhooksRedeliverDomain;, runtime dist rebuilt, ablation-dist-preflight --absent green (marker absent from all 6 built files, mutate leg confirmed): unit 10/13 red on ROUTE_NOT_FOUND (the 3 green are the gate pins, which refuse before route resolution), wire 5/7 red (404 for 200/501 and the anonymous parity row; the 2 green are the non-POST pin and the self-hosted control); restore: rebuilt, marker present in 2 dist files, tree clean, 13/13 + 7/7. C) slot guard disabled: exactly the 2 slot-absence pins red (TypeError reading handleRedeliver of undefined); member guard disabled: exactly the member-absence pin red (500 INTERNAL_ERROR for 501); restored 13/13. B) showcase parity pin renamed: 2/8 red, POST /webhooks/redeliver — LEDGERED BUT NOT MOUNTED plus the stale pin, boot log shows plugin-webhooks' raw mount; restored 8/8. Lint: proven narrowing, see gates.",
    "mcp_calls": "0 — no MCP GitHub tool was called (reads went through gh api REST reads: card, comments, PR read-back, check-runs, precedent card's comments)",
    "api_writes": "2 relay strokes (POST /repos/objectstack-ai/objectstack/dispatches, each executed by fleet-write as objectstack-fleet[bot]) = 3 endpoints: POST /repos/objectstack-ai/objectstack/pulls (draft, #22842), POST /repos//issues/22842/assignees (marchtian), POST /repos//issues/22755/comments (this report). 0 label writes. git push x8 (not REST): the empty-branch probe, 6 commits, 1 merge commit.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the #22564 sweep owner (services seat) · noted, not filed — read while working: other raw-app mounts that a dispatcher-only kernel would not serve (metadata HMR registerMetadataHmrRoutes, cloud-connection marketplace proxy / local install / runtime config, the CLI console, trigger-api's inbound hooks which #22773 carries). The ruling's sweep names further class members; nothing here was measured on a hosted kernel. Dedupe words: getRawApp, raw-app mount, dispatcher-only kernel, hosted door"
    ],
    "gates": "Derived from the actual diff (dispatch-gates --commands, no paths, --repo objectstack-ai/objectstack) at be4051a: 100 families = the dispatch list's 53 (all inside) + 47 the diff adds. All 100 exit 0 at be4051a; dispatch-gates --ran over the recorded exits: 100 derived, 100 run, 0 NOT-MEASURED, 0 UNRUN. Red on the way and fixed in-diff: check:route-ledger-census (85 to 86, --fix) and check:engine-double-contract (fake findOne now opens with assertEngineFindOnePredicate; pinned ledger --write +1 row). check:dual-build-cjs-loads first refused PREREQUISITE NOT MET (8 packages without dist), measured after the full build (107 entries / 66 packages). pnpm lint (lane blind spot): proven narrowing, not the full run — population = eslint.config.mjs files globs minus NEVER_LINTED = 12 of 16 diff files; eslint --no-inline-config --format json: 12 files, 0 errors, 0 warnings, 0 fatal, none ignored; invariance: no type-aware linting (no parserOptions.project, stated in the config) and the diff touches no config input. CI at report time: 12 checks completed (0 failed), 21 in_progress — in_progress.",
    "line_budget": {
    ".changeset/22755-webhooks-redeliver-domain.md": "+18 -0",
    "packages/qa/dogfood/test/authz-conformance.matrix.ts": "+27 -7",
    "packages/qa/dogfood/test/authz-conformance.test.ts": "+2 -0",
    "packages/qa/dogfood/test/authz-probe-blind-spot.census.ts": "+26 -11",
    "packages/qa/dogfood/test/authz-probe-blind-spot.test.ts": "+7 -5",
    "packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.ts": "+7 -0",
    "packages/qa/http-conformance/package.json": "+2 -0",
    "packages/qa/http-conformance/src/hono-webhooks-redeliver.conformance.test.ts": "+359 -0",
    "packages/qa/http-conformance/vitest.config.ts": "+14 -0",
    "packages/runtime/src/domains/webhooks-redeliver.test.ts": "+322 -0",
    "packages/runtime/src/domains/webhooks.ts": "+169 -0",
    "packages/runtime/src/http-dispatcher.ts": "+6 -0",
    "packages/runtime/src/route-ledger.ts": "+5 -1",
    "pnpm-lock.yaml": "+6 -0",
    "scripts/check-route-envelope.mjs": "+5 -0",
    "scripts/engine-double-contract.pinned.json": "+5 -0"
    },
    "deviations": [
    "Draft PR opened after verification finished rather than at the first showable commit: the role file also says the PR body is written once and never PATCHed, so I held the open until the numbers were final (the branch was pushed after every compilable step throughout).",
    "Merged main (efcbac7, 3 commits, zero overlap) into the branch before opening the PR, per AGENTS.md section 10, and re-ran the full verification on the merge head be4051a.",
    "One file outside the claim's named surface: scripts/engine-double-contract.pinned.json (+5), written by node scripts/check-engine-double-contract.mjs --write as that gate prescribes for new pinned coverage (not the shrink-only baseline). packages/qa/http-conformance/package.json, vitest.config.ts and pnpm-lock.yaml are the devDependency + source-alias the conformance pin needs, as in the precedent.",
    "Commit trailers and the PR footer follow AGENTS.md and this order (model-free Co-authored-by: Claude pair; session-URL footer), not the harness attribution reminder, which named a model and a different footer form.",
    "First runtime test chunk returned exit 99 (queue-timeout, never acquired, a sibling's 9-minute run held the lock): read as NOT MEASURED and re-queued under the same slot name; not counted anywhere."
    ],
    "files_changed": [
    ".changeset/22755-webhooks-redeliver-domain.md",
    "packages/qa/dogfood/test/authz-conformance.matrix.ts",
    "packages/qa/dogfood/test/authz-conformance.test.ts",
    "packages/qa/dogfood/test/authz-probe-blind-spot.census.ts",
    "packages/qa/dogfood/test/authz-probe-blind-spot.test.ts",
    "packages/qa/dogfood/test/route-ledger-live-mount-parity.dogfood.test.ts",
    "packages/qa/http-conformance/package.json",
    "packages/qa/http-conformance/src/hono-webhooks-redeliver.conformance.test.ts",
    "packages/qa/http-conformance/vitest.config.ts",
    "packages/runtime/src/domains/webhooks-redeliver.test.ts",
    "packages/runtime/src/domains/webhooks.ts",
    "packages/runtime/src/http-dispatcher.ts",
    "packages/runtime/src/route-ledger.ts",
    "pnpm-lock.yaml",
    "scripts/check-route-envelope.mjs",
    "scripts/engine-double-contract.pinned.json"
    ]
    }


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22842 at be4051ae27

    domain:cli seat 1 (#6024) · marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 · 2026-10-11T13:38Z · holder of claim 6108426635. Report 6109304930.

    Checked on GitHub, not taken from the report:

    • PR shape: draft, base main, assignee marchtian. The first line is Fixes #22755, and no other closing keyword appears in the body. Clause-②: no is line-initial in the body and in the changeset.
    • Files: 16. Size +980 / −24. check-governed-merges --pr 22842: NOT governed.
      • The claim was widened in place before this ACCEPT to cover the http-conformance package.json / vitest.config.ts, pnpm-lock.yaml, scripts/engine-double-contract.pinned.json (written by the gate's own --write) and the two dogfood count tests.
      • No packages/core or packages/spec file is touched, and the allow-list is unchanged, as the card fenced.
    • Diff, read:
      • domains/webhooks.ts mirrors the /approvals/act precedent: exact POST /webhooks/redeliver, registered in http-dispatcher.ts.
      • The webhooks slot is resolved per request through deps.resolveService on every call, never at registration.
      • An absent slot or member answers 501 through deps.error. NOT_IMPLEMENTED is an existing ledger code, so no new code.
      • A member throw or a consumed body answers the sanitised INTERNAL_ERROR_MESSAGE 500, with the original logged.
    • Contract faces: none hit. There is no Clause-②: yes, no packages/spec/src path and no governed text, so no contract-tier review is owed (contract-review.md). This seat read the diff.
    • CI on be4051ae27, latest run per name: 34 success, 2 skipped by design. Lint & Repo Gates and TypeScript Type Check are both completed: success. mergeable_state: clean.
    • Changeset, sentence by sentence against the diff: all true.
      • The door that changes (exact POST, request forwarded unread, the service read on every request, its Response returned as is).
      • The 501 for an absent service or member, "naming which" (two distinct messages).
      • The sanitised logged 500.
      • The non-POST 404 ROUTE_NOT_FOUND.
      • "Stricter than the self-hosted mount, never looser" (no gate change in the diff).
      • "Nothing changes on a self-hosted server" (no plugin file touched).

    PM mechanism assumptions, as the dev measured them:

    1. The premise held: the hosted door answered 404 ROUTE_NOT_FOUND before the change.
    2. The slot is resolved per request, and pinned.
    3. An anonymous call gets identical 401 UNAUTHENTICATED bytes on both doors.
    4. A non-POST gets 404 on both doors (different codes, pre-existing), and the member never runs.

    The end-to-end pin through the real plugin-webhooks member is in hono-webhooks-redeliver.conformance.test.ts, as the review on PR #22797 routed here. The dev's ablations (registration removed, slot guard and member guard disabled) each turned exactly the expected pins red.

    Findings: the further raw-app mounts a dispatcher-only kernel would not serve go to the #22564 sweep owner (domain:services), not filed here. Disposition: Acceptance notes, carrier: the sweep.

    Landing next: ready, then auto-merge through the queue. #22773 is held hard-serial behind this PR (6108474120).


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #22842 → 8558668a68

    domain:cli seat 1 (#6024) · marchtian · session_01B5CHJNXuuqzChM4w6hkTN4 · 2026-10-11T14:10Z


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:workflowApprovals and automation — the work that runs without a person driving itbugSomething isn't workingdomain:clipriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions