@openrig/cli 0.4.4 → 0.4.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/daemon/assets/plugins/openrig-core/skills/mission-slice-sop/SKILL.md +6 -8
- package/daemon/assets/plugins/openrig-core/skills/openrig-cmux/SKILL.md +86 -0
- package/daemon/assets/plugins/openrig-core/skills/openrig-herdr/SKILL.md +158 -0
- package/daemon/assets/plugins/openrig-core/skills/openrig-user/SKILL.md +744 -96
- package/daemon/dist/adapters/codex-runtime-adapter.d.ts.map +1 -1
- package/daemon/dist/adapters/codex-runtime-adapter.js +12 -8
- package/daemon/dist/adapters/codex-runtime-adapter.js.map +1 -1
- package/daemon/dist/adapters/pi-resume.d.ts +32 -0
- package/daemon/dist/adapters/pi-resume.d.ts.map +1 -0
- package/daemon/dist/adapters/pi-resume.js +125 -0
- package/daemon/dist/adapters/pi-resume.js.map +1 -0
- package/daemon/dist/adapters/pi-runner-protocol.d.ts +88 -0
- package/daemon/dist/adapters/pi-runner-protocol.d.ts.map +1 -0
- package/daemon/dist/adapters/pi-runner-protocol.js +153 -0
- package/daemon/dist/adapters/pi-runner-protocol.js.map +1 -0
- package/daemon/dist/adapters/pi-runner.d.ts +99 -0
- package/daemon/dist/adapters/pi-runner.d.ts.map +1 -0
- package/daemon/dist/adapters/pi-runner.js +519 -0
- package/daemon/dist/adapters/pi-runner.js.map +1 -0
- package/daemon/dist/adapters/pi-runtime-adapter.d.ts +61 -0
- package/daemon/dist/adapters/pi-runtime-adapter.d.ts.map +1 -0
- package/daemon/dist/adapters/pi-runtime-adapter.js +356 -0
- package/daemon/dist/adapters/pi-runtime-adapter.js.map +1 -0
- package/daemon/dist/adapters/tmux.d.ts +22 -1
- package/daemon/dist/adapters/tmux.d.ts.map +1 -1
- package/daemon/dist/adapters/tmux.js +40 -1
- package/daemon/dist/adapters/tmux.js.map +1 -1
- package/daemon/dist/build-info.js +3 -3
- package/daemon/dist/builtins/workflow-specs/branched-remediation.yaml +31 -0
- package/daemon/dist/builtins/workflow-specs/factory-rsi.yaml +152 -0
- package/daemon/dist/builtins/workflow-specs/gated-release.yaml +26 -0
- package/daemon/dist/builtins/workflow-specs/linear-build.yaml +21 -0
- package/daemon/dist/db/migrations/049_workflow_instance_version.d.ts +20 -0
- package/daemon/dist/db/migrations/049_workflow_instance_version.d.ts.map +1 -0
- package/daemon/dist/db/migrations/049_workflow_instance_version.js +24 -0
- package/daemon/dist/db/migrations/049_workflow_instance_version.js.map +1 -0
- package/daemon/dist/db/migrations/050_workflow_spec_json.d.ts +22 -0
- package/daemon/dist/db/migrations/050_workflow_spec_json.d.ts.map +1 -0
- package/daemon/dist/db/migrations/050_workflow_spec_json.js +26 -0
- package/daemon/dist/db/migrations/050_workflow_spec_json.js.map +1 -0
- package/daemon/dist/db/migrations/051_workflow_resume.d.ts +22 -0
- package/daemon/dist/db/migrations/051_workflow_resume.d.ts.map +1 -0
- package/daemon/dist/db/migrations/051_workflow_resume.js +27 -0
- package/daemon/dist/db/migrations/051_workflow_resume.js.map +1 -0
- package/daemon/dist/db/migrations/052_workflow_instance_bound_rig.d.ts +26 -0
- package/daemon/dist/db/migrations/052_workflow_instance_bound_rig.d.ts.map +1 -0
- package/daemon/dist/db/migrations/052_workflow_instance_bound_rig.js +30 -0
- package/daemon/dist/db/migrations/052_workflow_instance_bound_rig.js.map +1 -0
- package/daemon/dist/db/migrations/053_sessions_node_id_index.d.ts +48 -0
- package/daemon/dist/db/migrations/053_sessions_node_id_index.d.ts.map +1 -0
- package/daemon/dist/db/migrations/053_sessions_node_id_index.js +53 -0
- package/daemon/dist/db/migrations/053_sessions_node_id_index.js.map +1 -0
- package/daemon/dist/db/migrations/054_queue_transitions_archive.d.ts +34 -0
- package/daemon/dist/db/migrations/054_queue_transitions_archive.d.ts.map +1 -0
- package/daemon/dist/db/migrations/054_queue_transitions_archive.js +49 -0
- package/daemon/dist/db/migrations/054_queue_transitions_archive.js.map +1 -0
- package/daemon/dist/domain/claim-service.d.ts +11 -0
- package/daemon/dist/domain/claim-service.d.ts.map +1 -1
- package/daemon/dist/domain/claim-service.js +3 -1
- package/daemon/dist/domain/claim-service.js.map +1 -1
- package/daemon/dist/domain/discovery-types.d.ts +1 -1
- package/daemon/dist/domain/discovery-types.d.ts.map +1 -1
- package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts +1 -0
- package/daemon/dist/domain/hosts/hosts-registry-reader.d.ts.map +1 -1
- package/daemon/dist/domain/hosts/hosts-registry-reader.js +19 -0
- package/daemon/dist/domain/hosts/hosts-registry-reader.js.map +1 -1
- package/daemon/dist/domain/hosts/hosts-registry-writer.d.ts +11 -0
- package/daemon/dist/domain/hosts/hosts-registry-writer.d.ts.map +1 -0
- package/daemon/dist/domain/hosts/hosts-registry-writer.js +54 -0
- package/daemon/dist/domain/hosts/hosts-registry-writer.js.map +1 -0
- package/daemon/dist/domain/hosts/read-through.d.ts +28 -0
- package/daemon/dist/domain/hosts/read-through.d.ts.map +1 -0
- package/daemon/dist/domain/hosts/read-through.js +139 -0
- package/daemon/dist/domain/hosts/read-through.js.map +1 -0
- package/daemon/dist/domain/hosts/remote-daemon-http.d.ts +21 -0
- package/daemon/dist/domain/hosts/remote-daemon-http.d.ts.map +1 -1
- package/daemon/dist/domain/hosts/remote-daemon-http.js +49 -0
- package/daemon/dist/domain/hosts/remote-daemon-http.js.map +1 -1
- package/daemon/dist/domain/node-inventory.d.ts +18 -0
- package/daemon/dist/domain/node-inventory.d.ts.map +1 -1
- package/daemon/dist/domain/node-inventory.js +206 -95
- package/daemon/dist/domain/node-inventory.js.map +1 -1
- package/daemon/dist/domain/node-launcher.d.ts +11 -0
- package/daemon/dist/domain/node-launcher.d.ts.map +1 -1
- package/daemon/dist/domain/node-launcher.js +10 -0
- package/daemon/dist/domain/node-launcher.js.map +1 -1
- package/daemon/dist/domain/policies/workflow-keepalive.d.ts +7 -0
- package/daemon/dist/domain/policies/workflow-keepalive.d.ts.map +1 -1
- package/daemon/dist/domain/policies/workflow-keepalive.js +93 -13
- package/daemon/dist/domain/policies/workflow-keepalive.js.map +1 -1
- package/daemon/dist/domain/ps-projection.d.ts.map +1 -1
- package/daemon/dist/domain/ps-projection.js +37 -26
- package/daemon/dist/domain/ps-projection.js.map +1 -1
- package/daemon/dist/domain/queue-repository.d.ts +105 -0
- package/daemon/dist/domain/queue-repository.d.ts.map +1 -1
- package/daemon/dist/domain/queue-repository.js +193 -4
- package/daemon/dist/domain/queue-repository.js.map +1 -1
- package/daemon/dist/domain/queue-retention.d.ts +130 -0
- package/daemon/dist/domain/queue-retention.d.ts.map +1 -0
- package/daemon/dist/domain/queue-retention.js +224 -0
- package/daemon/dist/domain/queue-retention.js.map +1 -0
- package/daemon/dist/domain/restore-orchestrator.d.ts +5 -0
- package/daemon/dist/domain/restore-orchestrator.d.ts.map +1 -1
- package/daemon/dist/domain/restore-orchestrator.js +29 -2
- package/daemon/dist/domain/restore-orchestrator.js.map +1 -1
- package/daemon/dist/domain/resume-token-capture.d.ts +13 -0
- package/daemon/dist/domain/resume-token-capture.d.ts.map +1 -1
- package/daemon/dist/domain/resume-token-capture.js +14 -1
- package/daemon/dist/domain/resume-token-capture.js.map +1 -1
- package/daemon/dist/domain/resume-token-validation.d.ts +1 -1
- package/daemon/dist/domain/resume-token-validation.d.ts.map +1 -1
- package/daemon/dist/domain/resume-token-validation.js +58 -10
- package/daemon/dist/domain/resume-token-validation.js.map +1 -1
- package/daemon/dist/domain/review/compose.d.ts +50 -1
- package/daemon/dist/domain/review/compose.d.ts.map +1 -1
- package/daemon/dist/domain/review/compose.js +92 -3
- package/daemon/dist/domain/review/compose.js.map +1 -1
- package/daemon/dist/domain/review/fleet-compose.d.ts +52 -0
- package/daemon/dist/domain/review/fleet-compose.d.ts.map +1 -0
- package/daemon/dist/domain/review/fleet-compose.js +294 -0
- package/daemon/dist/domain/review/fleet-compose.js.map +1 -0
- package/daemon/dist/domain/review/gather.d.ts +16 -1
- package/daemon/dist/domain/review/gather.d.ts.map +1 -1
- package/daemon/dist/domain/review/gather.js +177 -41
- package/daemon/dist/domain/review/gather.js.map +1 -1
- package/daemon/dist/domain/review/types.d.ts +104 -1
- package/daemon/dist/domain/review/types.d.ts.map +1 -1
- package/daemon/dist/domain/rig-expansion-service.d.ts.map +1 -1
- package/daemon/dist/domain/rig-expansion-service.js +3 -0
- package/daemon/dist/domain/rig-expansion-service.js.map +1 -1
- package/daemon/dist/domain/rig-repository.d.ts +7 -6
- package/daemon/dist/domain/rig-repository.d.ts.map +1 -1
- package/daemon/dist/domain/rig-repository.js +33 -11
- package/daemon/dist/domain/rig-repository.js.map +1 -1
- package/daemon/dist/domain/rigspec-codec.d.ts.map +1 -1
- package/daemon/dist/domain/rigspec-codec.js +3 -0
- package/daemon/dist/domain/rigspec-codec.js.map +1 -1
- package/daemon/dist/domain/rigspec-exporter.d.ts.map +1 -1
- package/daemon/dist/domain/rigspec-exporter.js +4 -0
- package/daemon/dist/domain/rigspec-exporter.js.map +1 -1
- package/daemon/dist/domain/rigspec-instantiator.d.ts.map +1 -1
- package/daemon/dist/domain/rigspec-instantiator.js +11 -0
- package/daemon/dist/domain/rigspec-instantiator.js.map +1 -1
- package/daemon/dist/domain/rigspec-preflight.d.ts +6 -0
- package/daemon/dist/domain/rigspec-preflight.d.ts.map +1 -1
- package/daemon/dist/domain/rigspec-preflight.js +26 -1
- package/daemon/dist/domain/rigspec-preflight.js.map +1 -1
- package/daemon/dist/domain/rigspec-schema.d.ts.map +1 -1
- package/daemon/dist/domain/rigspec-schema.js +20 -1
- package/daemon/dist/domain/rigspec-schema.js.map +1 -1
- package/daemon/dist/domain/runtime-verifier.d.ts +7 -1
- package/daemon/dist/domain/runtime-verifier.d.ts.map +1 -1
- package/daemon/dist/domain/runtime-verifier.js +39 -1
- package/daemon/dist/domain/runtime-verifier.js.map +1 -1
- package/daemon/dist/domain/seat-handover-service.d.ts +9 -0
- package/daemon/dist/domain/seat-handover-service.d.ts.map +1 -1
- package/daemon/dist/domain/seat-handover-service.js +2 -0
- package/daemon/dist/domain/seat-handover-service.js.map +1 -1
- package/daemon/dist/domain/seat-identity-store.d.ts +7 -0
- package/daemon/dist/domain/seat-identity-store.d.ts.map +1 -1
- package/daemon/dist/domain/seat-identity-store.js +27 -0
- package/daemon/dist/domain/seat-identity-store.js.map +1 -1
- package/daemon/dist/domain/seat-status-service.d.ts.map +1 -1
- package/daemon/dist/domain/seat-status-service.js +6 -4
- package/daemon/dist/domain/seat-status-service.js.map +1 -1
- package/daemon/dist/domain/session-name.d.ts +22 -0
- package/daemon/dist/domain/session-name.d.ts.map +1 -1
- package/daemon/dist/domain/session-name.js +29 -0
- package/daemon/dist/domain/session-name.js.map +1 -1
- package/daemon/dist/domain/slices/slice-detail-projector.d.ts.map +1 -1
- package/daemon/dist/domain/slices/slice-detail-projector.js +4 -5
- package/daemon/dist/domain/slices/slice-detail-projector.js.map +1 -1
- package/daemon/dist/domain/spec-library-workflow-scanner.d.ts +18 -2
- package/daemon/dist/domain/spec-library-workflow-scanner.d.ts.map +1 -1
- package/daemon/dist/domain/spec-library-workflow-scanner.js +23 -1
- package/daemon/dist/domain/spec-library-workflow-scanner.js.map +1 -1
- package/daemon/dist/domain/startup-proof.d.ts +10 -6
- package/daemon/dist/domain/startup-proof.d.ts.map +1 -1
- package/daemon/dist/domain/startup-proof.js +35 -7
- package/daemon/dist/domain/startup-proof.js.map +1 -1
- package/daemon/dist/domain/successor-session-launcher.d.ts +16 -0
- package/daemon/dist/domain/successor-session-launcher.d.ts.map +1 -1
- package/daemon/dist/domain/successor-session-launcher.js +13 -0
- package/daemon/dist/domain/successor-session-launcher.js.map +1 -1
- package/daemon/dist/domain/terminal/cmux-provider-adapter.d.ts +27 -0
- package/daemon/dist/domain/terminal/cmux-provider-adapter.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/cmux-provider-adapter.js +67 -0
- package/daemon/dist/domain/terminal/cmux-provider-adapter.js.map +1 -0
- package/daemon/dist/domain/terminal/herdr-adapter.d.ts +77 -0
- package/daemon/dist/domain/terminal/herdr-adapter.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/herdr-adapter.js +266 -0
- package/daemon/dist/domain/terminal/herdr-adapter.js.map +1 -0
- package/daemon/dist/domain/terminal/herdr-transport.d.ts +50 -0
- package/daemon/dist/domain/terminal/herdr-transport.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/herdr-transport.js +145 -0
- package/daemon/dist/domain/terminal/herdr-transport.js.map +1 -0
- package/daemon/dist/domain/terminal/terminal-provider.d.ts +88 -0
- package/daemon/dist/domain/terminal/terminal-provider.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/terminal-provider.js +17 -0
- package/daemon/dist/domain/terminal/terminal-provider.js.map +1 -0
- package/daemon/dist/domain/terminal/terminal-service.d.ts +60 -0
- package/daemon/dist/domain/terminal/terminal-service.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/terminal-service.js +174 -0
- package/daemon/dist/domain/terminal/terminal-service.js.map +1 -0
- package/daemon/dist/domain/terminal/terminal-views-store.d.ts +77 -0
- package/daemon/dist/domain/terminal/terminal-views-store.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/terminal-views-store.js +146 -0
- package/daemon/dist/domain/terminal/terminal-views-store.js.map +1 -0
- package/daemon/dist/domain/terminal/view-composer.d.ts +42 -0
- package/daemon/dist/domain/terminal/view-composer.d.ts.map +1 -0
- package/daemon/dist/domain/terminal/view-composer.js +137 -0
- package/daemon/dist/domain/terminal/view-composer.js.map +1 -0
- package/daemon/dist/domain/tmux-option-defaults.d.ts +89 -0
- package/daemon/dist/domain/tmux-option-defaults.d.ts.map +1 -0
- package/daemon/dist/domain/tmux-option-defaults.js +132 -0
- package/daemon/dist/domain/tmux-option-defaults.js.map +1 -0
- package/daemon/dist/domain/types.d.ts +34 -1
- package/daemon/dist/domain/types.d.ts.map +1 -1
- package/daemon/dist/domain/types.js.map +1 -1
- package/daemon/dist/domain/user-settings/settings-store.d.ts +1 -1
- package/daemon/dist/domain/user-settings/settings-store.d.ts.map +1 -1
- package/daemon/dist/domain/user-settings/settings-store.js +92 -0
- package/daemon/dist/domain/user-settings/settings-store.js.map +1 -1
- package/daemon/dist/domain/workflow/slice-workflow-projection.d.ts.map +1 -1
- package/daemon/dist/domain/workflow/slice-workflow-projection.js +6 -2
- package/daemon/dist/domain/workflow/slice-workflow-projection.js.map +1 -1
- package/daemon/dist/domain/workflow-boot-sweep.d.ts +28 -0
- package/daemon/dist/domain/workflow-boot-sweep.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-boot-sweep.js +102 -0
- package/daemon/dist/domain/workflow-boot-sweep.js.map +1 -0
- package/daemon/dist/domain/workflow-deadline.d.ts +74 -0
- package/daemon/dist/domain/workflow-deadline.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-deadline.js +165 -0
- package/daemon/dist/domain/workflow-deadline.js.map +1 -0
- package/daemon/dist/domain/workflow-exception-escalation.d.ts +33 -0
- package/daemon/dist/domain/workflow-exception-escalation.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-exception-escalation.js +104 -0
- package/daemon/dist/domain/workflow-exception-escalation.js.map +1 -0
- package/daemon/dist/domain/workflow-exception-router.d.ts +33 -0
- package/daemon/dist/domain/workflow-exception-router.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-exception-router.js +96 -0
- package/daemon/dist/domain/workflow-exception-router.js.map +1 -0
- package/daemon/dist/domain/workflow-exception.d.ts +90 -0
- package/daemon/dist/domain/workflow-exception.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-exception.js +122 -0
- package/daemon/dist/domain/workflow-exception.js.map +1 -0
- package/daemon/dist/domain/workflow-frontier-guard.d.ts +24 -0
- package/daemon/dist/domain/workflow-frontier-guard.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-frontier-guard.js +30 -0
- package/daemon/dist/domain/workflow-frontier-guard.js.map +1 -0
- package/daemon/dist/domain/workflow-instance-store.d.ts +28 -0
- package/daemon/dist/domain/workflow-instance-store.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-instance-store.js +66 -10
- package/daemon/dist/domain/workflow-instance-store.js.map +1 -1
- package/daemon/dist/domain/workflow-keepalive-arming.d.ts +35 -0
- package/daemon/dist/domain/workflow-keepalive-arming.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-keepalive-arming.js +87 -0
- package/daemon/dist/domain/workflow-keepalive-arming.js.map +1 -0
- package/daemon/dist/domain/workflow-projector.d.ts +117 -2
- package/daemon/dist/domain/workflow-projector.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-projector.js +672 -41
- package/daemon/dist/domain/workflow-projector.js.map +1 -1
- package/daemon/dist/domain/workflow-role-context.d.ts +57 -0
- package/daemon/dist/domain/workflow-role-context.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-role-context.js +120 -0
- package/daemon/dist/domain/workflow-role-context.js.map +1 -0
- package/daemon/dist/domain/workflow-role-resolver.d.ts +65 -0
- package/daemon/dist/domain/workflow-role-resolver.d.ts.map +1 -0
- package/daemon/dist/domain/workflow-role-resolver.js +108 -0
- package/daemon/dist/domain/workflow-role-resolver.js.map +1 -0
- package/daemon/dist/domain/workflow-runtime.d.ts +157 -1
- package/daemon/dist/domain/workflow-runtime.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-runtime.js +718 -11
- package/daemon/dist/domain/workflow-runtime.js.map +1 -1
- package/daemon/dist/domain/workflow-spec-cache.d.ts +26 -1
- package/daemon/dist/domain/workflow-spec-cache.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-spec-cache.js +327 -13
- package/daemon/dist/domain/workflow-spec-cache.js.map +1 -1
- package/daemon/dist/domain/workflow-types.d.ts +143 -8
- package/daemon/dist/domain/workflow-types.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-types.js +14 -1
- package/daemon/dist/domain/workflow-types.js.map +1 -1
- package/daemon/dist/domain/workflow-validator.d.ts +11 -1
- package/daemon/dist/domain/workflow-validator.d.ts.map +1 -1
- package/daemon/dist/domain/workflow-validator.js +246 -1
- package/daemon/dist/domain/workflow-validator.js.map +1 -1
- package/daemon/dist/index.d.ts +17 -0
- package/daemon/dist/index.d.ts.map +1 -1
- package/daemon/dist/index.js +44 -0
- package/daemon/dist/index.js.map +1 -1
- package/daemon/dist/routes/activity.js +22 -0
- package/daemon/dist/routes/activity.js.map +1 -1
- package/daemon/dist/routes/hosts.d.ts +5 -0
- package/daemon/dist/routes/hosts.d.ts.map +1 -0
- package/daemon/dist/routes/hosts.js +346 -0
- package/daemon/dist/routes/hosts.js.map +1 -0
- package/daemon/dist/routes/queue.d.ts +2 -7
- package/daemon/dist/routes/queue.d.ts.map +1 -1
- package/daemon/dist/routes/queue.js +264 -9
- package/daemon/dist/routes/queue.js.map +1 -1
- package/daemon/dist/routes/review.d.ts.map +1 -1
- package/daemon/dist/routes/review.js +26 -0
- package/daemon/dist/routes/review.js.map +1 -1
- package/daemon/dist/routes/seat.js +13 -0
- package/daemon/dist/routes/seat.js.map +1 -1
- package/daemon/dist/routes/terminal.d.ts +11 -0
- package/daemon/dist/routes/terminal.d.ts.map +1 -0
- package/daemon/dist/routes/terminal.js +104 -0
- package/daemon/dist/routes/terminal.js.map +1 -0
- package/daemon/dist/routes/workflow.d.ts.map +1 -1
- package/daemon/dist/routes/workflow.js +102 -10
- package/daemon/dist/routes/workflow.js.map +1 -1
- package/daemon/dist/server.d.ts +5 -0
- package/daemon/dist/server.d.ts.map +1 -1
- package/daemon/dist/server.js +22 -0
- package/daemon/dist/server.js.map +1 -1
- package/daemon/dist/startup.d.ts.map +1 -1
- package/daemon/dist/startup.js +221 -12
- package/daemon/dist/startup.js.map +1 -1
- package/daemon/specs/agents/factory-rsi/dogfood/agent.yaml +29 -0
- package/daemon/specs/agents/factory-rsi/dogfood/guidance/role.md +37 -0
- package/daemon/specs/agents/factory-rsi/release-manager/agent.yaml +29 -0
- package/daemon/specs/agents/factory-rsi/release-manager/guidance/role.md +40 -0
- package/daemon/specs/agents/shared/skills/core/mission-slice-sop/SKILL.md +6 -8
- package/daemon/specs/agents/shared/skills/core/openrig-cmux/SKILL.md +86 -0
- package/daemon/specs/agents/shared/skills/core/openrig-herdr/SKILL.md +158 -0
- package/daemon/specs/agents/shared/skills/core/openrig-user/SKILL.md +6 -6
- package/daemon/specs/rigs/launch/factory-rsi/CULTURE.md +47 -0
- package/daemon/specs/rigs/launch/factory-rsi/rig.yaml +115 -0
- package/dist/build-info.js +3 -3
- package/dist/commands/broadcast.d.ts +6 -1
- package/dist/commands/broadcast.d.ts.map +1 -1
- package/dist/commands/broadcast.js +101 -15
- package/dist/commands/broadcast.js.map +1 -1
- package/dist/commands/capture.d.ts.map +1 -1
- package/dist/commands/capture.js +99 -8
- package/dist/commands/capture.js.map +1 -1
- package/dist/commands/config.d.ts.map +1 -1
- package/dist/commands/config.js +4 -0
- package/dist/commands/config.js.map +1 -1
- package/dist/commands/down.d.ts.map +1 -1
- package/dist/commands/down.js +5 -0
- package/dist/commands/down.js.map +1 -1
- package/dist/commands/host.d.ts +14 -0
- package/dist/commands/host.d.ts.map +1 -1
- package/dist/commands/host.js +338 -14
- package/dist/commands/host.js.map +1 -1
- package/dist/commands/launch.d.ts.map +1 -1
- package/dist/commands/launch.js +5 -0
- package/dist/commands/launch.js.map +1 -1
- package/dist/commands/ps.d.ts.map +1 -1
- package/dist/commands/ps.js +10 -4
- package/dist/commands/ps.js.map +1 -1
- package/dist/commands/queue.d.ts +36 -0
- package/dist/commands/queue.d.ts.map +1 -1
- package/dist/commands/queue.js +73 -7
- package/dist/commands/queue.js.map +1 -1
- package/dist/commands/restore-packet.d.ts.map +1 -1
- package/dist/commands/restore-packet.js +13 -2
- package/dist/commands/restore-packet.js.map +1 -1
- package/dist/commands/send.d.ts.map +1 -1
- package/dist/commands/send.js +114 -9
- package/dist/commands/send.js.map +1 -1
- package/dist/commands/terminal.d.ts +6 -0
- package/dist/commands/terminal.d.ts.map +1 -0
- package/dist/commands/terminal.js +116 -0
- package/dist/commands/terminal.js.map +1 -0
- package/dist/commands/transcript.d.ts +6 -1
- package/dist/commands/transcript.d.ts.map +1 -1
- package/dist/commands/transcript.js +113 -30
- package/dist/commands/transcript.js.map +1 -1
- package/dist/commands/up.d.ts.map +1 -1
- package/dist/commands/up.js +9 -0
- package/dist/commands/up.js.map +1 -1
- package/dist/commands/whoami.d.ts.map +1 -1
- package/dist/commands/whoami.js +11 -0
- package/dist/commands/whoami.js.map +1 -1
- package/dist/commands/workflow-errors.d.ts +23 -0
- package/dist/commands/workflow-errors.d.ts.map +1 -0
- package/dist/commands/workflow-errors.js +100 -0
- package/dist/commands/workflow-errors.js.map +1 -0
- package/dist/commands/workflow-follow.d.ts +71 -0
- package/dist/commands/workflow-follow.d.ts.map +1 -0
- package/dist/commands/workflow-follow.js +259 -0
- package/dist/commands/workflow-follow.js.map +1 -0
- package/dist/commands/workflow-render.d.ts +114 -0
- package/dist/commands/workflow-render.d.ts.map +1 -0
- package/dist/commands/workflow-render.js +223 -0
- package/dist/commands/workflow-render.js.map +1 -0
- package/dist/commands/workflow.d.ts +1 -0
- package/dist/commands/workflow.d.ts.map +1 -1
- package/dist/commands/workflow.js +278 -14
- package/dist/commands/workflow.js.map +1 -1
- package/dist/config-store.d.ts +15 -1
- package/dist/config-store.d.ts.map +1 -1
- package/dist/config-store.js +103 -0
- package/dist/config-store.js.map +1 -1
- package/dist/cross-host-cli-helpers.d.ts +13 -0
- package/dist/cross-host-cli-helpers.d.ts.map +1 -1
- package/dist/cross-host-cli-helpers.js +27 -0
- package/dist/cross-host-cli-helpers.js.map +1 -1
- package/dist/cross-host-target.d.ts +49 -0
- package/dist/cross-host-target.d.ts.map +1 -0
- package/dist/cross-host-target.js +35 -0
- package/dist/cross-host-target.js.map +1 -0
- package/dist/host-registry.d.ts +8 -0
- package/dist/host-registry.d.ts.map +1 -1
- package/dist/host-registry.js +32 -0
- package/dist/host-registry.js.map +1 -1
- package/dist/host-selection.d.ts +13 -0
- package/dist/host-selection.d.ts.map +1 -0
- package/dist/host-selection.js +54 -0
- package/dist/host-selection.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/index.js.map +1 -1
- package/dist/remote-host-ops.d.ts +1 -0
- package/dist/remote-host-ops.d.ts.map +1 -1
- package/dist/remote-host-ops.js +11 -4
- package/dist/remote-host-ops.js.map +1 -1
- package/dist/session-name.d.ts +23 -0
- package/dist/session-name.d.ts.map +1 -0
- package/dist/session-name.js +61 -0
- package/dist/session-name.js.map +1 -0
- package/package.json +1 -1
- package/ui/dist/assets/index-CFqxa0zW.css +32 -0
- package/ui/dist/assets/index-zrFqS7gL.js +623 -0
- package/ui/dist/index.html +2 -2
- package/ui/dist/assets/index-D69ZhNIr.js +0 -598
- package/ui/dist/assets/index-DJYun-8d.css +0 -32
|
@@ -9,10 +9,19 @@
|
|
|
9
9
|
//
|
|
10
10
|
// Pattern mirrors Phase B's ProjectClassifier facade shape.
|
|
11
11
|
import { WorkflowInstanceStore, WorkflowInstanceError, } from "./workflow-instance-store.js";
|
|
12
|
-
import {
|
|
12
|
+
import { resolveExceptionRoute } from "./workflow-exception-router.js";
|
|
13
|
+
import { classifyGateTrip, workflowExceptionTags } from "./workflow-exception.js";
|
|
14
|
+
import { newQitemId } from "./queue-repository.js";
|
|
15
|
+
import { WorkflowProjector, WorkflowProjectorError, compileGate, nodeRuntimeOf, reconcileExplicitOwnerHarness, resolveDefaultOwner, } from "./workflow-projector.js";
|
|
16
|
+
import { loadHostRegistry } from "./hosts/hosts-registry-reader.js";
|
|
13
17
|
import { WorkflowSpecCache, WorkflowSpecError } from "./workflow-spec-cache.js";
|
|
14
18
|
import { WorkflowStepTrailLog } from "./workflow-step-trail-log.js";
|
|
15
19
|
import { WorkflowValidator, } from "./workflow-validator.js";
|
|
20
|
+
import { disarmWorkflowKeepalive, ensureWorkflowKeepaliveArmed } from "./workflow-keepalive-arming.js";
|
|
21
|
+
import { evaluateStepDeadline } from "./workflow-deadline.js";
|
|
22
|
+
import { rigDeclaresRole, rigMemberExists, roleResolutionContext, tryResolveRoleByCapability, } from "./workflow-role-context.js";
|
|
23
|
+
import { isHumanSeatSession } from "./human-route-enforcer.js";
|
|
24
|
+
import { parseSessionName } from "./session-name.js";
|
|
16
25
|
export class WorkflowRuntime {
|
|
17
26
|
specCache;
|
|
18
27
|
instanceStore;
|
|
@@ -23,20 +32,65 @@ export class WorkflowRuntime {
|
|
|
23
32
|
eventBus;
|
|
24
33
|
queueRepo;
|
|
25
34
|
now;
|
|
35
|
+
watchdogJobsRepo;
|
|
36
|
+
exceptionDial;
|
|
26
37
|
constructor(deps) {
|
|
27
38
|
this.db = deps.db;
|
|
28
39
|
this.eventBus = deps.eventBus;
|
|
29
40
|
this.queueRepo = deps.queueRepo;
|
|
30
41
|
this.now = deps.now ?? (() => new Date());
|
|
42
|
+
this.watchdogJobsRepo = deps.watchdogJobsRepo;
|
|
43
|
+
this.exceptionDial = deps.exceptionDial;
|
|
31
44
|
this.specCache = new WorkflowSpecCache(this.db, this.now);
|
|
32
45
|
this.instanceStore = new WorkflowInstanceStore(this.db, this.now);
|
|
33
46
|
this.trailLog = new WorkflowStepTrailLog(this.db);
|
|
34
47
|
this.validator = new WorkflowValidator();
|
|
35
|
-
this.projector = new WorkflowProjector(this.db, this.eventBus, this.queueRepo, this.instanceStore, this.trailLog, this.specCache, this.now);
|
|
48
|
+
this.projector = new WorkflowProjector(this.db, this.eventBus, this.queueRepo, this.instanceStore, this.trailLog, this.specCache, this.now, this.watchdogJobsRepo, deps.exceptionDial);
|
|
36
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* OPR.0.4.6.WF5 FR-2: resolve the maturity dial for a CACHED spec —
|
|
52
|
+
* the class-(b) detection paths (sweep/keepalive) call this through
|
|
53
|
+
* the startup-injected closure. null = spec not cached (the caller's
|
|
54
|
+
* never-lost fallback applies). Uses the SAME preferred_targets[0]
|
|
55
|
+
* string-pick as step-owner resolution (arch Seam-A uniformity).
|
|
56
|
+
*/
|
|
57
|
+
resolveExceptionRouteFor(workflowName, workflowVersion, exceptionClass,
|
|
58
|
+
/** OPR.0.4.6.FAC1 (arch Q3): the instance's bound rig — dial
|
|
59
|
+
* position 3 (orchestrator-role) then resolves capability-aware
|
|
60
|
+
* on that rig when the role declares no preferred_targets.
|
|
61
|
+
* Absent/null = the shipped fleet-blind string-pick only. */
|
|
62
|
+
boundRig) {
|
|
63
|
+
const specRow = this.specCache.getByNameVersion(workflowName, workflowVersion);
|
|
64
|
+
if (!specRow)
|
|
65
|
+
return null;
|
|
66
|
+
const spec = specRow.spec;
|
|
67
|
+
const roleCtx = roleResolutionContext(this.db, boundRig ?? null);
|
|
68
|
+
return resolveExceptionRoute({
|
|
69
|
+
exceptionClass,
|
|
70
|
+
spec,
|
|
71
|
+
hostDialDefault: this.exceptionDial?.hostDefault() ?? null,
|
|
72
|
+
resolveRoleTarget: (role) => spec.roles?.[role]?.preferred_targets?.[0] ??
|
|
73
|
+
tryResolveRoleByCapability(roleCtx, role),
|
|
74
|
+
humanFallbackSeat: this.exceptionDial?.humanFallbackSeat ?? "human@host",
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* OPR.0.4.6.WF2 FR-3: the production host-registry probe for the
|
|
79
|
+
* validator — built on the daemon hosts-registry reader (read-only
|
|
80
|
+
* twin of the CLI registry). An unreadable/missing registry reports
|
|
81
|
+
* every id as unregistered with an empty id list (fail-loud at the
|
|
82
|
+
* validator's host_not_registered issue, never a silent pass).
|
|
83
|
+
*/
|
|
84
|
+
hostRegistryLookup = (hostId) => {
|
|
85
|
+
const loaded = loadHostRegistry();
|
|
86
|
+
if (!loaded.ok)
|
|
87
|
+
return { registered: false, registeredIds: [] };
|
|
88
|
+
const ids = loaded.registry.hosts.map((h) => h.id);
|
|
89
|
+
return { registered: ids.includes(hostId), registeredIds: ids };
|
|
90
|
+
};
|
|
37
91
|
validate(specPath, seatLivenessCheck) {
|
|
38
92
|
const specRow = this.specCache.readThrough(specPath);
|
|
39
|
-
return this.validator.validate(specRow.spec, seatLivenessCheck);
|
|
93
|
+
return this.validator.validate(specRow.spec, seatLivenessCheck, this.hostRegistryLookup);
|
|
40
94
|
}
|
|
41
95
|
/**
|
|
42
96
|
* Create a workflow instance + first-step qitem. The entry qitem is
|
|
@@ -53,7 +107,7 @@ export class WorkflowRuntime {
|
|
|
53
107
|
// the bare name straight to readThrough -> spec_file_missing.
|
|
54
108
|
const resolvedSpecPath = this.specCache.resolveSourcePathByName(input.specPath) ?? input.specPath;
|
|
55
109
|
const specRow = this.specCache.readThrough(resolvedSpecPath);
|
|
56
|
-
const validation = this.validator.validate(specRow.spec);
|
|
110
|
+
const validation = this.validator.validate(specRow.spec, undefined, this.hostRegistryLookup);
|
|
57
111
|
if (!validation.ok) {
|
|
58
112
|
throw new WorkflowProjectorError("spec_invalid", `cannot instantiate: spec ${specRow.name}@${specRow.version} has ${validation.issues.filter((i) => i.severity === "error").length} validation error(s); run validate to inspect`, { specPath: input.specPath, issues: validation.issues });
|
|
59
113
|
}
|
|
@@ -61,8 +115,245 @@ export class WorkflowRuntime {
|
|
|
61
115
|
if (!entryStep) {
|
|
62
116
|
throw new WorkflowProjectorError("spec_no_steps", `cannot instantiate: spec ${specRow.name}@${specRow.version} has no steps[]`, { specPath: input.specPath });
|
|
63
117
|
}
|
|
64
|
-
|
|
65
|
-
|
|
118
|
+
// OPR.0.4.6.WF2 FR-3: THE v1 execution boundary (the slice-11
|
|
119
|
+
// pattern). A REMOTE host pin is legal LANGUAGE (it validated
|
|
120
|
+
// above) but fails loud HERE — the queue is local-only until MH-3
|
|
121
|
+
// (cross-host queue routing); minting a qitem into a queue that
|
|
122
|
+
// cannot route it, or silently running the step locally, are both
|
|
123
|
+
// forbidden. Checked for EVERY step at instantiate (the earliest
|
|
124
|
+
// knowable moment — a mid-run surprise would strand the instance).
|
|
125
|
+
for (const step of specRow.spec.steps) {
|
|
126
|
+
if (step.host && step.host !== "local") {
|
|
127
|
+
throw new WorkflowProjectorError("host_pin_remote_unsupported", `cannot instantiate: step "${step.id}" pins host "${step.host}". Remote-step execution requires MH-3 (cross-host queue routing), which has not shipped — the queue is local-only today. Workaround: run that step's seat on this host (host: local, or drop the pin), or wait for MH-3.`, { specPath: input.specPath, stepId: step.id, host: step.host, boundary: "MH-3" });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
// OPR.0.4.6.FAC1 (AC-1) + arch ruling 2026-07-07 (target-rig
|
|
131
|
+
// zero-regression, "Option A refined by PROVENANCE"): resolve the
|
|
132
|
+
// instance's rig binding by SPLITTING on the provenance of the rig
|
|
133
|
+
// name, because the two sources carry different intent:
|
|
134
|
+
//
|
|
135
|
+
// - Operator `input.targetRig` (explicit `--rig X`) is AUTHORITATIVE:
|
|
136
|
+
// an explicit instantiation demand. Unknown X → `bound_rig_unknown`
|
|
137
|
+
// HARD-FAIL, loud, before any mutation (unchanged built behavior).
|
|
138
|
+
// - Spec-default `spec.target.rig` is ADVISORY: the spec author's
|
|
139
|
+
// default HINT, authored under the pre-FAC-1 regime where the field
|
|
140
|
+
// was IGNORED at runtime (display-only). Unknown → DEGRADE to
|
|
141
|
+
// UNBOUND + a LOUD advisory (not silent, not a hard-fail). This
|
|
142
|
+
// preserves AC-1 zero-regression for shipped/example specs (e.g.
|
|
143
|
+
// `conveyor` declares `target.rig: conveyor` AND routes every step
|
|
144
|
+
// via preferred_targets — degrading to unbound routes exactly as
|
|
145
|
+
// pre-FAC-1). A spec that genuinely needs a bound rig still fails
|
|
146
|
+
// loudly, per-step, at the right granularity (entry →
|
|
147
|
+
// `entry_owner_unresolved` at instantiate; later role-only step →
|
|
148
|
+
// `next_owner_unresolved` at projection). Nothing degrades to
|
|
149
|
+
// silence — it degrades to per-step honest failure with a heads-up.
|
|
150
|
+
// - Neither set → unbound (byte-identical today's behavior).
|
|
151
|
+
//
|
|
152
|
+
// name→id re-resolves fresh at each later resolution site, so a rig
|
|
153
|
+
// vanishing mid-run fails loud there (WF-5 catches it).
|
|
154
|
+
const registeredRigNames = () => this.db.prepare(`SELECT DISTINCT name FROM rigs ORDER BY name`).all().map((r) => r.name);
|
|
155
|
+
const rigIsRegistered = (name) => this.db.prepare(`SELECT id FROM rigs WHERE name = ? LIMIT 1`).get(name) !== undefined;
|
|
156
|
+
const advisories = [];
|
|
157
|
+
let boundRig;
|
|
158
|
+
if (input.targetRig != null) {
|
|
159
|
+
// AUTHORITATIVE path: honor the operator's explicit demand or fail loud.
|
|
160
|
+
if (!rigIsRegistered(input.targetRig)) {
|
|
161
|
+
const registered = registeredRigNames();
|
|
162
|
+
throw new WorkflowProjectorError("bound_rig_unknown", `cannot instantiate: target rig "${input.targetRig}" is not a registered rig on this daemon. Registered rigs: ${registered.length > 0 ? registered.join(", ") : "(none)"}. Check \`rig ps\`, create/import the rig first, or instantiate with a different --rig.`, { specPath: input.specPath, targetRig: input.targetRig, registeredRigs: registered });
|
|
163
|
+
}
|
|
164
|
+
boundRig = input.targetRig;
|
|
165
|
+
}
|
|
166
|
+
else if (specRow.spec.target?.rig != null) {
|
|
167
|
+
// ADVISORY path: the spec author's DEFAULT hint. Unknown → degrade
|
|
168
|
+
// to unbound with a loud advisory (never a hard-fail on a default).
|
|
169
|
+
const specDefaultRig = specRow.spec.target.rig;
|
|
170
|
+
if (!rigIsRegistered(specDefaultRig)) {
|
|
171
|
+
const registered = registeredRigNames();
|
|
172
|
+
boundRig = null;
|
|
173
|
+
advisories.push(`workflow spec default target.rig "${specDefaultRig}" is not a registered rig on this daemon — instantiating UNBOUND. ` +
|
|
174
|
+
`Steps route via their declared preferred_targets; any role-only step (no preferred_targets) will fail per-role at the right time ` +
|
|
175
|
+
`(entry at instantiate, later steps at projection). Pass --rig <name> to bind explicitly. ` +
|
|
176
|
+
`Registered rigs: ${registered.length > 0 ? registered.join(", ") : "(none)"}.`);
|
|
177
|
+
}
|
|
178
|
+
else {
|
|
179
|
+
boundRig = specDefaultRig;
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
else {
|
|
183
|
+
boundRig = null;
|
|
184
|
+
}
|
|
185
|
+
// OPR.0.4.6.WF2 FR-2: static harness-pin reconciliation for EVERY
|
|
186
|
+
// pinned step at instantiate (the earliest knowable moment against
|
|
187
|
+
// current inventory); projection re-checks at each route (runtimes
|
|
188
|
+
// can change mid-flight).
|
|
189
|
+
//
|
|
190
|
+
// OPR.0.4.6.FAC1 (ARCH Q2 = GUARD B1, binding): this eager loop
|
|
191
|
+
// does NO live role resolution and RECORDS NOTHING. It keeps the
|
|
192
|
+
// shipped harness/preferred-target reconciliation for steps whose
|
|
193
|
+
// role DECLARES targets (spec-only facts — sound to check now); a
|
|
194
|
+
// BOUND instance's role-only step (zero declared targets) is
|
|
195
|
+
// deliberately SKIPPED here — a factory rig warms up, and that
|
|
196
|
+
// step's liveness is its own projection-time concern where
|
|
197
|
+
// loud-with-candidates + WF-5 own the failure. The structural
|
|
198
|
+
// zero-role-coverage check below is the only instantiate-time
|
|
199
|
+
// hard-fail for role-only steps. Unbound specs keep today's
|
|
200
|
+
// behavior byte-identically (a pinned no-target step still fails
|
|
201
|
+
// "(none declared)" at instantiate — nothing could ever resolve it).
|
|
202
|
+
const runtimeOf = (session) => nodeRuntimeOf(this.db, session);
|
|
203
|
+
const declaredTargetsOf = (roleName) => (specRow.spec.roles?.[roleName]?.preferred_targets ?? []).length;
|
|
204
|
+
for (const step of specRow.spec.steps) {
|
|
205
|
+
if (step.harness) {
|
|
206
|
+
// rev1-r2 blocker fix: gated steps are NOT excluded — a pinned
|
|
207
|
+
// gated step reconciles through its gate compile (human gates
|
|
208
|
+
// resolve the step owner pin-aware; handler gates match the pin
|
|
209
|
+
// against the handler role's targets). Both throw
|
|
210
|
+
// harness_pin_unsatisfied when no candidate matches.
|
|
211
|
+
if (step.gate) {
|
|
212
|
+
const gateIsHuman = isHumanSeatSession(step.gate.target);
|
|
213
|
+
const gateRoleTargets = gateIsHuman
|
|
214
|
+
? declaredTargetsOf(step.actor_role)
|
|
215
|
+
: declaredTargetsOf(step.gate.target);
|
|
216
|
+
if (boundRig !== null && gateRoleTargets === 0)
|
|
217
|
+
continue; // role-only on a bound rig: projection resolves
|
|
218
|
+
compileGate(specRow.spec, step, runtimeOf);
|
|
219
|
+
}
|
|
220
|
+
else if (!(step === entryStep && input.entryOwnerSession)) {
|
|
221
|
+
if (boundRig !== null && declaredTargetsOf(step.actor_role) === 0)
|
|
222
|
+
continue;
|
|
223
|
+
resolveDefaultOwner(specRow.spec, step, runtimeOf);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
// OPR.0.4.6.FAC1 (ARCH Q2): the STRUCTURAL role-coverage check for
|
|
228
|
+
// a BOUND instance — hard-fail ONLY when a step's role (or a
|
|
229
|
+
// handler-gate's target role) with zero declared preferred_targets
|
|
230
|
+
// is declared by ZERO seats on the bound rig, at ANY lifecycle
|
|
231
|
+
// state. Existence, not liveness: catches typos and missing role
|
|
232
|
+
// attributes at instantiate WITHOUT eager live resolution. The
|
|
233
|
+
// entry step is included — it resolves live below anyway, but a
|
|
234
|
+
// structural miss reads better as this named error.
|
|
235
|
+
if (boundRig !== null) {
|
|
236
|
+
for (const step of specRow.spec.steps) {
|
|
237
|
+
const rolesToCover = [];
|
|
238
|
+
if (declaredTargetsOf(step.actor_role) === 0)
|
|
239
|
+
rolesToCover.push(step.actor_role);
|
|
240
|
+
if (step.gate && !isHumanSeatSession(step.gate.target) && declaredTargetsOf(step.gate.target) === 0) {
|
|
241
|
+
rolesToCover.push(step.gate.target);
|
|
242
|
+
}
|
|
243
|
+
for (const roleName of rolesToCover) {
|
|
244
|
+
if (!rigDeclaresRole(this.db, boundRig, roleName)) {
|
|
245
|
+
throw new WorkflowProjectorError("bound_rig_role_uncovered", `cannot instantiate: step "${step.id}" needs role "${roleName}" but NO seat on rig "${boundRig}" declares that role (at any lifecycle state). Add a member with role ${roleName} to rig ${boundRig} (rig add), declare role: ${roleName} on an existing member, or add preferred_targets to the role in the spec. (A declared-but-not-yet-running seat is fine — liveness is checked when the step projects.)`, { specPath: input.specPath, stepId: step.id, role: roleName, boundRig });
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
// OPR.0.4.6.FAC3 (FR-5): the member-exists instantiate ADVISORY —
|
|
251
|
+
// catch a mis-routed destination (a typo'd/stale member on a rig
|
|
252
|
+
// this daemon DOES know) loudly at the earliest knowable moment,
|
|
253
|
+
// never silently orphaned. ADVISORY-NEVER-DENY: instantiate always
|
|
254
|
+
// proceeds, and the queue transport gate stays rig-exists-only —
|
|
255
|
+
// hardening it to member-exists would gate EVERY queue write and
|
|
256
|
+
// break legitimate non-managed destinations (adopted seats, human
|
|
257
|
+
// seats, MH-3-forwarded items). A read-only pre-txn pass over
|
|
258
|
+
// spec-declared targets; sync SQL only.
|
|
259
|
+
//
|
|
260
|
+
// Scope = roles REFERENCED BY STEPS (actor_role + a handler gate's
|
|
261
|
+
// target role — the same reference set the structural coverage
|
|
262
|
+
// check above walks): the advisory must name a declaring step, and
|
|
263
|
+
// an unreferenced role's targets never route. Skip order per the
|
|
264
|
+
// queue-gate archetype: human-seat classifier BEFORE parse (the
|
|
265
|
+
// identical predicate the transport uses) → non-canonical
|
|
266
|
+
// (raw/adopted destinations are legitimate; the inventory cannot
|
|
267
|
+
// vouch for them) → unregistered rig (the transport already rejects
|
|
268
|
+
// those loudly at queue-write — no double advisory) → member probe
|
|
269
|
+
// (existence at ANY lifecycle state/kind; liveness is projection's
|
|
270
|
+
// business). ONE aggregated advisory per unique unknown target,
|
|
271
|
+
// naming every declaring step/role pair.
|
|
272
|
+
{
|
|
273
|
+
const unknownTargets = new Map();
|
|
274
|
+
const seenPairs = new Set();
|
|
275
|
+
const probeRoleTargets = (roleName, stepId) => {
|
|
276
|
+
for (const target of specRow.spec.roles?.[roleName]?.preferred_targets ?? []) {
|
|
277
|
+
if (isHumanSeatSession(target))
|
|
278
|
+
continue;
|
|
279
|
+
const parsed = parseSessionName(target);
|
|
280
|
+
if (parsed.kind !== "canonical")
|
|
281
|
+
continue;
|
|
282
|
+
if (!rigIsRegistered(parsed.rig))
|
|
283
|
+
continue;
|
|
284
|
+
let memberExists;
|
|
285
|
+
try {
|
|
286
|
+
memberExists = rigMemberExists(this.db, parsed.rig, target);
|
|
287
|
+
}
|
|
288
|
+
catch {
|
|
289
|
+
// ADVISORY-NEVER-THROW (VM-caught, run-1): the probe rides the
|
|
290
|
+
// full inventory projection, which can error on a
|
|
291
|
+
// partial-schema DB (e.g. a test fixture without the snapshots
|
|
292
|
+
// table). A probe error means the inventory cannot vouch
|
|
293
|
+
// EITHER way — skip silently; an advisory path must never be
|
|
294
|
+
// able to fail the instantiate.
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (memberExists)
|
|
298
|
+
continue;
|
|
299
|
+
const pairKey = JSON.stringify([target, stepId, roleName]);
|
|
300
|
+
if (seenPairs.has(pairKey))
|
|
301
|
+
continue;
|
|
302
|
+
seenPairs.add(pairKey);
|
|
303
|
+
const entry = unknownTargets.get(target) ?? { rig: parsed.rig, declaredBy: [] };
|
|
304
|
+
entry.declaredBy.push({ stepId, role: roleName });
|
|
305
|
+
unknownTargets.set(target, entry);
|
|
306
|
+
}
|
|
307
|
+
};
|
|
308
|
+
for (const step of specRow.spec.steps) {
|
|
309
|
+
probeRoleTargets(step.actor_role, step.id);
|
|
310
|
+
if (step.gate && !isHumanSeatSession(step.gate.target)) {
|
|
311
|
+
probeRoleTargets(step.gate.target, step.id);
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
for (const [target, { rig, declaredBy }] of unknownTargets) {
|
|
315
|
+
const declares = declaredBy
|
|
316
|
+
.map((d) => `step "${d.stepId}" (role "${d.role}")`)
|
|
317
|
+
.join(", ");
|
|
318
|
+
advisories.push(`preferred target "${target}" names rig "${rig}" (registered) but NO member of that rig has this coordinate — declared by ${declares}. ` +
|
|
319
|
+
`Work routed there will not be claimed; it will surface as a stuck exception. ` +
|
|
320
|
+
`Check the member name against \`rig ps\`, or add the member to rig "${rig}".`);
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
// OPR.0.4.6.WF2 FR-5: a gated ENTRY step compiles to the gate item
|
|
324
|
+
// (human-routed or handler-routed) and the instance parks waiting
|
|
325
|
+
// from birth — same socket as a mid-flow gate.
|
|
326
|
+
// OPR.0.4.6.FAC1 (ARCH Q2/call-site row 3): the ENTRY step gets
|
|
327
|
+
// FULL live resolution at instantiate and is RECORDED — the entry
|
|
328
|
+
// packet is actually created now (the first routing decision), so
|
|
329
|
+
// this is resolve-once, not eager pre-resolution. A tier-3 failure
|
|
330
|
+
// here re-throws under the entry error code, candidates preserved.
|
|
331
|
+
const entryRoleCtx = roleResolutionContext(this.db, boundRig);
|
|
332
|
+
let entryGate;
|
|
333
|
+
let entryOwner;
|
|
334
|
+
try {
|
|
335
|
+
entryGate = entryStep.gate
|
|
336
|
+
? compileGate(specRow.spec, entryStep, runtimeOf, entryRoleCtx)
|
|
337
|
+
: null;
|
|
338
|
+
if (entryGate) {
|
|
339
|
+
entryOwner = entryGate.destinationSession;
|
|
340
|
+
}
|
|
341
|
+
else if (input.entryOwnerSession) {
|
|
342
|
+
reconcileExplicitOwnerHarness(entryStep, input.entryOwnerSession, runtimeOf);
|
|
343
|
+
entryOwner = input.entryOwnerSession;
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
entryOwner = resolveDefaultOwner(specRow.spec, entryStep, runtimeOf, entryRoleCtx);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (err) {
|
|
350
|
+
if (err instanceof WorkflowProjectorError && err.code === "next_owner_unresolved") {
|
|
351
|
+
// entry_owner_unresolved SPEAKS CANDIDATES: same structured
|
|
352
|
+
// details, the entry site's error-code contract preserved.
|
|
353
|
+
throw new WorkflowProjectorError("entry_owner_unresolved", `cannot instantiate: ${err.message}`, { specPath: input.specPath, entryStepId: entryStep.id, entryRole: entryStep.actor_role, ...(err.details ?? {}) });
|
|
354
|
+
}
|
|
355
|
+
throw err;
|
|
356
|
+
}
|
|
66
357
|
if (!entryOwner) {
|
|
67
358
|
throw new WorkflowProjectorError("entry_owner_unresolved", `cannot instantiate: entry step "${entryStep.id}" (role "${entryStep.actor_role}") has no preferred_targets and no entryOwnerSession was supplied`, { specPath: input.specPath, entryStepId: entryStep.id, entryRole: entryStep.actor_role });
|
|
68
359
|
}
|
|
@@ -82,10 +373,31 @@ export class WorkflowRuntime {
|
|
|
82
373
|
// projector resolves the correct step on the first project()
|
|
83
374
|
// call without any trail-based inference.
|
|
84
375
|
currentStepId: entryStep.id,
|
|
376
|
+
// OPR.0.4.6.FAC1: the resolved rig binding persists with the
|
|
377
|
+
// instance row (same txn as the entry packet).
|
|
378
|
+
boundRig,
|
|
85
379
|
});
|
|
86
380
|
instanceId = instance.instanceId;
|
|
87
|
-
// Create entry qitem in the same txn
|
|
381
|
+
// Create entry qitem in the same txn (gate-aware: a gated entry
|
|
382
|
+
// rides the shipped human-route / handler-route write path).
|
|
383
|
+
// OPR.0.4.6.WF5 FR-1 class (c) (guard code-review fold — the entry
|
|
384
|
+
// twin of the projector's mid-flow stamp): a HUMAN-gated ENTRY
|
|
385
|
+
// carries the class-(c) exception identity on the WF-2 item
|
|
386
|
+
// itself, occurrence = the preallocated packet id. Handler-role
|
|
387
|
+
// entries stay negative.
|
|
388
|
+
const entryGateQitemId = entryGate ? newQitemId() : undefined;
|
|
389
|
+
const entryGateException = entryGate && entryGateQitemId
|
|
390
|
+
? classifyGateTrip({
|
|
391
|
+
workflowName: specRow.name,
|
|
392
|
+
instanceId: instance.instanceId,
|
|
393
|
+
gatedStepId: entryStep.id,
|
|
394
|
+
gateKind: entryGate.kind,
|
|
395
|
+
gatePacketId: entryGateQitemId,
|
|
396
|
+
parkOn: entryGate.parkOn,
|
|
397
|
+
})
|
|
398
|
+
: null;
|
|
88
399
|
const created = this.queueRepo.createWithinTransaction({
|
|
400
|
+
qitemId: entryGateQitemId,
|
|
89
401
|
sourceSession: input.createdBySession,
|
|
90
402
|
destinationSession: entryOwner,
|
|
91
403
|
body: workflowInstantiateBody({
|
|
@@ -93,16 +405,55 @@ export class WorkflowRuntime {
|
|
|
93
405
|
instanceId: instance.instanceId,
|
|
94
406
|
entryStep,
|
|
95
407
|
rootObjective: input.rootObjective,
|
|
408
|
+
gate: entryGate,
|
|
96
409
|
}),
|
|
97
410
|
priority: "routine",
|
|
98
|
-
tier: "mode2",
|
|
99
|
-
tags: [
|
|
411
|
+
tier: entryGate?.tier ?? "mode2",
|
|
412
|
+
tags: [
|
|
413
|
+
"workflow",
|
|
414
|
+
entryGate ? "gate" : "entry",
|
|
415
|
+
`workflow:${specRow.name}`,
|
|
416
|
+
`instance:${instance.instanceId}`,
|
|
417
|
+
...(entryGateException ? workflowExceptionTags(entryGateException.identity).filter((t) => !t.startsWith("workflow:") && !t.startsWith("instance:")) : []),
|
|
418
|
+
],
|
|
419
|
+
summary: entryGate?.summary ?? undefined,
|
|
420
|
+
evidenceRef: entryGate?.evidenceRef ?? undefined,
|
|
100
421
|
});
|
|
101
422
|
entryQitemId = created.qitemId;
|
|
102
423
|
entryQitemDestinationSession = created.destinationSession;
|
|
103
424
|
entryQitemNudge = created.nudge;
|
|
104
425
|
persistedEvents.push(created.persistedEvent);
|
|
105
|
-
|
|
426
|
+
// OPR.0.4.6.WF2 FR-5 (guard blocker 1): a HUMAN-gated ENTRY parks
|
|
427
|
+
// in the same txn — the leg-1 blocked_on human-seat shape the
|
|
428
|
+
// shipped resolve verb acts on (same as the projector's mid-flow
|
|
429
|
+
// gate park).
|
|
430
|
+
if (entryGate?.parkOn) {
|
|
431
|
+
const parked = this.queueRepo.updateWithinTransaction({
|
|
432
|
+
qitemId: created.qitemId,
|
|
433
|
+
actorSession: input.createdBySession,
|
|
434
|
+
state: "blocked",
|
|
435
|
+
closureReason: "blocked_on",
|
|
436
|
+
closureTarget: entryGate.parkOn,
|
|
437
|
+
blockedOn: entryGate.parkOn,
|
|
438
|
+
transitionNote: `workflow gate: parked on ${entryGate.parkOn} pending sign-off`,
|
|
439
|
+
});
|
|
440
|
+
persistedEvents.push(parked.persistedEvent);
|
|
441
|
+
}
|
|
442
|
+
this.instanceStore.updateFrontier(instance.instanceId, [created.qitemId], entryGate ? "waiting" : "active", {
|
|
443
|
+
// FR-5: guarded even here — the instance was created in this
|
|
444
|
+
// txn at version 0; uniformity keeps every advance guarded.
|
|
445
|
+
expectedVersion: instance.version,
|
|
446
|
+
});
|
|
447
|
+
// FR-3: arm the per-instance keepalive INSIDE the same txn that
|
|
448
|
+
// creates the entry packet (covers commit-then-crash-before-nudge
|
|
449
|
+
// from the very first step).
|
|
450
|
+
if (this.watchdogJobsRepo) {
|
|
451
|
+
ensureWorkflowKeepaliveArmed(this.watchdogJobsRepo, {
|
|
452
|
+
instanceId: instance.instanceId,
|
|
453
|
+
targetSession: entryOwner,
|
|
454
|
+
registeredBySession: input.createdBySession,
|
|
455
|
+
});
|
|
456
|
+
}
|
|
106
457
|
persistedEvents.push(this.eventBus.persistWithinTransaction({
|
|
107
458
|
type: "workflow.instantiated",
|
|
108
459
|
instanceId: instance.instanceId,
|
|
@@ -123,11 +474,328 @@ export class WorkflowRuntime {
|
|
|
123
474
|
spec: specRow,
|
|
124
475
|
entryQitemId: entryQitemId,
|
|
125
476
|
entryOwnerSession: entryOwner,
|
|
477
|
+
advisories,
|
|
126
478
|
};
|
|
127
479
|
}
|
|
128
480
|
async project(input) {
|
|
129
481
|
return this.projector.project(input);
|
|
130
482
|
}
|
|
483
|
+
/**
|
|
484
|
+
* OPR.0.4.6.WF3 FR-4 — `route`: re-target the CURRENT FRONTIER step
|
|
485
|
+
* of a live instance to a new owner. THE ADJUDICATED MECHANISM
|
|
486
|
+
* (arch, formal, on the advance-authority ground): CLOSE + RECREATE
|
|
487
|
+
* + FRONTIER REBIND in ONE scribe transaction. Revocation is
|
|
488
|
+
* STRUCTURAL: the old packet leaves the frontier inside this txn, so
|
|
489
|
+
* a zombie old owner's stale `project` hits the shipped
|
|
490
|
+
* `packet_not_on_frontier` 409 — zero new validation machinery on
|
|
491
|
+
* the hot advance path (the weighed-and-rejected alternative).
|
|
492
|
+
*
|
|
493
|
+
* The observable contract (PRD FR-4 (1)-(8)):
|
|
494
|
+
* (1) owner = target after route (5) frontier non-dangling
|
|
495
|
+
* (2) current_step_id UNCHANGED (6) additive event detail
|
|
496
|
+
* (3) actor+reason+old→new durable (7) pin + version guard held
|
|
497
|
+
* (4) NO forged completion closure (8) zombie structurally 409'd
|
|
498
|
+
* Route is NOT an advance: hop_count does not bump (max_hops counts
|
|
499
|
+
* steps, not re-targets); BR-3 — `project` stays the sole advance.
|
|
500
|
+
*/
|
|
501
|
+
/**
|
|
502
|
+
* OPR.0.4.6.WF5 FR-4 — RESUME from where it stopped (redrive
|
|
503
|
+
* semantics, the one engine extension). One scribe transaction:
|
|
504
|
+
* failed → active REBOUND to the failed step, a FRESH frontier packet
|
|
505
|
+
* to the step's RE-RESOLVED owner, the trail preserved and extended
|
|
506
|
+
* (completed steps never re-run), the livelock rail re-baselined
|
|
507
|
+
* (hops-since-resume — exactly one more bounded window), the redrive
|
|
508
|
+
* count recorded, open exception items for THIS occurrence closed
|
|
509
|
+
* (resolve+resume closes the occurrence; a later re-failure is a NEW
|
|
510
|
+
* occurrence), keepalive re-armed, additive workflow.resumed event.
|
|
511
|
+
*
|
|
512
|
+
* THE ARCH PIN (plan Rev-2, binding): the owner is RE-RESOLVED
|
|
513
|
+
* through the SAME resolution path projection uses
|
|
514
|
+
* (resolveDefaultOwner — preferred_targets + harness reconciliation),
|
|
515
|
+
* NEVER copied from the closed packet's recorded destination: a dead
|
|
516
|
+
* seat is a common CAUSE of the exception, and resume is the one
|
|
517
|
+
* sanctioned re-resolution point (FAC-1 R1) the binding layer later
|
|
518
|
+
* upgrades uniformly.
|
|
519
|
+
*/
|
|
520
|
+
async resume(input) {
|
|
521
|
+
const persistedEvents = [];
|
|
522
|
+
let result;
|
|
523
|
+
let nudgeTo = null;
|
|
524
|
+
const txn = this.db.transaction(() => {
|
|
525
|
+
const instance = this.instanceStore.getByIdOrThrow(input.instanceId);
|
|
526
|
+
if (instance.status !== "failed") {
|
|
527
|
+
throw new WorkflowProjectorError("instance_not_failed", `instance ${instance.instanceId} is ${instance.status}; resume re-drives FAILED instances only (a waiting instance resumes via the shipped project path; an active instance needs no resume)`, { instanceId: instance.instanceId, status: instance.status, expectedStatus: "failed" });
|
|
528
|
+
}
|
|
529
|
+
const specRow = this.specCache.getByNameVersion(instance.workflowName, instance.workflowVersion);
|
|
530
|
+
if (!specRow) {
|
|
531
|
+
throw new WorkflowProjectorError("spec_not_cached", `workflow spec ${instance.workflowName}@${instance.workflowVersion} is not in the spec cache; re-run validate to refresh it before resuming`, { workflowName: instance.workflowName, workflowVersion: instance.workflowVersion });
|
|
532
|
+
}
|
|
533
|
+
const spec = specRow.spec;
|
|
534
|
+
const decision = (instance.lastContinuationDecision ?? {});
|
|
535
|
+
const failedStepId = decision.currentStep;
|
|
536
|
+
const failedPacketId = decision.closedPacket;
|
|
537
|
+
if (!failedStepId) {
|
|
538
|
+
throw new WorkflowProjectorError("resume_step_unrecoverable", `instance ${instance.instanceId} carries no recorded failed step (pre-R2 row without lastContinuationDecision.currentStep); cannot rebind — instantiate a fresh run`, { instanceId: instance.instanceId });
|
|
539
|
+
}
|
|
540
|
+
const step = spec.steps.find((st) => st.id === failedStepId);
|
|
541
|
+
if (!step) {
|
|
542
|
+
throw new WorkflowProjectorError("resume_step_missing_from_spec", `failed step "${failedStepId}" no longer exists in ${instance.workflowName}@${instance.workflowVersion}; fix the spec (the cached version is authoritative for in-flight instances) or instantiate a fresh run`, { instanceId: instance.instanceId, stepId: failedStepId });
|
|
543
|
+
}
|
|
544
|
+
// THE ARCH PIN: re-resolve, never copy.
|
|
545
|
+
// OPR.0.4.6.FAC1 (call-site row 5): resume is the ONE sanctioned
|
|
546
|
+
// re-resolution point and now runs the full tier stack — a bound
|
|
547
|
+
// instance's role-only failed step re-resolves capability-aware
|
|
548
|
+
// against CURRENT inventory (a dead seat is a common CAUSE of the
|
|
549
|
+
// exception; the redrive picks the seat that is eligible NOW).
|
|
550
|
+
const owner = resolveDefaultOwner(spec, step, (session) => nodeRuntimeOf(this.db, session), roleResolutionContext(this.db, instance.boundRig));
|
|
551
|
+
if (!owner) {
|
|
552
|
+
throw new WorkflowProjectorError("next_owner_unresolved", `cannot resolve an owner for failed step "${step.id}" (role "${step.actor_role}"); add preferred_targets to the role before resuming`, { instanceId: instance.instanceId, stepId: step.id, role: step.actor_role });
|
|
553
|
+
}
|
|
554
|
+
// Fresh frontier packet — the redrive delivery. The --decision
|
|
555
|
+
// text lands durably in the packet body (the resumer’s
|
|
556
|
+
// instruction reaches the step owner).
|
|
557
|
+
const created = this.queueRepo.createWithinTransaction({
|
|
558
|
+
sourceSession: input.actorSession,
|
|
559
|
+
destinationSession: owner,
|
|
560
|
+
body: `WORKFLOW RESUME (redrive)\n` +
|
|
561
|
+
`workflow: ${instance.workflowName} v${instance.workflowVersion}\n` +
|
|
562
|
+
`instance: ${instance.instanceId}\n` +
|
|
563
|
+
`step: ${step.id} (role ${step.actor_role}) — re-driven from the recorded failure; completed steps are NOT re-run\n` +
|
|
564
|
+
`resumed by: ${input.actorSession} (redrive #${(instance.resumeCount ?? 0) + 1})\n` +
|
|
565
|
+
(input.decision ? `decision: ${input.decision}\n` : "") +
|
|
566
|
+
`history: rig workflow trace ${instance.instanceId}`,
|
|
567
|
+
priority: "routine",
|
|
568
|
+
tier: "mode2",
|
|
569
|
+
tags: [
|
|
570
|
+
"workflow",
|
|
571
|
+
"resume",
|
|
572
|
+
`workflow:${instance.workflowName}`,
|
|
573
|
+
`instance:${instance.instanceId}`,
|
|
574
|
+
],
|
|
575
|
+
chainOfRecord: failedPacketId ? [failedPacketId] : undefined,
|
|
576
|
+
});
|
|
577
|
+
persistedEvents.push(created.persistedEvent);
|
|
578
|
+
nudgeTo = { qitemId: created.qitemId, session: created.destinationSession, nudge: created.nudge };
|
|
579
|
+
// Resolve+resume CLOSES the occurrence: open exception items for
|
|
580
|
+
// THIS episode close honestly with resume provenance. A later
|
|
581
|
+
// re-failure mints a NEW packet id = a NEW occurrence (never
|
|
582
|
+
// hidden behind this resolved past).
|
|
583
|
+
let exceptionItemsClosed = 0;
|
|
584
|
+
if (failedPacketId) {
|
|
585
|
+
const openItems = this.db
|
|
586
|
+
.prepare(`SELECT qitem_id FROM queue_items
|
|
587
|
+
WHERE state IN ('pending','in-progress','blocked')
|
|
588
|
+
AND tags LIKE ? AND tags LIKE ?`)
|
|
589
|
+
.all(`%"occurrence:${failedPacketId}"%`, `%"workflow-exception"%`);
|
|
590
|
+
for (const row of openItems) {
|
|
591
|
+
const closedItem = this.queueRepo.updateWithinTransaction({
|
|
592
|
+
qitemId: row.qitem_id,
|
|
593
|
+
actorSession: input.actorSession,
|
|
594
|
+
state: "done",
|
|
595
|
+
closureReason: "no-follow-on",
|
|
596
|
+
transitionNote: `workflow resume: occurrence resolved by ${input.actorSession} redriving step ${step.id}${input.decision ? ` — ${input.decision}` : ""}`,
|
|
597
|
+
});
|
|
598
|
+
persistedEvents.push(closedItem.persistedEvent);
|
|
599
|
+
exceptionItemsClosed += 1;
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
// Frontier rebind + status + THE LIVELOCK RAIL: hops_baseline =
|
|
603
|
+
// hopCount at resume (one fresh bounded window under the same
|
|
604
|
+
// max_hops), resume_count recorded, version guard held
|
|
605
|
+
// (concurrent resumes: exactly one commits).
|
|
606
|
+
this.instanceStore.updateFrontier(instance.instanceId, [created.qitemId], "active", {
|
|
607
|
+
currentStepId: step.id,
|
|
608
|
+
expectedVersion: instance.version,
|
|
609
|
+
resumeStamp: {
|
|
610
|
+
resumeCount: (instance.resumeCount ?? 0) + 1,
|
|
611
|
+
hopsBaseline: instance.hopCount,
|
|
612
|
+
},
|
|
613
|
+
});
|
|
614
|
+
// Keepalive re-arm for the redriven owner (in-txn, WF-1 FR-3).
|
|
615
|
+
if (this.watchdogJobsRepo) {
|
|
616
|
+
ensureWorkflowKeepaliveArmed(this.watchdogJobsRepo, {
|
|
617
|
+
instanceId: instance.instanceId,
|
|
618
|
+
targetSession: owner,
|
|
619
|
+
registeredBySession: input.actorSession,
|
|
620
|
+
});
|
|
621
|
+
}
|
|
622
|
+
persistedEvents.push(this.eventBus.persistWithinTransaction({
|
|
623
|
+
type: "workflow.resumed",
|
|
624
|
+
instanceId: instance.instanceId,
|
|
625
|
+
workflowName: instance.workflowName,
|
|
626
|
+
stepId: step.id,
|
|
627
|
+
resumedBy: input.actorSession,
|
|
628
|
+
decision: input.decision ?? null,
|
|
629
|
+
resumeCount: (instance.resumeCount ?? 0) + 1,
|
|
630
|
+
}));
|
|
631
|
+
result = {
|
|
632
|
+
instanceId: instance.instanceId,
|
|
633
|
+
stepId: step.id,
|
|
634
|
+
newPacketId: created.qitemId,
|
|
635
|
+
ownerSession: owner,
|
|
636
|
+
resumeCount: (instance.resumeCount ?? 0) + 1,
|
|
637
|
+
exceptionItemsClosed,
|
|
638
|
+
};
|
|
639
|
+
});
|
|
640
|
+
txn();
|
|
641
|
+
for (const e of persistedEvents) {
|
|
642
|
+
this.eventBus.notifySubscribers(e);
|
|
643
|
+
}
|
|
644
|
+
// Closure-assignment cast (the shipped post-commit idiom): TS cannot
|
|
645
|
+
// track the txn-closure write, so narrow via the cast.
|
|
646
|
+
const resumeNudge = nudgeTo;
|
|
647
|
+
if (resumeNudge) {
|
|
648
|
+
await this.queueRepo.maybeNudge(resumeNudge.qitemId, resumeNudge.session, resumeNudge.nudge);
|
|
649
|
+
}
|
|
650
|
+
return result;
|
|
651
|
+
}
|
|
652
|
+
async route(input) {
|
|
653
|
+
const persistedEvents = [];
|
|
654
|
+
let result;
|
|
655
|
+
let nudgeTo = null;
|
|
656
|
+
const txn = this.db.transaction(() => {
|
|
657
|
+
const instance = this.instanceStore.getByIdOrThrow(input.instanceId);
|
|
658
|
+
if (instance.status !== "active" && instance.status !== "waiting") {
|
|
659
|
+
throw new WorkflowProjectorError("instance_not_active", `instance ${instance.instanceId} is ${instance.status}; only a live (active|waiting) instance can be re-routed`, { instanceId: instance.instanceId, status: instance.status });
|
|
660
|
+
}
|
|
661
|
+
const oldPacketId = instance.currentFrontier[0];
|
|
662
|
+
if (!oldPacketId) {
|
|
663
|
+
throw new WorkflowProjectorError("packet_not_found", `instance ${instance.instanceId} has an empty frontier; nothing to re-route`, { instanceId: instance.instanceId });
|
|
664
|
+
}
|
|
665
|
+
const oldPacket = this.queueRepo.getById(oldPacketId);
|
|
666
|
+
if (!oldPacket) {
|
|
667
|
+
throw new WorkflowProjectorError("packet_not_found", `frontier packet ${oldPacketId} not found`, { instanceId: instance.instanceId, packetId: oldPacketId });
|
|
668
|
+
}
|
|
669
|
+
const fromSession = oldPacket.destinationSession;
|
|
670
|
+
// (7) harness pin: the SAME reconciliation the projector applies
|
|
671
|
+
// to explicit --next-owner overrides — an explicit route target
|
|
672
|
+
// never silently defeats a declared pin.
|
|
673
|
+
const specRow = this.specCache.getByNameVersion(instance.workflowName, instance.workflowVersion);
|
|
674
|
+
const step = instance.currentStepId && specRow
|
|
675
|
+
? specRow.spec.steps.find((s) => s.id === instance.currentStepId) ?? null
|
|
676
|
+
: null;
|
|
677
|
+
if (step) {
|
|
678
|
+
reconcileExplicitOwnerHarness(step, input.toSession, (session) => nodeRuntimeOf(this.db, session));
|
|
679
|
+
}
|
|
680
|
+
// (3)+(4) close the old packet HONESTLY: handed_off_to with full
|
|
681
|
+
// provenance in the transition — never a forged completion.
|
|
682
|
+
const closed = this.queueRepo.updateWithinTransaction({
|
|
683
|
+
qitemId: oldPacketId,
|
|
684
|
+
actorSession: input.actorSession,
|
|
685
|
+
viaWorkflowVerb: true,
|
|
686
|
+
state: "handed-off",
|
|
687
|
+
closureReason: "handed_off_to",
|
|
688
|
+
closureTarget: input.toSession,
|
|
689
|
+
handedOffTo: input.toSession,
|
|
690
|
+
transitionNote: `workflow route: ${input.actorSession} re-routed step ${instance.currentStepId ?? "?"} from ${fromSession} to ${input.toSession}${input.reason ? ` — ${input.reason}` : ""}`,
|
|
691
|
+
});
|
|
692
|
+
persistedEvents.push(closed.persistedEvent);
|
|
693
|
+
// Recreate the SAME step for the new owner (step identity is the
|
|
694
|
+
// work's continuity — the qitem id is a storage artifact of the
|
|
695
|
+
// append-only design). chainOfRecord threads the lineage.
|
|
696
|
+
// FULL-FIDELITY FIELD CARRY (rev1-r2 BLOCKING fold): the
|
|
697
|
+
// successor IS the same work item, so it keeps the source
|
|
698
|
+
// packet's priority/tier/summary/evidenceRef/targetRepo — a
|
|
699
|
+
// human-gated packet (blocked on a human seat) MUST keep
|
|
700
|
+
// summary + evidence_ref or the shipped human-park validator
|
|
701
|
+
// rejects the repark below (human_route_fields_required) and
|
|
702
|
+
// the waiting-on-human class — the one route most exists for —
|
|
703
|
+
// becomes un-routable.
|
|
704
|
+
const created = this.queueRepo.createWithinTransaction({
|
|
705
|
+
sourceSession: input.actorSession,
|
|
706
|
+
destinationSession: input.toSession,
|
|
707
|
+
body: oldPacket.body,
|
|
708
|
+
priority: oldPacket.priority ?? "routine",
|
|
709
|
+
tier: oldPacket.tier ?? "mode2",
|
|
710
|
+
// OPR.0.4.6.WF5 (rev1-r2 B1 fold): the successor IS the same work
|
|
711
|
+
// item — tags carry VERBATIM (+ re-route) so a routed class-(c)
|
|
712
|
+
// gate item keeps its exception identity on the live frontier
|
|
713
|
+
// packet (workflow-exception/step:/exception:human_gate_trip/
|
|
714
|
+
// occurrence:<ORIGINAL gate packet id> — the occurrence is the
|
|
715
|
+
// EPISODE, which route does not end; chainOfRecord links the
|
|
716
|
+
// packet lineage). The WF-3 full-fidelity-carry lesson, extended
|
|
717
|
+
// to the tag dimension.
|
|
718
|
+
tags: Array.from(new Set([
|
|
719
|
+
...(oldPacket.tags ?? []),
|
|
720
|
+
"workflow",
|
|
721
|
+
"re-route",
|
|
722
|
+
`workflow:${instance.workflowName}`,
|
|
723
|
+
`instance:${instance.instanceId}`,
|
|
724
|
+
])),
|
|
725
|
+
chainOfRecord: [oldPacketId],
|
|
726
|
+
summary: oldPacket.summary ?? undefined,
|
|
727
|
+
evidenceRef: oldPacket.evidenceRef ?? undefined,
|
|
728
|
+
targetRepo: oldPacket.targetRepo ?? undefined,
|
|
729
|
+
});
|
|
730
|
+
persistedEvents.push(created.persistedEvent);
|
|
731
|
+
nudgeTo = { qitemId: created.qitemId, session: created.destinationSession, nudge: created.nudge };
|
|
732
|
+
// A parked (waiting) frontier packet keeps its park on the
|
|
733
|
+
// successor — route changes the owner, never the recorded state.
|
|
734
|
+
// summary/evidenceRef passed explicitly too (belt + suspenders
|
|
735
|
+
// with the create-side carry): validateHumanPark evaluates the
|
|
736
|
+
// EFFECTIVE values, so a human park re-parks with its fields
|
|
737
|
+
// intact instead of throwing human_route_fields_required.
|
|
738
|
+
if (oldPacket.state === "blocked" && oldPacket.blockedOn) {
|
|
739
|
+
const reparked = this.queueRepo.updateWithinTransaction({
|
|
740
|
+
qitemId: created.qitemId,
|
|
741
|
+
actorSession: input.actorSession,
|
|
742
|
+
state: "blocked",
|
|
743
|
+
closureReason: "blocked_on",
|
|
744
|
+
closureTarget: oldPacket.blockedOn,
|
|
745
|
+
blockedOn: oldPacket.blockedOn,
|
|
746
|
+
summary: oldPacket.summary ?? undefined,
|
|
747
|
+
evidenceRef: oldPacket.evidenceRef ?? undefined,
|
|
748
|
+
transitionNote: `workflow route: park preserved (${oldPacket.blockedOn})`,
|
|
749
|
+
});
|
|
750
|
+
persistedEvents.push(reparked.persistedEvent);
|
|
751
|
+
}
|
|
752
|
+
// (2)+(5)+(7) frontier REBIND: same step, new packet, version
|
|
753
|
+
// guard held; NO hop bump (not an advance).
|
|
754
|
+
this.instanceStore.updateFrontier(instance.instanceId, [created.qitemId], instance.status, {
|
|
755
|
+
currentStepId: "preserve",
|
|
756
|
+
expectedVersion: instance.version,
|
|
757
|
+
});
|
|
758
|
+
// Keepalive re-target IN-TXN (arch n3: covers route's lost-nudge
|
|
759
|
+
// window — the armed job re-nudges the new owner).
|
|
760
|
+
if (this.watchdogJobsRepo) {
|
|
761
|
+
disarmWorkflowKeepalive(this.watchdogJobsRepo, instance.instanceId, `workflow route: re-targeted to ${input.toSession}`);
|
|
762
|
+
ensureWorkflowKeepaliveArmed(this.watchdogJobsRepo, {
|
|
763
|
+
instanceId: instance.instanceId,
|
|
764
|
+
targetSession: input.toSession,
|
|
765
|
+
registeredBySession: input.actorSession,
|
|
766
|
+
});
|
|
767
|
+
}
|
|
768
|
+
// (6) the shipped event shape {rigName, cause} extended ADDITIVELY.
|
|
769
|
+
persistedEvents.push(this.eventBus.persistWithinTransaction({
|
|
770
|
+
type: "workflow.routing_table_changed",
|
|
771
|
+
// OPR.0.4.6.FAC1 (display-only): the instance's actual bound
|
|
772
|
+
// rig wins over the spec's default label.
|
|
773
|
+
rigName: instance.boundRig ?? specRow?.targetRig ?? "",
|
|
774
|
+
cause: "workflow_route",
|
|
775
|
+
instanceId: instance.instanceId,
|
|
776
|
+
stepId: instance.currentStepId,
|
|
777
|
+
from: fromSession,
|
|
778
|
+
to: input.toSession,
|
|
779
|
+
}));
|
|
780
|
+
result = {
|
|
781
|
+
instanceId: instance.instanceId,
|
|
782
|
+
stepId: instance.currentStepId,
|
|
783
|
+
closedPacketId: oldPacketId,
|
|
784
|
+
newPacketId: created.qitemId,
|
|
785
|
+
fromSession,
|
|
786
|
+
toSession: input.toSession,
|
|
787
|
+
instanceStatus: instance.status,
|
|
788
|
+
};
|
|
789
|
+
});
|
|
790
|
+
txn();
|
|
791
|
+
for (const e of persistedEvents)
|
|
792
|
+
this.eventBus.notifySubscribers(e);
|
|
793
|
+
if (nudgeTo) {
|
|
794
|
+
const n = nudgeTo;
|
|
795
|
+
await this.queueRepo.maybeNudge(n.qitemId, n.session, n.nudge, input.actorSession);
|
|
796
|
+
}
|
|
797
|
+
return result;
|
|
798
|
+
}
|
|
131
799
|
/**
|
|
132
800
|
* Continue: idempotent inspector for the current frontier of an
|
|
133
801
|
* instance. v1 is read-only — returns the current state. POC's
|
|
@@ -137,7 +805,39 @@ export class WorkflowRuntime {
|
|
|
137
805
|
continue(instanceId) {
|
|
138
806
|
const instance = this.instanceStore.getByIdOrThrow(instanceId);
|
|
139
807
|
const trail = this.trailLog.listForInstance(instanceId);
|
|
140
|
-
return { instance, trail };
|
|
808
|
+
return { instance: this.withDeadline(instance), trail };
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* OPR.0.4.6.WF1 FR-2 COMPLETION FIXBACK (build-vs-ratified-AC debt,
|
|
812
|
+
* qitem-20260706211220-279039f5): the ratified FR-2 AC requires the
|
|
813
|
+
* stuck classification to be "queryable via list/show/trace … with
|
|
814
|
+
* the evidence (step, owner, deadline, age)". The merged WF-1 build
|
|
815
|
+
* surfaced it only through the boot sweep + keepalive nudges; this
|
|
816
|
+
* closes the queryability clause by deriving the SAME evaluator
|
|
817
|
+
* verdict (one threshold home — workflow-deadline.ts) at read time.
|
|
818
|
+
*
|
|
819
|
+
* DERIVED, NEVER STORED: recomputed per read from (instance,
|
|
820
|
+
* frontier packets, now) — a normal re-projection self-clears it,
|
|
821
|
+
* exactly like every other evaluator consumer. Exposes the FULL
|
|
822
|
+
* classification tuple (state + evidence{step, owner, anchor,
|
|
823
|
+
* anchorAt, overdueBySeconds, ageSeconds}) so BOTH consumers (WF-3's
|
|
824
|
+
* status rollup and WF-5's FR-3 ▲ source) read one shape — no
|
|
825
|
+
* boolean flattening, no second path.
|
|
826
|
+
*/
|
|
827
|
+
deadlineFor(instance) {
|
|
828
|
+
const packets = instance.currentFrontier
|
|
829
|
+
.map((id) => this.queueRepo.getById(id))
|
|
830
|
+
.filter((p) => p != null);
|
|
831
|
+
return evaluateStepDeadline(instance, packets, this.now());
|
|
832
|
+
}
|
|
833
|
+
/** The additive read enrichment consumed by list/show/trace routes. */
|
|
834
|
+
withDeadline(instance) {
|
|
835
|
+
return { ...instance, deadline: this.deadlineFor(instance) };
|
|
836
|
+
}
|
|
837
|
+
/** List instances (optionally filtered) with the deadline verdict attached. */
|
|
838
|
+
listInstancesWithDeadline(status) {
|
|
839
|
+
const rows = status ? this.instanceStore.listByStatus(status) : this.instanceStore.listAll();
|
|
840
|
+
return rows.map((row) => this.withDeadline(row));
|
|
141
841
|
}
|
|
142
842
|
}
|
|
143
843
|
function workflowInstantiateBody(input) {
|
|
@@ -149,6 +849,13 @@ function workflowInstantiateBody(input) {
|
|
|
149
849
|
"",
|
|
150
850
|
`Root objective: ${input.rootObjective}`,
|
|
151
851
|
];
|
|
852
|
+
if (input.gate) {
|
|
853
|
+
lines.push("", `Gate: ${input.gate.kind === "human" ? "human sign-off" : "handler-role check"} — the workflow is PARKED (waiting) until this item is resolved/closed; the flow then continues from this step.`);
|
|
854
|
+
if (input.gate.summary)
|
|
855
|
+
lines.push(`Ask: ${input.gate.summary}`);
|
|
856
|
+
if (input.gate.evidenceRef)
|
|
857
|
+
lines.push(`Evidence: ${input.gate.evidenceRef}`);
|
|
858
|
+
}
|
|
152
859
|
if (input.entryStep.objective) {
|
|
153
860
|
lines.push("", `Step objective: ${input.entryStep.objective}`);
|
|
154
861
|
}
|