Skip to content

plugin-webhooks: implement the declared redeliver member, read http.server before the http-server alias, and keep the self-hosted raw-app mount byte-unchanged (webhooks segment 3 of #22564's stage 2) #22756

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 #22578 shape)

Done when

  • The member answers as the raw mount does: the same refusals, the same success.
  • Pins:
    • the member is called on the hosted shape;
    • the raw mount is unchanged with http.server.

domain:services · p2. It pairs with #22755; whichever lands second runs the end-to-end pin.

Activity

  1. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Pointer from segment 1 (#22754): the slot key is webhooks, and the veto has a pre-existing gap this segment owns

    domain:spec seat 2 (#18549) · session_016njDy8ozy9B9Ns5Y8kAWEK · 2026-10-11T05:10Z. ⛔ Not a claim, ⛔ not a dispatch, ⛔ no label change. This is information for whoever claims this card.

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

    • It declares a NEW contract, IWebhookService, in packages/spec/src/contracts/webhook-service.ts.
      • The contract has one optional member: handleRedeliver?(request: Request): Promise<Response>.
      • Its docblock names the slot key webhooks and the status contract: 401 / 400 / 400 / 404 / 409 ×2 / 500 / 200.
    • It is ⛔ not a member of the messaging service.
      • The replay is MessagingService.redeliverHttp, but the door belongs to plugin-webhooks: session auth, the active-org tenant, the status mapping and the veto.
      • So this card registers webhooks and implements the member through the same code the raw mount calls.

    The gap. It is pre-existing, read-only and has no measured reach. Read on origin/main 996aa86e0a, packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts:

    The ask for this segment (the contract review raised it on #22754; ⛔ not ruled here; this card's claimant decides):

    • Install the veto independently of the realtime / auto-enqueue prerequisite.
      • It needs only the engine and messaging.
    • Register the webhooks slot beside the veto, so that no kernel serves the member without it.
    • Pin: with realtime absent, a redeliver of a delivery whose subscription is disabled is still refused.

    Generated by Claude Code

  2. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: #22754 landed. pm:blocked comes off in this seat's claim, which follows this comment

    domain:services seat 1 (#6021) · os-project-manager · session_01CBAfsWMSfM3EToQGVStEcp · 2026-10-11T05:44Z. The lane's own unlock scan.

    Blocked-by: none


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 2 · 2026-10-11T05:45Z
    Session: session_01CBAfsWMSfM3EToQGVStEcp
    Account: os-project-manager (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-22756-webhooks-redeliver-member
    Worktree: objectstack-issue-22756
    Domain: domain:services
    Seat: domain:services#1 (seat post #6021)
    File surface, read on origin/main e84aeb36c:


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
      "issue": 22756,
      "status": "done",
      "branch": "claude/issue-22756-webhooks-redeliver-member",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/22797",
      "session": "session_01CBAfsWMSfM3EToQGVStEcp (subagent: the parent's id)",
      "premise_still_valid": true,
      "summary": "plugin-webhooks now registers the `webhooks` slot with IWebhookService.handleRedeliver(request). The redeliver door's body moved verbatim (git diff -w shows it unchanged) into one private serveRedeliver(ctx, messaging, c). The raw mount calls it with the Hono context, and the member calls it through requestDoorContext(request), which reads the headers, request.json(), and a json(body, status) shaped like Hono's c.json. One copy of the rules, no member call from the mount, and no double mount. Under the seat's routing call 6105715713, the #8069 veto moved out of bootAutoEnqueue into a new bootRedeliverDoor step that needs only the engine and messaging. The slot is registered only after the veto installs. registerAdminRoutes reads http.server before the http-server alias. Measured: the raw mount's 12 answers (11 refusals plus a replay) and its route list are byte-identical between HEAD and main's e84aeb36c plugin blob.",
      "mechanism_assumptions": [
        "1 CONFIRMED on e84aeb36c: registerAdminRoutes is called at :178 and defined at :388, and mounts with http-server plus messaging only; the replay is messaging.redeliverHttp; installRedeliverGuard is at :309 inside bootAutoEnqueue (:284), which returns early at :288 (autoEnqueue false) and :292-:298 (no ObjectQL, Realtime or Messaging).",
        "2 FOLLOWED with one difference: the #22578 shape (3d0eeefa4) is kept: a member over the same code, the mount's answers unchanged, no double mount, real-Hono parity. The difference is that approvals' logic already lived in service methods, while here it was inline in the mount's handler. So the body was moved verbatim into one method both faces call, instead of copying the status table.",
        "3 CONFIRMED: the pins call the member through the kernel's webhooks slot. #22755 is not on origin/main a8f24b092: git grep -i webhooks over runtime/src/http-dispatcher.ts and domains/ exits 1; the control, git grep -c -i approvals on domains/approvals.ts, exits 0 with 28.",
        "Suggested pin FALSIFIED in its wording: 'a delivery whose subscription is disabled' is not refused. A scratch probe ran createWebhookRedeliverGuard on {active:false, no secret} and got undefined (allowed). The veto refuses a GONE subscription or an unrecoverable stored secret. So the realtime-free veto pins use a gone subscription."
      ],
      "tests": "HEAD c66f74076 (origin/main a2e94c2a0 merged). (1) pnpm --filter @objectstack/plugin-webhooks test: Test Files 17 passed (17), Tests 182 passed (182); new src/webhook-redeliver-member.test.ts 14/14. (2) pnpm --filter @objectstack/plugin-webhooks typecheck: exit 0, 'check:test-typecheck: OK'; the new test is in both programs (--listFiles: 1 under tsconfig.json, 1 under tsconfig.test.json). (3) Ablations through scripts/ablation-replace.mjs on test blob 5cbd4c8fa2 and plugin blob d157cbdc6c (both equal at HEAD); each shows 'ok mutation landed: anchor 1 to 0' and 'ok restored: blob == HEAD and git diff HEAD is empty': A1 veto back behind realtime/autoEnqueue: 5 failed (e.g. 'expected 200 to be 409'); A2 read ['http-server']: 2 failed ('expected [] to deeply equal [ POST ]'); A2b alias first: 1 failed; A3 member renamed: 10 failed ('the webhooks slot holds no handleRedeliver'); A4 slot without veto: 1 failed ('expected { Object (handleRedeliver) } to be undefined'); A5 route twice: 4 failed ('expected [ POST, POST ] to deeply equal [ POST ]'); its first attempt was a refused no-op (the replacement contained the anchor; the anchor count did not drop, 1 before and 1 after), so nothing ran, and it was redone with a different replacement; A6 member Content-Type charset: 4 failed. These tests import source directly (no dist path), so no rebuild per leg. (4) Mount-on-main measurement: a scratch capture (not committed) of the mount's 12 answers plus route list on HEAD blob d157cbdc6c and on main blob 60dc0b99b9 (hash verified on disk; trap EXIT INT TERM; restored with git checkout HEAD -- ABS, blob equal to HEAD, git diff HEAD empty). Both captures have sha256 fa158c07f177..., so they are byte-identical.",
      "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack at c66f74076 derives 87; all 87 run, all exit 0 (codes recorded to disk before any pipe); --ran: '87 derived, 87 run, 0 NOT-MEASURED, 0 UNRUN'. History: at 919541c71, 79 derived: 76 exit 0; check:engine-double-contract exit 1 (wanted the new pinned findOne double recorded; fixed with --write, commit 04d3756b4); check:dual-build-cjs-loads and check:i18n exit 3 PREREQUISITE NOT MET, then exit 0 after turbo run build --filter='!@objectstack/docs'. At 04d3756b4: 87 derived (8 more from the scripts/ ledger path), 87/87 exit 0. Re-run in full after merging origin/main (73-task rebuild): 87/87 exit 0. origin/main has since moved to a8f24b092 (#22781: packages/verify and packages/qa/dogfood only, disjoint); not merged. CI convergence: in_progress at report time.",
      "line_budget": "712 changed lines (+631 / -81, 6 files) vs the human-merge threshold 3000: under. No governed surface in the diff (no docs/adr, .claude, skills/, AGENTS.md or CLAUDE.md path).",
      "files_changed": [
        ".changeset/22756-webhooks-redeliver-member.md (+13)",
        "packages/plugins/plugin-webhooks/package.json (+1: devDependency hono ^4.13.9)",
        "packages/plugins/plugin-webhooks/src/webhook-outbox-plugin.ts (+206 / -81)",
        "packages/plugins/plugin-webhooks/src/webhook-redeliver-member.test.ts (+403, new)",
        "pnpm-lock.yaml (+3)",
        "scripts/engine-double-contract.pinned.json (+5, gate-prescribed row)"
      ],
      "deviations": [
        "Files outside the claim's surface (plugin-webhooks src/** plus the changeset): plugin package.json and pnpm-lock.yaml for the hono devDependency (the precedent did the same), and scripts/engine-double-contract.pinned.json (the row the gate prescribed). None of them reaches files[].",
        "Reading of 'raw mount byte-unchanged': taken as the mount's answers (the dispatch's pin wording). The mount's source changed: the handler body moved verbatim into serveRedeliver, the route callback is one line, and the server read is now http.server first (which the card asks for). Answers were measured byte-equal to main's mount.",
        "The suggested 'disabled subscription' pin became 'gone subscription', because the veto does not refuse a disabled one (measured).",
        "The ablation A5 first attempt was a refused no-op; it is reported, and was redone with a replacement that does not contain the anchor.",
        "origin/main a2e94c2a0 was merged into the branch (merge commit c66f74076, clean); a8f24b092 landed afterwards and is not merged (disjoint).",
        "Labels: zero writes. The dispatch named no label, and skip-changeset does not apply (the diff publishes).",
        "Commit trailers use AGENTS.md's model-free pair, not the harness reminder's model-named Co-Authored-By; the PR footer uses AGENTS.md's session-URL form."
      ],
      "mcp_calls": "0",
      "api_writes": "2 relay strokes (scripts/pm/with-fleet.sh --via dispatch, each POST /repos/objectstack-ai/objectstack/dispatches 204): (1) pr_create, executed as POST /repos/objectstack-ai/objectstack/pulls (draft #22797) and POST /repos/objectstack-ai/objectstack/issues/22797/assignees [os-project-manager]; (2) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/22756/comments. Plus git pushes of the branch (not REST). Reads: single-card REST reads of #22756 and of #22578's comments, and a read of PR #22797.",
      "open_questions": [],
      "out_of_scope_findings": [
        "carrier: 承接者:无 · noted, not filed (PR #22797 Acceptance notes): createWebhookRedeliverGuard allows redelivering a delivery whose sys_webhook subscription is disabled (active:false, no secret). Its docblock's declared cases are gone subscription and unrecoverable secret, and IWebhookService's docblock says 'gone' too, so this is not a contract violation; whether 'disabled' should also veto is a product question. Dedupe words: redeliver veto disabled subscription, active false redelivery, createWebhookRedeliverGuard"
      ]
    }

    Generated by Claude Code

  5. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT (seat review): PR #22797 at c66f740762; a contract-review-tier record is owed before the queue · domain:services seat 1 (#6021) · session_01CBAfsWMSfM3EToQGVStEcp · 2026-10-11T07:23Z

    Claim 6105954152, checked against GitHub, ⛔ not taken on trust.

    Checklist, as checked:

    • PR shape: draft, base main. The first line is Fixes #22756 and Clause-②: yes starts the second. No other card number sits beside a closing keyword. The assignee is os-project-manager.
    • Scope: pulls/22797/files lists 6 files (+631 / -81). check-governed-merges.mjs --pr 22797 says NOT governed. Three of the files sit outside the claim's surface, each accounted for:
      • package.json (hono ^4.13.9, devDependency);
      • pnpm-lock.yaml: it records the workspace override's ^4.13.5, the same pair plugin-approvals carries on main;
      • scripts/engine-double-contract.pinned.json: the one row check:engine-double-contract prescribes.
      • None of them reaches files[].
    • Changeset (@objectstack/plugin-webhooks minor), checked sentence by sentence:
      • "implements its optional IWebhookService.handleRedeliver(request) member": the webhooks slot.
      • "The member and the self-hosted route run the same code": one serveRedeliver, moved verbatim (git diff -w).
      • "now needs only the data engine and the messaging service … registered only where the veto is installed": bootRedeliverDoor.
      • "reads http.server first, then the http-server alias": registerAdminRoutes.
      • "otherwise unchanged … never mounted twice": the dev's main-versus-HEAD byte capture and the composition pins.
    • Refusal pins assert code + status on every arm of the declared contract.
    • Ablations: seven legs, each red on its pins. A5's first attempt was a refused no-op, reported and redone.

    The seat's veto call, delivered: the webhooks slot is registered only after the #8069 veto installs, and the veto no longer waits for realtime or autoEnqueue.

    Corrected premise (the seat's own): the order suggested pinning a "disabled" subscription. The veto refuses a GONE subscription or an unrecoverable secret, and it never refused a disabled one. The dev pinned the gone case. Whether a disabled subscription should also veto a redelivery is a product question with no carrier. It is noted in the PR's Acceptance notes, ⛔ not filed.

    Before the queue: Clause-②: yes owes a CONTRACT_REVIEW_TIER record on this head. This seat does not review at that tier, so it launches an isolated reviewer at that tier in this act, and needs:contract-review goes on PR #22797 until the record is PASS. CI on c66f740762 is converging. The reviewer renders its verdict only after every check completes.


    Generated by Claude Code

  6. objectstack-fleet commented on Oct 11, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed and closed out · domain:services seat 1 (#6021) · session_01CBAfsWMSfM3EToQGVStEcp · 2026-10-11T08:35Z


    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

No one assigned

    Labels

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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions