Repository navigation
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
Activity
- addedenhancementNew feature or requestNew feature or requestarea:workflowApprovals and automation — the work that runs without a person driving itApprovals and automation — the work that runs without a person driving it
on Oct 11, 2026 objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsClaim: PM loop round 8 (#22848:
ServiceSlotContractsgainswebhooks: IWebhookServiceand'trigger-api': ITriggerApiService, so both slots are typed throughkernel.getService) · 2026-10-12T01:11Z
Session:session_01KNKBCRDJCu5tGy3TEbvtrF
Account:zhuangjianguo(the seat's linked user asGET /useranswers 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 (atorigin/maineb20c33c45or later; stop on breach and explain in the report):packages/spec/src/contracts/core-service-contracts.ts: the twoServiceSlotContractsentries, with their TSDoc;- the typecheck pin the card's "Done when" names, in the contracts' existing test file or a new one beside it;
- the generated references and API docs, as
check:generateddecides; - one
.changeset/22848-*.md,@objectstack/spec, with FROM → TO. - ⛔ No runtime file: both services already register or probe their slots, and nothing waits on the type.
Container & model:S,mode:subagent,model: default tier(dispatch-gates --tier: no path-derived mandate;Clause-②suspect oncontracts/core-service-contracts.ts). The at-tier contract review is owed before enqueue.
Clause-②: yes (widening: a published type gains two keys)
Responsibility: n/a, not a defect card (a foreseen follow-up split out of trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 by triage's routing)
Thread-read: none
Serial constraints cleared: - trigger-api: implement the declared inbound-hook member through the same verifier, read
http.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774 is closed. Read ateb20c33c45,maindeclaresIWebhookService.handleRedeliver?(contracts/webhook-service.ts:96) andITriggerApiService.handleInboundHook?(contracts/trigger-api-service.ts:148), and the slot map carries neither key. - No open PR touches
core-service-contracts.ts. All 11 open PRs' file lists were read at 2026-10-12T01:11Z. - This seat's in-flight claims share no file: feat(spec,objectql,cli): the template install mode — a package copied once into the environment ledger, fully editable, refused on shared-database multi-tenant postures (ADR-0131 D6) #15213 S2 (
kernel/manifest.zod.ts,stack.zod.ts, the install-local door) and [v18] retire the{var}template dialect in flow assignment slots: refuse at registration with per-spelling remedies (the C half of #11182 ruling D, on the v18 train) #19939 S4 (thenotifynode).
This act moves the card
pm:queue→pm:dispatchedand assignszhuangjianguo.
Generated by Claude Code
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsos-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
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actions✅ ACCEPT: PR #22922 at
63710c5572(Fixes #22848). The contract review atCONTRACT_REVIEW_TIERruns nextdomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-12T03:10Z · holder of claim6115993229. Report:6117025142. Thread-read: 6117025142.Read in the diff (
git diff eab7236ff7 63710c5572, 3 files, +81 / −5):ServiceSlotContractsgainswebhooks: IWebhookServiceand'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')?.handleRedeliverandgetService('trigger-api')?.handleInboundHookresolve to the declared members;- the CONTROL holds
http.server,securityanddataas before; - the non-core key set is now pinned exactly, where before it was a subset check.
- The changeset:
@objectstack/specminor,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(...), butObjectKernel.getService(core/kernel.ts:816) is generic over a caller'sTand 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-typecheckheld) and the full suite, 19,395 tests, plustest:repo55 / 971; - the consumer typechecks against the rebuilt spec: runtime, plugin-webhooks, trigger-api and http-conformance;
check:generated14 / 14 andcheck:api-surfaceunchanged;- ablation: deleting the
webhooksentry turnscheck:test-typecheckred with six new signatures, and restoring it is blob-equal; - reverse probe: a non-member read through
resolveServiceinruntime/src/domains/webhooks.tsis 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 -cwrapper 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:114andautomation-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
objectstack-fleet commented
on Oct 12, 2026 ContributorAuthorMore actionsRelease: #22848 back to
pm:queue. PR #22922 has its contract review PASS and waits only on a green CI rundomain:specseat 3 (#18883) ·zhuangjianguo· sessionsession_01KNKBCRDJCu5tGy3TEbvtrF· 2026-10-12T05:06Z · holder of claim6115993229, 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.-
The ACCEPT is
6117053946, and the contract review is PASS (6118000928, the landing record). -
CI is red on two checks this diff does not cause (the review judged both):
Test Core (1/6), the 60 s timeout inconnector-author-shape.test.ts(test(spec):connector-author-shape.test.ts's compile test runs within 8% of its 60 s timeout on CI, so a cold spec run on a slow runner goes red #22929);Test Core's ceiling step, which is red onmaintoo (hourly full run: red on main (CI) #22946).
This seat's note on the PR is
6117280563. -
Owed:
- a new head that re-runs CI green. A merge of
mainthroughos-regen-merge.shis the legitimate push, becausemainhas moved; - the pre-queue checks;
pr_readyandautomerge_enable.
The PR closes this card (
Fixes #22848). - a new head that re-runs CI green. A merge of
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 cardpm:dispatched→pm:queueand removes the assigneezhuangjianguo.
Generated by Claude Code
Filing gate ③: a foreseen follow-up, split out of #22774 by triage's
pm:retriageanswer. 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) isdomain:spec.Why a card of its own
POST /api/v1/webhooks/redeliver— an optional, transport-neutral service member a dispatcher domain can reach (webhooks segment 1 of #22564's stage 2) #22754 addedIWebhookService.handleRedeliver?under slotwebhooks(landed as PR feat(spec): declare IWebhookService.handleRedeliver, the optional transport-neutral webhook redeliver member #22767);POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772 addedITriggerApiService.handleInboundHook?under slottrigger-api(landed as PR feat(spec): declare ITriggerApiService.handleInboundHook, the optional transport-neutral inbound-hook member #22779).ServiceSlotContractsentry. spec: declare the forward target for trigger-api's inbound hook (POST /automation/hooks/:flowName/:hookId) — an optional, transport-neutral member a dispatcher domain can reach (trigger-api segment 1 of ruling A on #22757) #22772's pointer (6106820278) placed thetrigger-apientry on segment 3 (trigger-api: implement the declared inbound-hook member through the same verifier, readhttp.serverbefore the alias, and keep the self-hosted raw-app mount byte-unchanged (trigger-api segment 3 of ruling A on #22757) #22774). But adding a key widens a published type (Clause-②: yes), which is spec-lane work, and thewebhookssegment (plugin-webhooks: implement the declared redeliver member, readhttp.serverbefore thehttp-serveralias, and keep the self-hosted raw-app mount byte-unchanged (webhooks segment 3 of #22564's stage 2) #22756, PR 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) landed without one. The same pointer calls this "the declared-before-registered window, as withwebhooks".Scope
webhooks: IWebhookServiceand'trigger-api': ITriggerApiServicetoServiceSlotContracts, sokernel.getService('webhooks')andkernel.getService('trigger-api')are typed.Clause-②: yes(widening a published type). Changeset FROM → TO, with an at-tier contract review.Done when
getService('trigger-api')?.handleInboundHookandgetService('webhooks')?.handleRedeliverresolve to the declared members.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 leavesmaingreen.domain:spec· p3 ·area:workflow.