Skip to content

spec: ServiceSlotContracts gains the webhooks and trigger-api entries, typing the two slots whose optional members (#22754, #22772) are declared but not registered in the slot map #22848

Description

@objectstack-fleet

Filing gate ③: a foreseen follow-up, split out of #22774 by triage's pm:retriage answer. Filed by the triage seat (seat post #6015, session_01AavokzJ5DndAwitDXvKy4U). ⛔ Not a claim.

Lane: packages/spec/src/contracts/core-service-contracts.ts (ServiceSlotContracts, near :128) is domain:spec.

Why a card of its own

Scope

  • Add webhooks: IWebhookService and 'trigger-api': ITriggerApiService to ServiceSlotContracts, so kernel.getService('webhooks') and kernel.getService('trigger-api') are typed.
  • Regenerate whatever the slot map feeds: the contract references and the API docs.
  • Clause-②: yes (widening a published type). Changeset FROM → TO, with an at-tier contract review.

Done when

  • Both keys type their slot. A typecheck pin shows getService('trigger-api')?.handleInboundHook and getService('webhooks')?.handleRedeliver resolve to the declared members.
  • CONTROL: the other slots are unchanged.

Order

None required. If #22774 (which registers trigger-api) lands first, this types an already-registered slot. If this lands first, it types a declared one. Either order leaves main green.

domain:spec · p3 · area:workflow.

Activity

  1. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 8 (#22848: ServiceSlotContracts gains webhooks: IWebhookService and 'trigger-api': ITriggerApiService, so both slots are typed through kernel.getService) · 2026-10-12T01:11Z
    Session: session_01KNKBCRDJCu5tGy3TEbvtrF
    Account: zhuangjianguo (the seat's linked user as GET /user answers it; the card's assignee from this act)
    Branch: claude/issue-22848-slot-contracts
    Worktree: objectstack-issue-22848
    Domain: domain:spec
    Seat: domain:spec#3 (seat post #18883)
    File surface (at origin/main eb20c33c45 or later; stop on breach and explain in the report):

    This act moves the card pm:queue → pm:dispatched and assigns zhuangjianguo.


    Generated by Claude Code

  2. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report

    {
    "issue": 22848,
    "status": "done",
    "branch": "claude/issue-22848-slot-contracts",
    "pr": "#22922",
    "session": "session_01KNKBCRDJCu5tGy3TEbvtrF",
    "premise_still_valid": true,
    "summary": "ServiceSlotContracts gains webhooks: IWebhookService and 'trigger-api': ITriggerApiService, each with TSDoc naming the contract and the registering package. The binding is evidenced: plugin-webhooks' WebhookOutboxPlugin (webhook-outbox-plugin.ts:411) and trigger-api's ApiTriggerPlugin (plugin.ts:169) both register an occupant declared as the contract. core-service-contracts.test.ts pins getService('webhooks')?.handleRedeliver and getService('trigger-api')?.handleInboundHook to the declared members, through a lookup with the runtime's keyed signature. CONTROL: three already-mapped slots answer as before, and the non-core key set is now pinned exactly (it was a subset check). One premise is narrower than the card says: ObjectKernel.getService (core kernel.ts:816) is generic over a caller-chosen T and never reads the map. What the map types is the runtime's keyed lookups (DomainHandlerDeps.resolveService and getRequestKernelService), so the pin restates that signature. Type-only: no runtime file, and no generated artifact moved. The PR is a draft, assignee zhuangjianguo. The at-tier contract review is owed before enqueue, and the PM commissions it.",
    "tests": [
    "All runs at HEAD 63710c5 unless named. origin/main eab7236 was merged through os-regen-merge.sh, and the merge touched no packages/spec file. git diff fbe0be0 63710c5 -- packages/spec is empty.",
    "spec build (lock): VERDICT command-exit 0. spec typecheck (lock): exit 0; check:test-typecheck OK, 52 files / 246 errors / 135 signatures held, ledger unchanged.",
    "spec test, at fbe0be0 (lock): 648 files passed; 19395 tests passed, 1 todo. The bare -- dropped --maxWorkers=2, and the whole package still ran. Single-file verbose run (lock): core-service-contracts.test.ts, 11 of 11 pass, both new tests among them. spec test:repo, at fbe0be0 (lock): 55 files, 971 passed.",
    "Consumer typechecks against the rebuilt spec (lock), all exit 0: runtime (tsc; test layer 27/190/68 held), plugin-webhooks (tsc, scripts tsc, test layer), trigger-api (tsc), http-conformance (tsc; test layer 3/27/10 held). Their 36-task closure built first: turbo build, concurrency 2 (lock), 36 of 36 successful.",
    "check:generated: all 14 artifacts current, at fbe0be0 and again at HEAD. check:api-surface: public API surface unchanged.",
    "Ablation, spec pin (WRAP, auto-restore; committed first at 9fe6996). Deleted 'webhooks: IWebhookService;'. Mutation landed: anchor 1 to 0, blob 13ce41bf32ee to 80becbb0dba7. Ran pnpm --filter @objectstack/spec check:test-typecheck: RED, exit 1, six new signatures, all in core-service-contracts.test.ts: TS2345 x3 ('webhooks' not assignable to keyof ServiceSlotContracts), TS2339 (handleRedeliver), TS2344 x2 (slot-type pin and key-set pin). Restored: blob == HEAD 13ce41bf32ee, git diff HEAD empty. No dist or build leg: the test imports the source by a relative path.",
    "Reverse verification, runtime reads the rebuilt .d.ts (WRAP, auto-restore). Inserted 'const probe = await deps.resolveService(context, WEBHOOKS_SLOT, …); void probe?.notAMember;' in runtime/src/domains/webhooks.ts. Mutation landed: anchor 1 to 0, blob c920bff6c2bd to 6aaa357c3a81. Ran pnpm --filter @objectstack/runtime exec tsc --noEmit: RED with exactly one error, webhooks.ts(115,17) TS2339 Property 'notAMember' does not exist on type 'IWebhookService'. A stale map would fall to the string overload, which answers any, and the line would compile. Restored: blob == HEAD c920bff6c2bd, git diff HEAD empty. The first attempt was a no-op: its replacement contained the anchor again, so ablation-replace refused it (anchor 1 to 1) before running tsc, and its exit 1 is not a reading."
    ],
    "gates": [
    "dispatch-gates --commands --repo objectstack-ai/objectstack, no paths, at 63710c5: 85 commands. 83 ran, all exit 0. 2 NOT MEASURED by the dispatch's instruction: check:dual-build-cjs-loads (no whole-workspace build) and check:type-check-debt (its script is the --re-measure form). --ran reconciliation with recorded exit codes: 85 accounted, 83 run, 2 NOT-MEASURED claimed, 0 UNRUN, exit 0.",
    "Exit 0 with partial coverage: spec check:entry-nameability measured 19 public entries (533 probes, 0 new). It prints its standing NOT MEASURED note for api-assembled and qa, which have no callable export.",
    "Artifact-roster block, 50 rows: 47 ran without PR context, 46 at exit 0 (14 of the 50 rows are checker-health self-tests only). check:published-readme-exports: PREREQUISITE NOT MET, exit 3, 19 unbuilt packages: NOT MEASURED. check:console-injection: exit 0 but skipped, no packages/console/dist: NOT MEASURED.",
    "The 3 PR-context roster guards (node scripts/check-closing-target-claim.mjs, check-partof-closing-keyword.mjs, check-single-claim-paths.mjs, wired to PR 22922): NOT MEASURED, because the environment's safety check refused the step (quoted under deviations). Not re-issued, per the stop rule. Before the PR existed, check-partof-closing-keyword ran with PR_BODY set to the body file: exit 0, no contradiction. check-closing-keyword-parity --body: the only closing declaration is the intended one, naming this card.",
    "Not run locally, CI-owned: the 11 declared-wide families, the type-check lanes, pnpm lint. Local CI convergence is not awaited; checks on PR 22922 are in_progress."
    ],
    "deviations": [
    "Safety-check refusal, quoted: 'Permission for this command was denied by a built-in Claude Code safety check, not by the user. The check stops removals that can delete far more than intended … What was flagged: This command passes a shell -c script that runs rm, and Claude Code could not check the script for dangerous removals. Approve only if you have read the script.' The refused command ran the three PR-context guards through bash -c on a variable. No removal was intended. Not worked around.",
    "spec test was invoked as test -- --maxWorkers=2. The bare -- dropped the worker cap. The whole package ran as intended, so this is a resource deviation, not a coverage one.",
    "The runtime reverse probe's first attempt was a no-op, refused by ablation-replace (anchor not consumed). It was redone with a consuming replacement, and that is the cited run.",
    "Verify lock: three queue-timeouts (exit 99, never acquired), each resumed under the kept slot issue-22848. Each heavy suite ran once under the lock.",
    "git push x4: the empty-branch probe, two commits, and the origin/main merge commit."
    ],
    "files_changed": [
    "packages/spec/src/contracts/core-service-contracts.ts (+24: two imports, two entries with TSDoc)",
    "packages/spec/src/contracts/core-service-contracts.test.ts (+41/-5: the probe pin with CONTROL, and the exact key-set pin)",
    ".changeset/22848-service-slot-contracts-webhooks-trigger-api.md (@objectstack/spec minor; Clause-②: yes (widening); FROM → TO)"
    ],
    "zone2_readings": [
    "1. ServiceSlotContracts extends CoreServiceContracts (core-service-contracts.ts:128), and quoted kebab keys exist ('http-server'). ServiceSlotContract of K answers the entry, or unknown. The map keys three runtime seams: DomainHandlerDeps.resolveService (domain-handler-registry.ts:182, with an untyped string overload at :183), getRequestKernelService (:299/:300), and the execution-context resolver (security/resolve-execution-context.ts:57). ObjectKernel.getService (core kernel.ts:816) does not read the map. The existing slot pins are in core-service-contracts.test.ts, and the new pin went there, with expectTypeOf: new local type aliases would have added TS6196 debt to the exact, shrink-only test-typecheck ledger.",
    "2. webhook-service.ts:96 handleRedeliver? and trigger-api-service.ts:148 handleInboundHook? are confirmed. The contracts barrel exports both (contracts/index.ts:36 trigger-api-service, :66 webhook-service).",
    "3. Registrations: plugin-webhooks registers 'webhooks' (webhook-outbox-plugin.ts:411, const service: IWebhookService). trigger-api's ApiTriggerPlugin registers 'trigger-api' at kernel:ready (plugin.ts:169, const service: ITriggerApiService). Both satisfy their interface. registerService takes name: string and does not read the map, so no registration is re-checked. Neither slot is a CoreServiceName member (core-services.zod.ts:20-57).",
    "4. Callers through the typed seam: runtime/src/domains/webhooks.ts:114 and automation-hooks.ts:120. Both still compile (runtime typecheck exit 0), and each now keeps a redundant cast to Pick of the contract's one member or undefined. Neither file is in this PR, so the casts are listed only. Three test files call kernel.getService with an explicit generic (webhook-redeliver-member.test.ts:189, hono-webhooks-redeliver.conformance.test.ts:174, inbound-hook-member.test.ts:169); the map does not affect them. objectui at pin 4997995022: zero hits for ServiceSlotContract or CoreServiceContract (git grep exit 1), with the control grep for @objectstack/spec hitting.",
    "5. check:generated regenerates nothing for this change: 14 of 14 current, and api-surface records that an export exists, not its members."
    ],
    "mcp_calls": "0",
    "api_writes": "3 — each a fleet-write relay repository_dispatch executed as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (draft, PR 22922; body read back byte-identical, 9739 bytes); (2) label-write --assign, POST /repos//issues/22922/assignees (zhuangjianguo, read back matching); (3) this os-dev-report comment, POST /repos//issues/22848/comments via post-stamped.mjs. git push x4 is not REST. Reads: single-card REST reads of issue 22848 and its comments, and of PR 22922.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: none (承接者:无) · noted, not filed · runtime/src/domains/webhooks.ts:114 and runtime/src/domains/automation-hooks.ts:120 keep a now-redundant cast (Pick of the contract's one member, or undefined) after the typed lookup. Both compile; it is redundancy, not a defect. Recorded in PR Acceptance notes.",
    "carrier: none (承接者:无) · noted, not filed · contracts/trigger-api-service.ts header still says the trigger-api registration 'is a separate change: until it lands no kernel fills the slot', but ApiTriggerPlugin registers it (triggers/trigger-api/src/plugin.ts:169). Doc drift, not a defect class, and outside this claim's file surface. Recorded in PR Acceptance notes."
    ],
    "cleanup": "Next, after this comment posts (the posting script lives in the worktree): rm -rf the worktree's node_modules, then git worktree remove /home/user/objectstack-issue-22848, without --force. No dev server or monitor was started, and every detached run was waited on to exit."
    }


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    ✅ ACCEPT: PR #22922 at 63710c5572 (Fixes #22848). The contract review at CONTRACT_REVIEW_TIER runs next

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-12T03:10Z · holder of claim 6115993229. Report: 6117025142. Thread-read: 6117025142.

    Read in the diff (git diff eab7236ff7 63710c5572, 3 files, +81 / −5):

    • ServiceSlotContracts gains webhooks: IWebhookService and 'trigger-api': ITriggerApiService. Each carries TSDoc naming the registering package (WebhookOutboxPlugin, ApiTriggerPlugin), and nothing else in the map moves.
    • The pin (core-service-contracts.test.ts:115) types both probes through the runtime's keyed lookup signature:
      • getService('webhooks')?.handleRedeliver and getService('trigger-api')?.handleInboundHook resolve to the declared members;
      • the CONTROL holds http.server, security and data as before;
      • the non-core key set is now pinned exactly, where before it was a subset check.
    • The changeset: @objectstack/spec minor, Clause-②: yes (widening), FROM → TO, and "type information only". No runtime file moves, and no generated artifact moves.

    One premise, narrowed by measurement, accepted: the card says kernel.getService(...), but ObjectKernel.getService (core/kernel.ts:816) is generic over a caller's T and never reads the map. What the map types is the runtime's keyed lookups (DomainHandlerDeps.resolveService, getRequestKernelService), and the pin restates that signature. "Done when" is met at the seam that actually reads the map.

    Evidence accepted:

    • spec build, typecheck (check:test-typecheck held) and the full suite, 19,395 tests, plus test:repo 55 / 971;
    • the consumer typechecks against the rebuilt spec: runtime, plugin-webhooks, trigger-api and http-conformance;
    • check:generated 14 / 14 and check:api-surface unchanged;
    • ablation: deleting the webhooks entry turns check:test-typecheck red with six new signatures, and restoring it is blob-equal;
    • reverse probe: a non-member read through resolveService in runtime/src/domains/webhooks.ts is a TS2339 against the rebuilt .d.ts, and restoring it is blob-equal;
    • gates: 85 derived, 83 run, all exit 0, and 2 NOT MEASURED as dispatched. The three PR-context guards are NOT MEASURED (below), and CI carries them;
    • CI at 63710c5572: running at this act, with no failure so far.

    Deviation recorded, not worked around: the environment's safety check refused the dev's bash -c wrapper around the three PR-context guards ("a shell -c script that runs rm"). The dev stopped there, per the stop rule, and the seat does not re-issue that step on the dev's behalf.

    Acceptance notes (carrier: none):

    • the two now-redundant casts at runtime/src/domains/webhooks.ts:114 and automation-hooks.ts:120;
    • the stale header sentence in contracts/trigger-api-service.ts, which says no kernel fills the slot.

    Both are recorded in the PR's Acceptance notes. Neither is a defect.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 12, 2026

    @objectstack-fleet
    ContributorAuthor

    Release: #22848 back to pm:queue. PR #22922 has its contract review PASS and waits only on a green CI run

    domain:spec seat 3 (#18883) · zhuangjianguo · session session_01KNKBCRDJCu5tGy3TEbvtrF · 2026-10-12T05:06Z · holder of claim 6115993229, released by this act. Thread-read: 6117053946.

    The maintainer's words, in this session's chat:

    • 「当前任务处理完,合并后就下班」;
    • then, with the week's usage at 3%: 「后续如果有契约复审可以交给总监。」 and 「后续的改进也可以交接给下一pm」.

    This seat starts no new subagent, so the card is handed over at this point.

    State: PR #22922 (draft) at 63710c5572.

    Release: session_01KNKBCRDJCu5tGy3TEbvtrF · why: the maintainer's stand-down and hand-over words above (this session's chat) · to: pm:queue, unassigned. Resume-from: claude/issue-22848-slot-contracts@63710c5572. This act moves the card pm:dispatched → pm:queue and removes the assignee zhuangjianguo.


    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

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions