refactor(workflow-executor): keep HTTP and wire formats out of the automated-inbox business code - #1951
Conversation
…ugh the real adapters Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…n record-id helper Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…een runs and automated inboxes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ilures, not the poller Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… not in the domain errors Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…own records can be excluded Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…nd map the wire in the adapters Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…le, lease, inbox poll and pure rules Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…s and the lease trust window Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…review Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
9 new issues
|
Scra3
left a comment
There was a problem hiding this comment.
Spec (PRD-1441): conforms. Every concern the ticket lists now lives in an adapter or a pure domain module, and the end-to-end sweep test pinned before the refactor still passes unchanged.
… and classification tables Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
matthv
left a comment
There was a problem hiding this comment.
Spec (PRD-1441): conforms. Every path the production wiring reaches keeps its HTTP calls, sync bodies and logs.
| timezone: toProjectTimezone(config.timezone), | ||
| liana: config.liana, | ||
| segment: config.segment, | ||
| user: toStepUser(config.serviceAccountProfile), |
There was a problem hiding this comment.
Claude Opus 5.5 (claude-opus-5-5): This diff meets the criteria for a security review, so please run /security-review locally before merging.
Triggers
forest-server-automation-port.ts:64builds the service-account identity the agent is called as, from the orchestrator's config.agent-client-segment-reader.ts:129signs the per-call agent token for that identity.
There was a problem hiding this comment.
Ran /security-review on this PR's diff (4a4d450…32e424e8): no finding. The identity is the same orchestrator-supplied profile through the unchanged toStepUser, now called once in toAutomatedInbox after the zod parse instead of in the reader; mintStepToken and its claims (snake_case aliases, scope: 'step-execution', 5 min TTL) are untouched, nothing new from the response enters the token, and no log or sync body carries the token, the secrets or the user object.
…ion cross-product tables Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
matthv
left a comment
There was a problem hiding this comment.
Approved after /verify-fixes run 1951-20261002135925-matthv-verify: 2 findings of 1951-20261001211330-Scra3 and 1951-20261002133556-matthv closed or accepted, none reopened.
2928d2c
into
feature/prd-1183-runtime-automation-poller
…tomated-inbox business code (#1951)

fixes PRD-1441
Targets the integration branch
feature/prd-1183-runtime-automation-poller(#1906). Refactor only: no behaviour change, no contract change.Why
The rest of the executor keeps transport out of the business code:
AgentPortandWorkflowPortspeak domain types, and adapters map the wire (run-to-available-step-mapper.ts). The automated-inbox code did not:automation-poller.tsimportedAgentHttpErrorand classified HTTP failures itself (401/403, 502-504, 408/429, network error codes, status 0);AutomationPort/SegmentReaderPortwere typed withServer*wire types;not_inoperator, the 150 excluded-ids query-string bound, the composite-key rule, and split packed ids itself;errors.tsimported agent-client to parse agent error bodies;What moves where
forbidden·unreachable·overloaded·failed)adapters/agent-errors.ts→SegmentReadError(extendsAgentPortError, same message)not_indeclared)SegmentReaderPort.exclusionUnavailableReason, same order of checks, capabilities read in exactly the same casesStepUser, timezone fallback)ForestServerAutomationPort(toAutomatedInbox); timezone fallback shared with the run mapper (adapters/project-timezone.ts)src/record-id.ts(wasadapters/record-id-serializer.ts)src/types/automation.ts; the two ports import only theseautomation/read-failure.ts(domain)automation/reconciliation.ts(pure, no port, no logger)automation/lease-keeper.ts(no I/O)automation/inbox-poll.tsautomation/automation-poller.tsgit grepfinds no@forestadmin/agent-client,@forestadmin/forestadmin-clientorserver-typesimport left insrc/automation/,src/types/automation.ts, the two automation ports,errors.tsorrecord-id.ts.Unchanged
HTTP calls (paths, query strings, bodies), sync bodies, log messages, levels and fields, retries and timings, the orchestrator contract. The first commit adds
test/integration/automation-sweep.test.ts, which drives the real poller through the realForestServerAutomationPort(mockedServerUtils) and the realAgentClientSegmentReader(nock). It pins sync bodies, every agent request and the poll / read-failure logs for: 403, refused connection,not_inrefused (400) then padding, 429 (no padding), unknown liana, composite key with 0 and N known records, 151 known records, null and invalid timezone, reconciliation and the JWT claims, an assignments route answering 404. It was written against the pre-refactor code and only its import line changed since.Two things worth knowing:
AgentPortError(public export) now takes the agent message as an optional third argument instead of reading it off the cause. Every internal construction goes throughagentPortError()/segmentReadError()and produces the same message; a host that buildsnew AgentPortError(op, cause)itself no longer gets the| agent error:suffix.server-types.tskeeps its automation types: it is the contract mirror PRD-1177 keeps in sync with the server. The sync body is now typed againstServerAutomatedInboxSyncRequest.Out of scope, pre-existing:
ports/activity-log-port.tsimports two string unions from forestadmin-client.Tests
Suite: 2058 → 2412 tests (2405 passed, 7 skipped). Every commit is green on its own. Tests moved with the logic; nothing was weakened:
test/adapters/agent-errors.test.ts; the poller keeps one wiring case per behaviour.errors.test.ts) →agent-errors.test.tsthrough the factory;errors.test.tsnow pinsAgentPortErroritself.not_indeclared, no capabilities call when nothing is known) →test/adapters/agent-client-segment-reader.test.ts; the poller keeps oneit.eachover the four reasons andcapabilities-unreadable.test/adapters/forest-server-automation-port.test.ts.toReadFailure,mayBeOperatorRefusal) →test/automation/read-failure.test.ts.test/automation/reconciliation.test.ts(assignment state × run state × run id cross product, paging, chunking) andtest/automation/lease-keeper.test.ts(30 s boundaries). Four sabotages are caught only by these:auto-canceled, the doing + live-run branch, and both 30 s boundaries.Every move was checked by sabotage (one change at a time, restored byte-for-byte): dropping 502 /
ECONNABORTED/ 429, 150 → 149, dropping a liana, skipping the 0-known shortcut,ininstead ofnot_in, mapper droppinguser/liana/ keeping the raw timezone,newCandidatesignoring found ids, the page cap, the lease boundaries; each turned at least one test red.Two Opus reviews ran before the first push (behaviour preservation, separation and tests). Their fixes are in the last commit: the capabilities catch only takes a
SegmentReadErroragain (a malformed capabilities answer fails the candidate read as before), explicit mapping without a cast, sync body typed against the wire contract, policy moved toread-failure.ts,LeaseKeepermethods named after what they return, duplicated tables trimmed.🤖 Generated with Claude Code
Note
Move automated-inbox poller and adapters off server HTTP/wire types onto shared domain types
automationmodule withAutomationPoller(15s lease heartbeats, up to 5 concurrent inbox polls, draining on stop),LeaseKeeper(30s trust expiry),InboxPoll, and reconciliation/candidate helpers in reconciliation.tsAutomatedInbox,InboxAssignment,SegmentDescriptor,SegmentReadError,InboxSyncReport) in automation.ts; ports and adapters like forest-server-automation-port.ts now translate wire payloads into these domain typeslistFieldOperatorson the segment-reader port with anexclusionUnavailableReasonpreflight that rejects composite keys, unknown lianas, fields without a not-in operator, and more than 150 known recordsSegmentReadErrorconstruction into agent-errors.ts, classifying failures as forbidden, unreachable, overloaded, or failed with optional HTTP statusAgentPortErrorin errors.ts no longer extracts agent detail from 5xx response bodies itself — callers must pass the optional agent message; the exportedagentErrorDetailhelper is removed, andrecord-id-serializermoved to record-id.tsChanges since #1951 opened
Macroscope summarized eb6c0f3.