Repository navigation
feat(spec): optional caller on IAutomationService.toggleFlow - #26545
Conversation
toggleFlow(name, enabled, caller?: Pick<AutomationContext, 'userId'>) lets a door hand down who switched a packaged flow, so the activation ledger row and its audit row can name that user. Optional, so every current implementer and caller compiles unchanged. Type-level pin and a patch changeset for @objectstack/spec. Claude-Session: https://claude.ai/code/session_01WGufzgdxgk35nWqLB6EMzg Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 26 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 6 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 146 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 8b3bc378720b16e736d3f74e4f4653afc91b55dc && git checkout 8b3bc378720b16e736d3f74e4f4653afc91b55dc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 416d9ddd3d54a0a957941a765b60ff06076984ad f3ce50a7cc2bb55ce12807ae1181253966ac26e2 && git checkout -B drift-repro 416d9ddd3d54a0a957941a765b60ff06076984ad && git merge --no-ff f3ce50a7cc2bb55ce12807ae1181253966ac26e2
node scripts/docs-audit/affected-docs.mjs --json 416d9ddd3d54a0a957941a765b60ff06076984ad
|
Contract reviewServed-tier: Inputs read: card #26506 body and both comments (claim ① Derived judgments
Head check-runs: read at 2026-10-27T07:29:07Z, 46 runs: 24 success, 8 skipped (opt-in and path-filtered jobs), 14 in progress, 0 failure. Green already: all six ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
席位核对 · ACCEPT ·
|
Requested by Yibo Wang · project thread
Fixes #26506
Clause-②: yes (widening)
Metadata-protocol: none
维护者速读
IAutomationService.toggleFlow增加可选的第三个参数caller,只携带userId,用来告诉服务是谁开关了这个打包流程。packages/spec/src/contracts/automation-service.ts(toggleFlow签名与 TSDoc),packages/spec/src/contracts/automation-toggle-flow-caller.pin.test.ts。pnpm --filter @objectstack/spec exec vitest run src/contracts/automation-toggle-flow-caller.pin.test.ts,预期 2 个用例通过。What changes
IAutomationService.toggleFlow?(name, enabled)gains an optional third parameter,caller, typed asPickoverAutomationContextnarrowed to the single key'userId'. A door that serves a person passes that person's id, so thesys_metadata_activationrow the switch writes, and itssys_audit_logrow, can name the admin instead of a null actor. The method's TSDoc now says what the channel carries, that it is attribution only (it grants nothing, and an implementation neither refuses nor widens the switch on it), and that an absent caller, or one with nouserId, means an unattributed write.The parameter is optional, so every current implementer (
AutomationEngine.toggleFlowin@objectstack/service-automation) and every caller (the runtime'sPOST /api/v1/automation/:name/toggledoor and its test doubles) compiles unchanged. This is the contract half only: the door passing the caller and the engine writing with it are the server half, #26474.Why this shape
The order was to follow
execute(flowName, context?: AutomationContext)in the same contract, reusingAutomationContextor a narrow subset of it. I took the subset, one key:userIdis the key the write records. The engine stampscreated_by/updated_byfrom the context's user, and the audit writer readsuser_idfrom it.AutomationContext.userIdis already the contract's spelling for "the acting user", soexecuteandtoggleFlowname the person the same way.toggleFlowis no authority gate: ADR-0126 §5 gates the switch at the door that receives the request, and the ledger write runs elevated. A channel carrying grants would invite a second gate inside the service whose rules could drift from the door's.AutomationContext. Most of its keys (record,params,runAs,flowName,flowRunId,callerParamKeys) describe a run and mean nothing for a switch. Taking the whole type would leave each implementer to decide which to ignore. With the narrow type, a caller literal carryingpositionsorrecorddoes not compile (pinned).ExecutionContext. Several store contracts take the caller asExecutionContext | undefined(IAIConversationService,ISharingService). That would hand the service the caller's whole principal, grants included, which is the second-gate risk above. The order also pointed at theAutomationContextprecedent in this contract.Widening later is additive: another key can join the
Pickif a reader ever needs it.Tests
packages/spec/src/contracts/automation-toggle-flow-caller.pin.test.ts, compiled bycheck:test-typecheckundertsconfig.test.jsonand run by vitest:Pickorundefined; the arity is 2 or 3;nameandenabledkeep their types and places;positions, or withrecord, is a compile error (@ts-expect-error, which itself fails if the line ever compiles);caller?.userIdwithout a cast, and reads it as absent when not passed; a two-parameter implementation runs when a caller is passed.Reverse verification (one-off, not kept): the signature reverted to the two-parameter form via
scripts/ablation-replace.mjs(anchor 1 to 0, blob8fc1e1ccto201b1e11), thentsc --noEmit -p tsconfig.test.json. The pin file turned red with 9 errors (TS2344 on both identity asserts, TS2554 on each three-argument call, TS2493 and TS7006 where the third parameter vanished). Restored: blob equals HEAD andgit diff HEADis empty.Acceptance notes
toHaveBeenCalledWith(FLOW, false)(automation-activation-posture-gate.test.ts,http-dispatcher.test.ts) will need the caller argument when the door starts passing it. That is the server half's edit, noted here so it is not a surprise.actorlabel on this channel;AutomationContextdeclares none. No reader needs one today.Verification
All readings at head
f3ce50a7, in theobjectstack-issue-26506worktree based onorigin/main3fcafcd3.pnpm --filter @objectstack/spec buildunder the verify lock: VERDICT command-exit 0.pnpm --filter @objectstack/spec check:generated: exit 0, all 14 generated artifacts up to date, andgit statusclean afterwards, so no generated artifact moved (api-surface/contracts.jsonrecordsIAutomationServiceby name only).pnpm --filter @objectstack/spec typecheck(tsc, scripts typecheck,check:test-typecheck) and vitest on the new pin,automation-service.test.tsandautomation-context-caller-param-keys.pin.test.ts: VERDICT command-exit 0, Test Files 3 passed, Tests 22 passed.pnpm exec eslint --no-inline-configon both changed.tsfiles: exit 0.Derived gates: 79 derived, 75 run with exit 0, 4 NOT MEASURED
node scripts/pm/dispatch-gates.mjs --commandsderived 79 families from the three changed paths; all were run serially and reconciled with--ran(79 accounted, 0 unrun). Four exited 3, PREREQUISITE NOT MET, because this container has a shallow clone and no full build. They read nothing this diff touches, and CI runs them:node scripts/check-plugin-teardown-shape.mjs --self-test(needs an unshallowedmain)pnpm --filter @objectstack/lint run check:doc-formula-expressions(needs built packages)pnpm check:dual-build-cjs-loads(needs every package'sdist)pnpm check:lean-entry-closure(needs@objectstack/objectqlbuilt)Not run locally: the full dogfood battery (the title has no
!and the declaration has no(narrowing)arm),test:repoand the repo-wide lint, which are CI's.Human Minimum
packages/specholds no runtime logic.pnpm lintpasses.main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01WGufzgdxgk35nWqLB6EMzg
Generated by Claude Code