@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
|
@@ -19,6 +19,30 @@
|
|
|
19
19
|
//
|
|
20
20
|
// All in one db.transaction. If any step throws, all rollback. After
|
|
21
21
|
// commit, fan out events to subscribers + nudge next owner.
|
|
22
|
+
import { exceedsMaxHops, MAX_HOPS_BASELINE_V1 } from "./workflow-deadline.js";
|
|
23
|
+
import { isHumanSeatSession } from "./human-route-enforcer.js";
|
|
24
|
+
import { selectRoleSeat } from "./workflow-role-resolver.js";
|
|
25
|
+
import { roleResolutionContext, tryResolveRoleByCapability, } from "./workflow-role-context.js";
|
|
26
|
+
import { classifyFailedInstance, classifyGateTrip, workflowExceptionTags } from "./workflow-exception.js";
|
|
27
|
+
import { newQitemId } from "./queue-repository.js";
|
|
28
|
+
import { resolveExceptionRoute } from "./workflow-exception-router.js";
|
|
29
|
+
import { disarmWorkflowKeepalive, ensureWorkflowKeepaliveArmed, } from "./workflow-keepalive-arming.js";
|
|
30
|
+
/**
|
|
31
|
+
* OPR.0.4.6.WF2 FR-2: canonical-session → node runtime column lookup
|
|
32
|
+
* (the harness-pin reconciliation source). Latest session row wins
|
|
33
|
+
* (the node-inventory join discipline). null = not a managed node.
|
|
34
|
+
* Shared by the projector (projection-time pins) and the runtime
|
|
35
|
+
* facade (instantiate-time static pin checks).
|
|
36
|
+
*/
|
|
37
|
+
export function nodeRuntimeOf(db, session) {
|
|
38
|
+
const row = db
|
|
39
|
+
.prepare(`SELECT n.runtime FROM nodes n
|
|
40
|
+
JOIN sessions s ON s.node_id = n.id
|
|
41
|
+
WHERE s.session_name = ?
|
|
42
|
+
ORDER BY s.id DESC LIMIT 1`)
|
|
43
|
+
.get(session);
|
|
44
|
+
return row?.runtime ?? null;
|
|
45
|
+
}
|
|
22
46
|
export class WorkflowProjectorError extends Error {
|
|
23
47
|
code;
|
|
24
48
|
details;
|
|
@@ -37,7 +61,17 @@ export class WorkflowProjector {
|
|
|
37
61
|
trailLog;
|
|
38
62
|
specCache;
|
|
39
63
|
now;
|
|
40
|
-
|
|
64
|
+
watchdogJobsRepo;
|
|
65
|
+
exceptionDial;
|
|
66
|
+
constructor(db, eventBus, queueRepo, instanceStore, trailLog, specCache, now = () => new Date(),
|
|
67
|
+
/** OPR.0.4.6.WF1 FR-3: optional — arms/disarms the per-instance keepalive in-txn. */
|
|
68
|
+
watchdogJobsRepo,
|
|
69
|
+
/** OPR.0.4.6.WF5 FR-2: the maturity-dial inputs, injected at startup
|
|
70
|
+
* (the validateRig precedent — the projector never reads config
|
|
71
|
+
* itself). hostDefault is read LIVE per exception (dial flips apply
|
|
72
|
+
* to future items only). Absent = engine defaults (orchestrator-
|
|
73
|
+
* first chain with the human@host never-lost fallback). */
|
|
74
|
+
exceptionDial) {
|
|
41
75
|
this.db = db;
|
|
42
76
|
this.eventBus = eventBus;
|
|
43
77
|
this.queueRepo = queueRepo;
|
|
@@ -45,6 +79,11 @@ export class WorkflowProjector {
|
|
|
45
79
|
this.trailLog = trailLog;
|
|
46
80
|
this.specCache = specCache;
|
|
47
81
|
this.now = now;
|
|
82
|
+
this.watchdogJobsRepo = watchdogJobsRepo;
|
|
83
|
+
this.exceptionDial = exceptionDial;
|
|
84
|
+
}
|
|
85
|
+
nodeRuntimeOf(session) {
|
|
86
|
+
return nodeRuntimeOf(this.db, session);
|
|
48
87
|
}
|
|
49
88
|
/**
|
|
50
89
|
* Close the current packet AND create/project the next-step packet
|
|
@@ -94,25 +133,144 @@ export class WorkflowProjector {
|
|
|
94
133
|
});
|
|
95
134
|
}
|
|
96
135
|
// Determine next step (one-hop default; multi-hop is graduation).
|
|
97
|
-
|
|
136
|
+
// OPR.0.4.6.WF2 FR-1: the recorded exit participates — a MAPPED exit
|
|
137
|
+
// resolves to its branch target (deterministic: same (spec, step,
|
|
138
|
+
// exit) → same target); unmapped exits fall through to the exact
|
|
139
|
+
// structural semantics shipped today.
|
|
140
|
+
const branchTargetId = currentStep.next_hop?.on?.[input.exit];
|
|
141
|
+
const branchRouted = branchTargetId !== undefined;
|
|
142
|
+
const nextStep = resolveNextStep(spec, currentStep, input.exit);
|
|
143
|
+
if (branchRouted && !nextStep) {
|
|
144
|
+
// Validator guarantees branch targets exist at validate/instantiate;
|
|
145
|
+
// this guards a stale cached spec drifting under a live instance.
|
|
146
|
+
throw new WorkflowProjectorError("branch_target_missing", `step "${currentStep.id}" maps exit "${input.exit}" to step "${branchTargetId}" but the spec ${spec.id}@${spec.version} has no such step. Re-validate the spec file.`, { instanceId: instance.instanceId, stepId: currentStep.id, exit: input.exit, branchTargetId });
|
|
147
|
+
}
|
|
148
|
+
// A route happens on a structural handoff OR any branch-mapped exit
|
|
149
|
+
// (the ONE WF-2 engine extension: language-driven routing through
|
|
150
|
+
// the same transactional scribe).
|
|
151
|
+
const willRoute = branchRouted || input.exit === "handoff";
|
|
152
|
+
// FR-5 (G3): the waiting-replay ABSORPTION check. `waiting`
|
|
153
|
+
// deliberately KEEPS the closed packet on the frontier (the
|
|
154
|
+
// keepalive wakes the parked owner off it), so a replayed waiting
|
|
155
|
+
// project passes the frontier guard — before this slice it
|
|
156
|
+
// double-wrote the trail. Absorption closes the hole under the
|
|
157
|
+
// guard-ratified FULL CLOSURE-INTENT IDENTITY (G-WF1-1): the replay
|
|
158
|
+
// is absorbed (ZERO writes, the stored first outcome returned) ONLY
|
|
159
|
+
// when EVERY field that can change observable queue/trail/decision
|
|
160
|
+
// state matches the stored decision exactly. ANY mismatch is a NEW
|
|
161
|
+
// legitimate decision (re-park-with-new-reason) recorded honestly
|
|
162
|
+
// through the normal write path below — never absorbed, never
|
|
163
|
+
// rejected. Terminal-exit replays stay the distinct frontier 409
|
|
164
|
+
// above (FR-1c).
|
|
165
|
+
if (input.exit === "waiting" && instance.status === "waiting") {
|
|
166
|
+
const absorbed = matchesStoredWaitingDecision(instance, input, currentStep.id, this.trailLog);
|
|
167
|
+
if (absorbed) {
|
|
168
|
+
return {
|
|
169
|
+
instance,
|
|
170
|
+
closurePriorPacketId: input.currentPacketId,
|
|
171
|
+
closureReason: "waiting",
|
|
172
|
+
nextQitemId: null,
|
|
173
|
+
nextOwnerSession: null,
|
|
174
|
+
nextStepId: null,
|
|
175
|
+
emittedEventTypes: [],
|
|
176
|
+
absorbedReplay: true,
|
|
177
|
+
};
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
// FR-6 (G4): enforce loop_guards.max_hops at projection time — this
|
|
181
|
+
// makes migration 034's "enforced at projection time" comment TRUE.
|
|
182
|
+
// A handoff that would exceed the guard is converted into an HONEST
|
|
183
|
+
// structured failure: the packet still closes (BR-3 shapes), the
|
|
184
|
+
// instance fails with the guard named in trail + event evidence,
|
|
185
|
+
// and no next qitem is minted — never an unbounded hop loop, never
|
|
186
|
+
// a silent stop, never a parked potato. The owner's requested exit
|
|
187
|
+
// was validated against allowed_exits ABOVE; the conversion below
|
|
188
|
+
// is engine-authored (guard enforcement), not an owner exit, so it
|
|
189
|
+
// deliberately bypasses allowed_exits.
|
|
190
|
+
// The comparison runs against an EFFECTIVE BASELINE (arch N1):
|
|
191
|
+
// v1 pins baseline = MAX_HOPS_BASELINE_V1 (0); WF-5 FR-4's resume
|
|
192
|
+
// later amends the baseline so each redrive gets one bounded window.
|
|
193
|
+
// OPR.0.4.6.WF2: the guard fires on ANY route (structural handoff OR
|
|
194
|
+
// a branch-mapped exit) — branch edges create the canonical
|
|
195
|
+
// remediation cycles, and an advance is an advance. Guard precedence
|
|
196
|
+
// is BEFORE branch resolution takes effect: a tripped guard is a
|
|
197
|
+
// terminal honest failure, never a routed branch.
|
|
198
|
+
const maxHops = spec.loop_guards?.max_hops;
|
|
199
|
+
// OPR.0.4.6.WF5 FR-4 (the livelock rail): the baseline is the
|
|
200
|
+
// instance's recorded hops_baseline — 0 for never-resumed instances
|
|
201
|
+
// (byte-identical to the v1 constant), hopCount-at-resume after a
|
|
202
|
+
// redrive, so each sanctioned drive gets exactly one bounded window.
|
|
203
|
+
const maxHopsTripped = willRoute &&
|
|
204
|
+
exceedsMaxHops(instance.hopCount, instance.hopsBaseline ?? MAX_HOPS_BASELINE_V1, maxHops);
|
|
205
|
+
const effectiveExit = maxHopsTripped ? "failed" : input.exit;
|
|
206
|
+
const effectiveResultNote = maxHopsTripped
|
|
207
|
+
? `max_hops_exceeded: hop ${instance.hopCount + 1} would exceed loop_guards.max_hops=${maxHops}`
|
|
208
|
+
: input.resultNote;
|
|
209
|
+
// The route actually happens only when the guard did not trip.
|
|
210
|
+
const routes = willRoute && !maxHopsTripped;
|
|
211
|
+
let effectiveClosureEvidence = maxHopsTripped
|
|
212
|
+
? {
|
|
213
|
+
...(input.closureEvidence ?? {}),
|
|
214
|
+
max_hops_guard: {
|
|
215
|
+
code: "max_hops_exceeded",
|
|
216
|
+
maxHops,
|
|
217
|
+
hopCount: instance.hopCount,
|
|
218
|
+
attemptedHop: instance.hopCount + 1,
|
|
219
|
+
},
|
|
220
|
+
}
|
|
221
|
+
: input.closureEvidence;
|
|
222
|
+
// OPR.0.4.6.WF2 FR-1 (arch PIN 1): the taken branch is recorded
|
|
223
|
+
// ADDITIVELY in the trail row's structured evidence JSON (and in
|
|
224
|
+
// last_continuation_decision below) — NEVER in closure_reason (a
|
|
225
|
+
// closed Phase-A enum + kept hot-potato contract).
|
|
226
|
+
if (routes && branchRouted && nextStep) {
|
|
227
|
+
effectiveClosureEvidence = {
|
|
228
|
+
...(effectiveClosureEvidence ?? {}),
|
|
229
|
+
branch_taken: { exit: input.exit, target: nextStep.id },
|
|
230
|
+
};
|
|
231
|
+
}
|
|
98
232
|
const evaluatedAt = this.now().toISOString();
|
|
233
|
+
// OPR.0.4.6.FAC1: the bound-rig resolution context — LAZY (building
|
|
234
|
+
// it reads nothing; the snapshot materializes only if tier-3 role
|
|
235
|
+
// resolution actually runs, which is strictly AFTER the frontier +
|
|
236
|
+
// absorption guards above returned/threw for replays — guard B1's
|
|
237
|
+
// zero-inventory-reads-on-replays pin holds by construction).
|
|
238
|
+
// undefined for unbound instances (tier 3 does not exist for them).
|
|
239
|
+
const roleCtx = roleResolutionContext(this.db, instance.boundRig);
|
|
99
240
|
let nextQitemId = null;
|
|
100
241
|
let nextOwnerSession = null;
|
|
101
242
|
let nextStepId = null;
|
|
102
243
|
let nextQitemCreatePostCommit = null;
|
|
244
|
+
// OPR.0.4.6.WF5 FR-2 class (a): the exception item's post-commit nudge
|
|
245
|
+
// (delivery is best-effort; the durable item is the guarantee).
|
|
246
|
+
let exceptionItemPostCommit = null;
|
|
103
247
|
const persistedEvents = [];
|
|
104
248
|
const txn = this.db.transaction(() => {
|
|
105
249
|
// 1. Resolve next owner BEFORE closing prior so closure_target is set.
|
|
250
|
+
// OPR.0.4.6.WF2: owner resolution runs for ANY route (structural
|
|
251
|
+
// handoff or branch-mapped exit). A gated target step compiles to
|
|
252
|
+
// the gate destination instead of the step's role owner (FR-5).
|
|
106
253
|
let resolvedNextOwner = null;
|
|
107
|
-
|
|
254
|
+
let gateCompile = null;
|
|
255
|
+
if (routes) {
|
|
108
256
|
if (!nextStep) {
|
|
109
257
|
throw new WorkflowProjectorError("no_next_step", `workflow instance ${instance.instanceId} reached terminal step "${currentStep.id}" but exit was handoff; use exit=done for terminal steps or extend the spec with a next step`, { instanceId: instance.instanceId, currentStepId: currentStep.id });
|
|
110
258
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
259
|
+
if (nextStep.gate) {
|
|
260
|
+
gateCompile = compileGate(spec, nextStep, (s) => this.nodeRuntimeOf(s), roleCtx);
|
|
261
|
+
resolvedNextOwner = gateCompile.destinationSession;
|
|
262
|
+
}
|
|
263
|
+
else if (input.nextOwnerSession) {
|
|
264
|
+
reconcileExplicitOwnerHarness(nextStep, input.nextOwnerSession, (s) => this.nodeRuntimeOf(s));
|
|
265
|
+
resolvedNextOwner = input.nextOwnerSession;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
const owner = resolveDefaultOwner(spec, nextStep, (s) => this.nodeRuntimeOf(s), roleCtx);
|
|
269
|
+
if (!owner) {
|
|
270
|
+
throw new WorkflowProjectorError("next_owner_unresolved", `cannot resolve next owner for step "${nextStep.id}" (role "${nextStep.actor_role}"); supply nextOwnerSession explicitly or add preferred_targets to the role in the spec`, { instanceId: instance.instanceId, nextStepId: nextStep.id, nextRole: nextStep.actor_role });
|
|
271
|
+
}
|
|
272
|
+
resolvedNextOwner = owner;
|
|
114
273
|
}
|
|
115
|
-
resolvedNextOwner = owner;
|
|
116
274
|
}
|
|
117
275
|
// 2. R1 fix (guard blocker 1): close the current packet via Phase A's
|
|
118
276
|
// QueueRepository.updateWithinTransaction. This validates closure
|
|
@@ -120,10 +278,11 @@ export class WorkflowProjector {
|
|
|
120
278
|
// metadata (closure_reason, closure_target, handed_off_to / blocked_on),
|
|
121
279
|
// appends queue_transitions, and emits queue.updated — all inside this
|
|
122
280
|
// outer transaction. The Phase A closure authority is preserved.
|
|
123
|
-
const closure = workflowExitToQueueClosure(input, resolvedNextOwner);
|
|
281
|
+
const closure = workflowExitToQueueClosure({ ...input, exit: effectiveExit, resultNote: effectiveResultNote }, resolvedNextOwner);
|
|
124
282
|
const queueUpdate = this.queueRepo.updateWithinTransaction({
|
|
125
283
|
qitemId: input.currentPacketId,
|
|
126
284
|
actorSession: input.actorSession,
|
|
285
|
+
viaWorkflowVerb: true,
|
|
127
286
|
state: closure.state,
|
|
128
287
|
closureReason: closure.closureReason,
|
|
129
288
|
closureTarget: closure.closureTarget ?? undefined,
|
|
@@ -132,12 +291,55 @@ export class WorkflowProjector {
|
|
|
132
291
|
transitionNote: closure.transitionNote,
|
|
133
292
|
});
|
|
134
293
|
persistedEvents.push(queueUpdate.persistedEvent);
|
|
135
|
-
// 3. If
|
|
294
|
+
// 3. If this close routes (structural handoff OR branch-mapped
|
|
295
|
+
// exit): create the next-step qitem in the same txn. A gated
|
|
296
|
+
// target compiles to the gate item (FR-5) — the SHIPPED write
|
|
297
|
+
// path (tier/summary/evidence_ref) — instead of an ordinary
|
|
298
|
+
// step packet; the instance then PARKS (status ladder below).
|
|
136
299
|
let createdNext = null;
|
|
137
|
-
if (
|
|
300
|
+
if (routes && nextStep && resolvedNextOwner) {
|
|
138
301
|
nextOwnerSession = resolvedNextOwner;
|
|
139
302
|
nextStepId = nextStep.id;
|
|
303
|
+
// OPR.0.4.6.FAC1 (arch-endorsed): the ADDITIVE owner_resolution
|
|
304
|
+
// trail evidence — which tier resolved this routing decision
|
|
305
|
+
// (the branch_taken precedent; structured evidence JSON, NEVER
|
|
306
|
+
// closure_reason). Makes the determinism proofs readable and
|
|
307
|
+
// gives trace a rendering cell.
|
|
308
|
+
effectiveClosureEvidence = {
|
|
309
|
+
...(effectiveClosureEvidence ?? {}),
|
|
310
|
+
owner_resolution: {
|
|
311
|
+
mode: gateCompile
|
|
312
|
+
? "gate"
|
|
313
|
+
: input.nextOwnerSession
|
|
314
|
+
? "explicit"
|
|
315
|
+
: (spec.roles?.[nextStep.actor_role]?.preferred_targets ?? []).length > 0
|
|
316
|
+
? "preferred_targets"
|
|
317
|
+
: "role",
|
|
318
|
+
role: nextStep.actor_role,
|
|
319
|
+
...(instance.boundRig ? { boundRig: instance.boundRig } : {}),
|
|
320
|
+
seat: resolvedNextOwner,
|
|
321
|
+
},
|
|
322
|
+
};
|
|
323
|
+
// OPR.0.4.6.WF5 FR-1 class (c) (guard code-review fold): a HUMAN
|
|
324
|
+
// gate reach IS the exception, and the WF-2 compiled item IS the
|
|
325
|
+
// attention item (no second item ever) — so the class-(c)
|
|
326
|
+
// exception identity rides THIS packet's tags. The packet id is
|
|
327
|
+
// PREALLOCATED so occurrence:<gatePacketId> exists at create
|
|
328
|
+
// (identity queryable from birth — arch cell-2). Handler-role
|
|
329
|
+
// gates stay negative (deterministic handoffs, not exceptions).
|
|
330
|
+
const gateQitemId = gateCompile ? newQitemId() : undefined;
|
|
331
|
+
const gateException = gateCompile && gateQitemId
|
|
332
|
+
? classifyGateTrip({
|
|
333
|
+
workflowName: instance.workflowName,
|
|
334
|
+
instanceId: instance.instanceId,
|
|
335
|
+
gatedStepId: nextStep.id,
|
|
336
|
+
gateKind: gateCompile.kind,
|
|
337
|
+
gatePacketId: gateQitemId,
|
|
338
|
+
parkOn: gateCompile.parkOn,
|
|
339
|
+
})
|
|
340
|
+
: null;
|
|
140
341
|
createdNext = this.queueRepo.createWithinTransaction({
|
|
342
|
+
qitemId: gateQitemId,
|
|
141
343
|
sourceSession: input.actorSession,
|
|
142
344
|
destinationSession: resolvedNextOwner,
|
|
143
345
|
body: workflowHandoffBody({
|
|
@@ -146,11 +348,20 @@ export class WorkflowProjector {
|
|
|
146
348
|
currentStep,
|
|
147
349
|
nextStep,
|
|
148
350
|
actorSession: input.actorSession,
|
|
149
|
-
resultNote:
|
|
351
|
+
resultNote: effectiveResultNote,
|
|
352
|
+
gate: gateCompile,
|
|
150
353
|
}),
|
|
151
354
|
priority: "routine",
|
|
152
|
-
tier: "mode2",
|
|
153
|
-
tags: [
|
|
355
|
+
tier: gateCompile?.tier ?? "mode2",
|
|
356
|
+
tags: [
|
|
357
|
+
"workflow",
|
|
358
|
+
gateCompile ? "gate" : "handoff",
|
|
359
|
+
`workflow:${spec.id}`,
|
|
360
|
+
`instance:${instance.instanceId}`,
|
|
361
|
+
...(gateException ? workflowExceptionTags(gateException.identity).filter((t) => !t.startsWith("workflow:") && !t.startsWith("instance:")) : []),
|
|
362
|
+
],
|
|
363
|
+
summary: gateCompile?.summary ?? undefined,
|
|
364
|
+
evidenceRef: gateCompile?.evidenceRef ?? undefined,
|
|
154
365
|
chainOfRecord: [input.currentPacketId],
|
|
155
366
|
});
|
|
156
367
|
nextQitemId = createdNext.qitemId;
|
|
@@ -158,6 +369,34 @@ export class WorkflowProjector {
|
|
|
158
369
|
destinationSession: createdNext.destinationSession,
|
|
159
370
|
nudge: createdNext.nudge,
|
|
160
371
|
};
|
|
372
|
+
// OPR.0.4.6.WF2 FR-5 (guard blocker 1): a HUMAN gate packet PARKS
|
|
373
|
+
// in the same txn — state=blocked, blocked_on=<human seat> — the
|
|
374
|
+
// exact leg-1 shape the shipped `rig queue resolve` verb acts on
|
|
375
|
+
// (validateHumanPark enforces summary + evidence_ref here, carried
|
|
376
|
+
// from create). Resolve unparks it for the step owner to resume.
|
|
377
|
+
if (gateCompile?.parkOn) {
|
|
378
|
+
const parked = this.queueRepo.updateWithinTransaction({
|
|
379
|
+
qitemId: createdNext.qitemId,
|
|
380
|
+
actorSession: input.actorSession,
|
|
381
|
+
state: "blocked",
|
|
382
|
+
closureReason: "blocked_on",
|
|
383
|
+
closureTarget: gateCompile.parkOn,
|
|
384
|
+
blockedOn: gateCompile.parkOn,
|
|
385
|
+
transitionNote: `workflow gate: parked on ${gateCompile.parkOn} pending sign-off`,
|
|
386
|
+
});
|
|
387
|
+
persistedEvents.push(parked.persistedEvent);
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// OPR.0.4.6.WF1 FR-1 proof seam (ACK Rev-2, reviewer-approved
|
|
391
|
+
// shape): an env-gated hold INSIDE the transaction, between the
|
|
392
|
+
// queue close / next-qitem create and the trail append, giving
|
|
393
|
+
// the fr1-midtxn-process-kill VM proof a deterministic window to
|
|
394
|
+
// SIGKILL the daemon mid-transaction. Default off/absent = zero
|
|
395
|
+
// effect (test-only; never set in production). Synchronous by
|
|
396
|
+
// design — better-sqlite3 transactions are sync.
|
|
397
|
+
const holdMs = Number(process.env.OPENRIG_TEST_WF_TXN_HOLD_MS ?? 0);
|
|
398
|
+
if (holdMs > 0) {
|
|
399
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, holdMs);
|
|
161
400
|
}
|
|
162
401
|
// 4. Append step trail entry (prior + next ids).
|
|
163
402
|
this.trailLog.record({
|
|
@@ -165,8 +404,8 @@ export class WorkflowProjector {
|
|
|
165
404
|
stepId: currentStep.id,
|
|
166
405
|
stepRole: currentStep.actor_role,
|
|
167
406
|
closedAt: evaluatedAt,
|
|
168
|
-
closureReason:
|
|
169
|
-
closureEvidence:
|
|
407
|
+
closureReason: effectiveExit,
|
|
408
|
+
closureEvidence: effectiveClosureEvidence ?? null,
|
|
170
409
|
actorSession: input.actorSession,
|
|
171
410
|
nextQitemId,
|
|
172
411
|
priorQitemId: input.currentPacketId,
|
|
@@ -179,24 +418,37 @@ export class WorkflowProjector {
|
|
|
179
418
|
// owner to wake.
|
|
180
419
|
const remainingFrontier = instance.currentFrontier.filter((id) => id !== input.currentPacketId);
|
|
181
420
|
let nextFrontier;
|
|
182
|
-
if (
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
421
|
+
if (nextQitemId) {
|
|
422
|
+
// OPR.0.4.6.WF2: a ROUTED close (structural handoff OR a
|
|
423
|
+
// branch-mapped exit — including a mapped `waiting`) moves the
|
|
424
|
+
// frontier to the new packet. Checked FIRST so a mapped waiting
|
|
425
|
+
// routes instead of re-parking the closed packet.
|
|
187
426
|
nextFrontier = [...remainingFrontier, nextQitemId];
|
|
188
427
|
}
|
|
428
|
+
else if (effectiveExit === "waiting") {
|
|
429
|
+
// Unrouted waiting: keep the closed packet on frontier; the
|
|
430
|
+
// watchdog uses it to wake the owner.
|
|
431
|
+
nextFrontier = [...remainingFrontier, input.currentPacketId];
|
|
432
|
+
}
|
|
189
433
|
else {
|
|
190
434
|
nextFrontier = remainingFrontier;
|
|
191
435
|
}
|
|
192
436
|
// R1 fix (guard blocker 2a): exit=failed sets status=failed, NOT
|
|
193
437
|
// completed. Emits workflow.failed instead of workflow.completed.
|
|
438
|
+
// OPR.0.4.6.WF2: a ROUTED close keeps the instance ACTIVE bound to
|
|
439
|
+
// the route target (the branch execution contract) — UNLESS the
|
|
440
|
+
// target step is GATED, in which case the instance parks honestly
|
|
441
|
+
// (`waiting`) on the gate item until resolve/close continues it.
|
|
442
|
+
// Non-routed exits keep today's ladder byte-identically.
|
|
194
443
|
let nextStatus;
|
|
195
444
|
let completedAt = null;
|
|
196
|
-
if (
|
|
445
|
+
if (routes) {
|
|
446
|
+
nextStatus = gateCompile ? "waiting" : "active";
|
|
447
|
+
}
|
|
448
|
+
else if (effectiveExit === "waiting") {
|
|
197
449
|
nextStatus = "waiting";
|
|
198
450
|
}
|
|
199
|
-
else if (
|
|
451
|
+
else if (effectiveExit === "failed") {
|
|
200
452
|
nextStatus = "failed";
|
|
201
453
|
completedAt = evaluatedAt;
|
|
202
454
|
}
|
|
@@ -208,14 +460,14 @@ export class WorkflowProjector {
|
|
|
208
460
|
nextStatus = "active";
|
|
209
461
|
}
|
|
210
462
|
// R2 fix: set durable current_step_id transition.
|
|
211
|
-
// handoff
|
|
212
|
-
// waiting
|
|
213
|
-
// done / failed → clear (terminal)
|
|
463
|
+
// routed (handoff or branch) → route target step
|
|
464
|
+
// waiting (unrouted) → preserve (resume on same packet/step)
|
|
465
|
+
// done / failed (unrouted) → clear (terminal)
|
|
214
466
|
let currentStepIdUpdate;
|
|
215
|
-
if (
|
|
467
|
+
if (routes && nextStep) {
|
|
216
468
|
currentStepIdUpdate = nextStep.id;
|
|
217
469
|
}
|
|
218
|
-
else if (
|
|
470
|
+
else if (effectiveExit === "waiting") {
|
|
219
471
|
currentStepIdUpdate = "preserve";
|
|
220
472
|
}
|
|
221
473
|
else {
|
|
@@ -223,25 +475,57 @@ export class WorkflowProjector {
|
|
|
223
475
|
currentStepIdUpdate = "clear";
|
|
224
476
|
}
|
|
225
477
|
this.instanceStore.updateFrontier(instance.instanceId, nextFrontier, nextStatus, {
|
|
226
|
-
|
|
478
|
+
// A branch route IS an advance (arch PIN 2): it bumps the hop
|
|
479
|
+
// count and the FR-5 version guard under the identical
|
|
480
|
+
// discipline as the linear path.
|
|
481
|
+
bumpHopCount: routes,
|
|
227
482
|
lastContinuationDecision: {
|
|
228
|
-
exit:
|
|
483
|
+
exit: effectiveExit,
|
|
229
484
|
actorSession: input.actorSession,
|
|
230
485
|
closedPacket: input.currentPacketId,
|
|
231
486
|
nextPacket: nextQitemId,
|
|
232
|
-
resultNote:
|
|
487
|
+
resultNote: effectiveResultNote ?? null,
|
|
233
488
|
blockedOn: input.blockedOn ?? null,
|
|
234
489
|
currentStep: currentStep.id,
|
|
490
|
+
// OPR.0.4.6.WF2 FR-1 (arch PIN 1): ADDITIVE branch-taken
|
|
491
|
+
// record — null on every non-branch close.
|
|
492
|
+
branchTaken: routes && branchRouted && nextStep ? nextStep.id : null,
|
|
235
493
|
},
|
|
236
494
|
completedAt,
|
|
237
495
|
currentStepId: currentStepIdUpdate,
|
|
496
|
+
// FR-5: the optimistic-concurrency guard — this write commits
|
|
497
|
+
// only if no other writer advanced the instance since our read;
|
|
498
|
+
// a stale writer throws instance_version_conflict and the whole
|
|
499
|
+
// scribe transaction rolls back.
|
|
500
|
+
expectedVersion: instance.version,
|
|
238
501
|
});
|
|
502
|
+
// 5b. OPR.0.4.6.WF1 FR-3: keepalive arm/disarm INSIDE the scribe
|
|
503
|
+
// txn. A handoff ensures the per-instance job is armed (idempotent
|
|
504
|
+
// — also heals pre-WF-1 instances on their first post-upgrade
|
|
505
|
+
// hop); a terminal status disarms it (no orphaned watchdog
|
|
506
|
+
// noise). Waiting keeps the job armed — the keepalive wakes the
|
|
507
|
+
// parked owner. Plain INSERT/UPDATE, verified txn-composable.
|
|
508
|
+
if (this.watchdogJobsRepo) {
|
|
509
|
+
if (routes && resolvedNextOwner) {
|
|
510
|
+
// Any route arms the keepalive for the new owner — including a
|
|
511
|
+
// gate park (waiting keeps the job armed; it wakes the parked
|
|
512
|
+
// gate target exactly like any parked owner).
|
|
513
|
+
ensureWorkflowKeepaliveArmed(this.watchdogJobsRepo, {
|
|
514
|
+
instanceId: instance.instanceId,
|
|
515
|
+
targetSession: resolvedNextOwner,
|
|
516
|
+
registeredBySession: input.actorSession,
|
|
517
|
+
});
|
|
518
|
+
}
|
|
519
|
+
else if (nextStatus === "completed" || nextStatus === "failed") {
|
|
520
|
+
disarmWorkflowKeepalive(this.watchdogJobsRepo, instance.instanceId, `workflow_${nextStatus}`);
|
|
521
|
+
}
|
|
522
|
+
}
|
|
239
523
|
// 6. Persist workflow events within the same txn.
|
|
240
524
|
persistedEvents.push(this.eventBus.persistWithinTransaction({
|
|
241
525
|
type: "workflow.step_closed",
|
|
242
526
|
instanceId: instance.instanceId,
|
|
243
527
|
stepId: currentStep.id,
|
|
244
|
-
closureReason:
|
|
528
|
+
closureReason: effectiveExit,
|
|
245
529
|
actorSession: input.actorSession,
|
|
246
530
|
priorQitemId: input.currentPacketId,
|
|
247
531
|
}));
|
|
@@ -267,8 +551,77 @@ export class WorkflowProjector {
|
|
|
267
551
|
type: "workflow.failed",
|
|
268
552
|
instanceId: instance.instanceId,
|
|
269
553
|
workflowName: instance.workflowName,
|
|
270
|
-
reason:
|
|
554
|
+
reason: effectiveResultNote ?? "workflow_step_failed",
|
|
271
555
|
}));
|
|
556
|
+
// OPR.0.4.6.WF5 FR-2 class (a): the exception attention item is
|
|
557
|
+
// BORN IN THIS TRANSACTION — if this commit lands, the item
|
|
558
|
+
// exists; if anything throws, instance failure AND item roll
|
|
559
|
+
// back together. There is NO window where the instance is
|
|
560
|
+
// failed and no item exists (the load-bearing never-lost AC,
|
|
561
|
+
// target-independent at every dial position — hold-onto 1).
|
|
562
|
+
const exception = classifyFailedInstance({
|
|
563
|
+
instance: { ...instance, status: "failed" },
|
|
564
|
+
failedStepId: currentStep.id,
|
|
565
|
+
failedPacketId: input.currentPacketId,
|
|
566
|
+
failureReason: effectiveResultNote ?? null,
|
|
567
|
+
});
|
|
568
|
+
if (exception) {
|
|
569
|
+
const route = resolveExceptionRoute({
|
|
570
|
+
exceptionClass: exception.identity.exceptionClass,
|
|
571
|
+
spec,
|
|
572
|
+
hostDialDefault: this.exceptionDial?.hostDefault() ?? null,
|
|
573
|
+
// FAC-1 (arch Q3 — routing uniformity, bounded): declared
|
|
574
|
+
// preferred_targets stay the override; a bound instance's
|
|
575
|
+
// orchestrator-role dial position falls through to a
|
|
576
|
+
// NON-THROWING capability pick on the bound rig (null →
|
|
577
|
+
// the router's human@host never-lost fallback — exception
|
|
578
|
+
// routing never fails a close). Fresh decision per episode;
|
|
579
|
+
// not a replay concern.
|
|
580
|
+
resolveRoleTarget: (role) => spec.roles?.[role]?.preferred_targets?.[0] ??
|
|
581
|
+
tryResolveRoleByCapability(roleCtx, role),
|
|
582
|
+
humanFallbackSeat: this.exceptionDial?.humanFallbackSeat ?? "human@host",
|
|
583
|
+
});
|
|
584
|
+
const evidenceRef = `rig workflow trace ${instance.instanceId}`;
|
|
585
|
+
const itemBody = `WORKFLOW EXCEPTION (${exception.identity.exceptionClass})\n` +
|
|
586
|
+
`workflow: ${instance.workflowName} v${instance.workflowVersion}\n` +
|
|
587
|
+
`instance: ${instance.instanceId}\n` +
|
|
588
|
+
`step: ${currentStep.id} (role ${currentStep.actor_role})\n` +
|
|
589
|
+
`reason: ${exception.reason}\n` +
|
|
590
|
+
`evidence: ${evidenceRef}\n` +
|
|
591
|
+
`resolve: diagnose via the trace above, then \`rig workflow resume ${instance.instanceId} [--decision <text>]\` re-drives from this step (completed steps never re-run).`;
|
|
592
|
+
const createExceptionItem = (destination, tier) => this.queueRepo.createWithinTransaction({
|
|
593
|
+
sourceSession: input.actorSession,
|
|
594
|
+
destinationSession: destination,
|
|
595
|
+
body: itemBody,
|
|
596
|
+
priority: "urgent",
|
|
597
|
+
tier,
|
|
598
|
+
tags: workflowExceptionTags(exception.identity),
|
|
599
|
+
summary: exception.reason,
|
|
600
|
+
evidenceRef,
|
|
601
|
+
chainOfRecord: [input.currentPacketId],
|
|
602
|
+
});
|
|
603
|
+
let createdException;
|
|
604
|
+
try {
|
|
605
|
+
createdException = createExceptionItem(route.destinationSession, route.tier);
|
|
606
|
+
}
|
|
607
|
+
catch {
|
|
608
|
+
// THE NEVER-LOST WRITE-GATE FALLBACK: a routed destination the
|
|
609
|
+
// queue's destination gate rejects (e.g. a spec-declared
|
|
610
|
+
// target that is not a live session) must not lose the
|
|
611
|
+
// exception OR fail the close — re-route human@host (always
|
|
612
|
+
// validates: the destination gate special-cases human seats)
|
|
613
|
+
// with the human-routed tier. Any failure of THIS create is a
|
|
614
|
+
// real storage error and propagates: the whole close rolls
|
|
615
|
+
// back rather than committing an item-less failure.
|
|
616
|
+
createdException = createExceptionItem(this.exceptionDial?.humanFallbackSeat ?? "human@host", "human-gate");
|
|
617
|
+
}
|
|
618
|
+
persistedEvents.push(createdException.persistedEvent);
|
|
619
|
+
exceptionItemPostCommit = {
|
|
620
|
+
qitemId: createdException.qitemId,
|
|
621
|
+
destinationSession: createdException.destinationSession,
|
|
622
|
+
nudge: createdException.nudge,
|
|
623
|
+
};
|
|
624
|
+
}
|
|
272
625
|
}
|
|
273
626
|
});
|
|
274
627
|
txn();
|
|
@@ -276,6 +629,13 @@ export class WorkflowProjector {
|
|
|
276
629
|
for (const e of persistedEvents) {
|
|
277
630
|
this.eventBus.notifySubscribers(e);
|
|
278
631
|
}
|
|
632
|
+
// OPR.0.4.6.WF5 FR-2: best-effort nudge for the exception item (the
|
|
633
|
+
// shipped non-fatal delivery pattern — the durable item is the
|
|
634
|
+
// guarantee, the nudge is a courtesy).
|
|
635
|
+
const excPost = exceptionItemPostCommit;
|
|
636
|
+
if (excPost) {
|
|
637
|
+
await this.queueRepo.maybeNudge(excPost.qitemId, excPost.destinationSession, excPost.nudge);
|
|
638
|
+
}
|
|
279
639
|
const postCommit = nextQitemCreatePostCommit;
|
|
280
640
|
if (nextQitemId && postCommit) {
|
|
281
641
|
await this.queueRepo.maybeNudge(nextQitemId, postCommit.destinationSession, postCommit.nudge);
|
|
@@ -284,7 +644,7 @@ export class WorkflowProjector {
|
|
|
284
644
|
return {
|
|
285
645
|
instance: updatedInstance,
|
|
286
646
|
closurePriorPacketId: input.currentPacketId,
|
|
287
|
-
closureReason:
|
|
647
|
+
closureReason: effectiveExit,
|
|
288
648
|
nextQitemId,
|
|
289
649
|
nextOwnerSession,
|
|
290
650
|
nextStepId,
|
|
@@ -292,6 +652,61 @@ export class WorkflowProjector {
|
|
|
292
652
|
};
|
|
293
653
|
}
|
|
294
654
|
}
|
|
655
|
+
/**
|
|
656
|
+
* OPR.0.4.6.WF1 FR-5 (G-WF1-1, guard-ratified): the FULL
|
|
657
|
+
* CLOSURE-INTENT IDENTITY predicate for waiting-replay absorption.
|
|
658
|
+
* Every field that can change observable queue/trail/decision state
|
|
659
|
+
* must match the stored decision exactly:
|
|
660
|
+
* exit=waiting · closedPacket · currentStep · actorSession ·
|
|
661
|
+
* resultNote (null ≡ absent) · blockedOn (normalized to the
|
|
662
|
+
* EFFECTIVE blocker — the shipped `external-gate` default applied
|
|
663
|
+
* before comparison, workflowExitToQueueClosure:waiting) ·
|
|
664
|
+
* closureEvidence (deep-equal against the stored TRAIL row's
|
|
665
|
+
* evidence, since that is where evidence lands).
|
|
666
|
+
* Plus instance.status === waiting (checked by the caller).
|
|
667
|
+
*/
|
|
668
|
+
function matchesStoredWaitingDecision(instance, input, currentStepId, trailLog) {
|
|
669
|
+
const stored = instance.lastContinuationDecision;
|
|
670
|
+
if (!stored)
|
|
671
|
+
return false;
|
|
672
|
+
if (stored.exit !== "waiting")
|
|
673
|
+
return false;
|
|
674
|
+
if (stored.closedPacket !== input.currentPacketId)
|
|
675
|
+
return false;
|
|
676
|
+
if (stored.currentStep !== currentStepId)
|
|
677
|
+
return false;
|
|
678
|
+
if (stored.actorSession !== input.actorSession)
|
|
679
|
+
return false;
|
|
680
|
+
const storedNote = stored.resultNote ?? null;
|
|
681
|
+
const inputNote = input.resultNote ?? null;
|
|
682
|
+
if (storedNote !== inputNote)
|
|
683
|
+
return false;
|
|
684
|
+
const storedBlocker = (stored.blockedOn ?? null) ?? "external-gate";
|
|
685
|
+
const inputBlocker = input.blockedOn ?? "external-gate";
|
|
686
|
+
if (storedBlocker !== inputBlocker)
|
|
687
|
+
return false;
|
|
688
|
+
// closureEvidence lands in the trail row, not the decision — compare
|
|
689
|
+
// against the most recent waiting close of this exact packet.
|
|
690
|
+
const trail = trailLog.listForInstance(instance.instanceId);
|
|
691
|
+
const storedRow = trail.find((t) => t.priorQitemId === input.currentPacketId && t.closureReason === "waiting");
|
|
692
|
+
if (!storedRow)
|
|
693
|
+
return false;
|
|
694
|
+
return isDeepEqual(storedRow.closureEvidence ?? null, input.closureEvidence ?? null);
|
|
695
|
+
}
|
|
696
|
+
function isDeepEqual(a, b) {
|
|
697
|
+
if (a === b)
|
|
698
|
+
return true;
|
|
699
|
+
if (a === null || b === null || typeof a !== "object" || typeof b !== "object") {
|
|
700
|
+
return false;
|
|
701
|
+
}
|
|
702
|
+
if (Array.isArray(a) !== Array.isArray(b))
|
|
703
|
+
return false;
|
|
704
|
+
const aKeys = Object.keys(a);
|
|
705
|
+
const bKeys = Object.keys(b);
|
|
706
|
+
if (aKeys.length !== bKeys.length)
|
|
707
|
+
return false;
|
|
708
|
+
return aKeys.every((k) => isDeepEqual(a[k], b[k]));
|
|
709
|
+
}
|
|
295
710
|
/**
|
|
296
711
|
* R2 fix (guard blocker 1): resolve the current step from durable
|
|
297
712
|
* instance.currentStepId, NOT from trail order. The previous trail-based
|
|
@@ -320,10 +735,25 @@ function resolveCurrentStep(spec, instance) {
|
|
|
320
735
|
null);
|
|
321
736
|
}
|
|
322
737
|
/**
|
|
323
|
-
* Resolve the next step.
|
|
324
|
-
*
|
|
738
|
+
* Resolve the next step. OPR.0.4.6.WF2 FR-1: when a `recordedExit` is
|
|
739
|
+
* supplied AND the step's `next_hop.on` maps it, the MAPPED target wins
|
|
740
|
+
* (the deterministic branch — same (spec, step, exit) → same target,
|
|
741
|
+
* every time). Otherwise the structural default: the spec-authored
|
|
742
|
+
* `next_hop.suggested_roles` edge, then declaration order.
|
|
743
|
+
*
|
|
744
|
+
* EXPORTED (OPR.0.4.6.WF1 FR-7): this is THE single routing seam —
|
|
745
|
+
* the validator's reachability/cycle analysis runs over these exact
|
|
746
|
+
* semantics (never a parallel re-implementation). The validator calls
|
|
747
|
+
* it WITHOUT an exit for the structural default path and unions the
|
|
748
|
+
* branch edges explicitly — one routing truth, no semantic fork.
|
|
325
749
|
*/
|
|
326
|
-
function resolveNextStep(spec, currentStep) {
|
|
750
|
+
export function resolveNextStep(spec, currentStep, recordedExit) {
|
|
751
|
+
const mappedTargetId = recordedExit
|
|
752
|
+
? currentStep.next_hop?.on?.[recordedExit]
|
|
753
|
+
: undefined;
|
|
754
|
+
if (mappedTargetId !== undefined) {
|
|
755
|
+
return spec.steps.find((s) => s.id === mappedTargetId) ?? null;
|
|
756
|
+
}
|
|
327
757
|
if (currentStep.next_hop?.mode === "forbid")
|
|
328
758
|
return null;
|
|
329
759
|
for (const role of currentStep.next_hop?.suggested_roles ?? []) {
|
|
@@ -338,15 +768,113 @@ function resolveNextStep(spec, currentStep) {
|
|
|
338
768
|
return null;
|
|
339
769
|
return spec.steps[idx + 1] ?? null;
|
|
340
770
|
}
|
|
771
|
+
/**
|
|
772
|
+
* OPR.0.4.6.FAC1 — resolve a ROLE to a live capable seat on the bound
|
|
773
|
+
* rig, or throw LOUD-WITH-CANDIDATES (BR-5). The structured details
|
|
774
|
+
* carry every evaluated candidate with its named disqualifier
|
|
775
|
+
* (`not_live(lifecycleState=x)` / `runtime_mismatch(has≠needs)` /
|
|
776
|
+
* `role_not_declared` / `adopted_seat_not_role_resolvable_v1`) so the
|
|
777
|
+
* operator OR the WF-5 exception orchestrator can act; the
|
|
778
|
+
* zero-candidate case gets its own named message. Thrown BEFORE any
|
|
779
|
+
* side effect (the :199-221 validation-before-mutation discipline);
|
|
780
|
+
* never a spawn, never auto-add_member, never a dead-seat route.
|
|
781
|
+
*/
|
|
782
|
+
function resolveRoleOnBoundRig(roleName, harness, roleCtx, stepId) {
|
|
783
|
+
const candidates = roleCtx.candidatesForRig();
|
|
784
|
+
if (candidates === null) {
|
|
785
|
+
throw new WorkflowProjectorError("bound_rig_not_found", `cannot resolve role "${roleName}" for step "${stepId}": the instance's bound rig "${roleCtx.boundRig}" no longer resolves to a registered rig (torn down mid-run?). Recreate/import the rig under the same name, or instantiate a fresh run bound elsewhere.`, { stepId, role: roleName, boundRig: roleCtx.boundRig });
|
|
786
|
+
}
|
|
787
|
+
const result = selectRoleSeat({ role: roleName, harness, candidates });
|
|
788
|
+
if (result.seat)
|
|
789
|
+
return result.seat;
|
|
790
|
+
const declaring = result.disqualified.filter((c) => c.facts.role === roleName);
|
|
791
|
+
if (declaring.length === 0) {
|
|
792
|
+
throw new WorkflowProjectorError("next_owner_unresolved", `cannot resolve role "${roleName}" for step "${stepId}" (no seats on rig "${roleCtx.boundRig}" declare role "${roleName}"). Add a member with role ${roleName} to rig ${roleCtx.boundRig} via rig add, declare role on an existing member, or add preferred_targets to the role in the spec.`, {
|
|
793
|
+
stepId,
|
|
794
|
+
role: roleName,
|
|
795
|
+
boundRig: roleCtx.boundRig,
|
|
796
|
+
candidates: result.disqualified,
|
|
797
|
+
});
|
|
798
|
+
}
|
|
799
|
+
throw new WorkflowProjectorError("next_owner_unresolved", `cannot resolve a live capable seat for role "${roleName}" for step "${stepId}" on rig "${roleCtx.boundRig}". Candidates: ${declaring
|
|
800
|
+
.map((c) => `${c.coordinate ?? c.logicalId} → ${c.disqualifier}`)
|
|
801
|
+
.join(", ")}. Start/repair a role seat (see rig ps), add a member with role ${roleName} via rig add, or add preferred_targets to the role.`, {
|
|
802
|
+
stepId,
|
|
803
|
+
role: roleName,
|
|
804
|
+
boundRig: roleCtx.boundRig,
|
|
805
|
+
candidates: result.disqualified,
|
|
806
|
+
});
|
|
807
|
+
}
|
|
341
808
|
/**
|
|
342
809
|
* Resolve a default owner for a step. v1 picks the first declared
|
|
343
|
-
* preferred_target.
|
|
344
|
-
*
|
|
810
|
+
* preferred_target. OPR.0.4.6.WF2 FR-2: a step with a `harness:` pin
|
|
811
|
+
* picks the first preferred_target whose node runtime matches the pin
|
|
812
|
+
* instead; no match is a STRUCTURED routing failure naming the pin and
|
|
813
|
+
* each candidate's actual runtime — never a silent mis-route to the
|
|
814
|
+
* wrong harness. `runtimeOf` resolves a canonical session name to its
|
|
815
|
+
* node's runtime column (null = not a managed node / unknown).
|
|
816
|
+
*
|
|
817
|
+
* OPR.0.4.6.FAC1: gains the optional bound-rig context — see the tier
|
|
818
|
+
* comment in the body. Tier order (P2-6, guard-BLOCKING on deviation):
|
|
819
|
+
* 1. explicit input.nextOwnerSession (caller-reconciled)
|
|
820
|
+
* 2. gate compile (the caller branches there first)
|
|
821
|
+
* 3. declared preferred_targets — TODAY'S path byte-for-byte
|
|
822
|
+
* 4. NEW: role capability on the bound rig (no targets + bound only)
|
|
823
|
+
* 5. null → the shipped next_owner_unresolved at the caller.
|
|
824
|
+
*
|
|
825
|
+
* Returns null only for the unpinned no-targets UNBOUND case (caller
|
|
826
|
+
* must supply nextOwnerSession explicitly — unchanged v1 behavior).
|
|
345
827
|
*/
|
|
346
|
-
function resolveDefaultOwner(spec, step
|
|
828
|
+
export function resolveDefaultOwner(spec, step, runtimeOf,
|
|
829
|
+
/** OPR.0.4.6.FAC1: the bound-rig resolution context. Present only for
|
|
830
|
+
* BOUND instances at LIVE resolution sites (projection next-step,
|
|
831
|
+
* gate compile, entry, resume) — never at the eager instantiate loop
|
|
832
|
+
* (guard B1: no eager live resolution of future steps). */
|
|
833
|
+
roleCtx) {
|
|
347
834
|
const role = spec.roles?.[step.actor_role];
|
|
348
835
|
const targets = role?.preferred_targets ?? [];
|
|
349
|
-
|
|
836
|
+
// OPR.0.4.6.FAC1 TIER 3 (the ONLY new tier; order is sacred — P2-6):
|
|
837
|
+
// declared preferred_targets above stay TODAY'S code path
|
|
838
|
+
// byte-for-byte and are NEVER liveness/inventory-filtered. Capability
|
|
839
|
+
// resolution activates exclusively when the role declares ZERO
|
|
840
|
+
// targets AND the instance is bound to a rig. Unbound no-target
|
|
841
|
+
// behavior below stays byte-identical (null / "(none declared)").
|
|
842
|
+
if (targets.length === 0 && roleCtx) {
|
|
843
|
+
return resolveRoleOnBoundRig(step.actor_role, step.harness, roleCtx, step.id);
|
|
844
|
+
}
|
|
845
|
+
if (!step.harness)
|
|
846
|
+
return targets[0] ?? null;
|
|
847
|
+
if (!runtimeOf) {
|
|
848
|
+
// Defense-in-depth: a pinned step must never resolve without
|
|
849
|
+
// runtime awareness (that would be exactly the silent mis-route
|
|
850
|
+
// FR-2 forbids). Production call sites always supply runtimeOf.
|
|
851
|
+
throw new WorkflowProjectorError("harness_pin_unverifiable", `step "${step.id}" pins harness "${step.harness}" but no runtime lookup is available to reconcile it — refusing to resolve an owner blind.`, { stepId: step.id, harness: step.harness });
|
|
852
|
+
}
|
|
853
|
+
const candidates = targets.map((t) => ({ session: t, runtime: runtimeOf(t) }));
|
|
854
|
+
const match = candidates.find((c) => c.runtime === step.harness);
|
|
855
|
+
if (match)
|
|
856
|
+
return match.session;
|
|
857
|
+
throw new WorkflowProjectorError("harness_pin_unsatisfied", `step "${step.id}" pins harness "${step.harness}" but no preferred_target of role "${step.actor_role}" runs it. Candidates: ${candidates.length === 0
|
|
858
|
+
? "(none declared)"
|
|
859
|
+
: candidates.map((c) => `${c.session} → ${c.runtime ?? "unknown"}`).join(", ")}. Add a ${step.harness} seat to the role's preferred_targets, or change/remove the pin.`, {
|
|
860
|
+
stepId: step.id,
|
|
861
|
+
harness: step.harness,
|
|
862
|
+
candidates: candidates.map((c) => ({ session: c.session, runtime: c.runtime })),
|
|
863
|
+
});
|
|
864
|
+
}
|
|
865
|
+
/**
|
|
866
|
+
* OPR.0.4.6.WF2 FR-2: reconcile an EXPLICIT owner override against a
|
|
867
|
+
* step's harness pin. An operator-supplied owner whose runtime does not
|
|
868
|
+
* match the pin is rejected loud — an explicit override must never
|
|
869
|
+
* silently defeat a declared pin.
|
|
870
|
+
*/
|
|
871
|
+
export function reconcileExplicitOwnerHarness(step, explicitOwner, runtimeOf) {
|
|
872
|
+
if (!step.harness)
|
|
873
|
+
return;
|
|
874
|
+
const actual = runtimeOf(explicitOwner);
|
|
875
|
+
if (actual !== step.harness) {
|
|
876
|
+
throw new WorkflowProjectorError("harness_pin_unsatisfied", `step "${step.id}" pins harness "${step.harness}" but the explicitly supplied owner ${explicitOwner} runs ${actual ?? "unknown (not a managed node)"}. Supply a ${step.harness} seat or change/remove the pin.`, { stepId: step.id, harness: step.harness, explicitOwner, actualRuntime: actual });
|
|
877
|
+
}
|
|
350
878
|
}
|
|
351
879
|
/**
|
|
352
880
|
* R1 fix (guard blocker 1): translate a workflow exit into the Phase A
|
|
@@ -413,14 +941,117 @@ function workflowExitToQueueClosure(input, resolvedNextOwner) {
|
|
|
413
941
|
}
|
|
414
942
|
}
|
|
415
943
|
}
|
|
944
|
+
export function compileGate(spec, gatedStep, runtimeOf,
|
|
945
|
+
/** OPR.0.4.6.FAC1: bound-rig context — threads capability resolution
|
|
946
|
+
* into BOTH gate paths (guard B3: the human-gate OWNER path and the
|
|
947
|
+
* handler-role DESTINATION branch are different code paths and each
|
|
948
|
+
* must carry it). Absent = shipped behavior byte-identical. */
|
|
949
|
+
roleCtx) {
|
|
950
|
+
const gate = gatedStep.gate;
|
|
951
|
+
if (!gate) {
|
|
952
|
+
throw new WorkflowProjectorError("gate_missing", `compileGate called for step "${gatedStep.id}" which declares no gate`, { stepId: gatedStep.id });
|
|
953
|
+
}
|
|
954
|
+
if (isHumanSeatSession(gate.target)) {
|
|
955
|
+
// Defense-in-depth: the validator requires these at validate time;
|
|
956
|
+
// the shipped human-park write path (validateHumanPark at the
|
|
957
|
+
// blocked_on transition) enforces them again at park.
|
|
958
|
+
if (!gate.summary || !gate.evidence_ref) {
|
|
959
|
+
throw new WorkflowProjectorError("gate_human_fields_missing", `step "${gatedStep.id}" gates on human seat ${gate.target} but is missing ${!gate.summary ? "summary" : "evidence_ref"} — a human-parked item requires both (the shipped human-route contract).`, { stepId: gatedStep.id, target: gate.target });
|
|
960
|
+
}
|
|
961
|
+
// The packet belongs to the gated step's ROLE OWNER (the seat that
|
|
962
|
+
// does the work once the human signs off) and PARKS blocked_on the
|
|
963
|
+
// human seat — the exact leg-1 shape `rig queue resolve` unparks.
|
|
964
|
+
// FAC-1 (guard B3 row 2a): the owner resolves through the full tier
|
|
965
|
+
// stack — a bound instance's role-only gated step gets a rig-local
|
|
966
|
+
// owner by capability.
|
|
967
|
+
const owner = resolveDefaultOwner(spec, gatedStep, runtimeOf, roleCtx);
|
|
968
|
+
if (!owner) {
|
|
969
|
+
throw new WorkflowProjectorError("gate_owner_unresolved", `step "${gatedStep.id}" gates on human seat ${gate.target} but its role "${gatedStep.actor_role}" declares no preferred_targets — the parked packet has no owner to resume after resolve. Add preferred_targets to the role.`, { stepId: gatedStep.id, target: gate.target, role: gatedStep.actor_role });
|
|
970
|
+
}
|
|
971
|
+
return {
|
|
972
|
+
destinationSession: owner,
|
|
973
|
+
tier: undefined,
|
|
974
|
+
summary: gate.summary,
|
|
975
|
+
evidenceRef: gate.evidence_ref,
|
|
976
|
+
kind: "human",
|
|
977
|
+
parkOn: gate.target,
|
|
978
|
+
};
|
|
979
|
+
}
|
|
980
|
+
const role = spec.roles?.[gate.target];
|
|
981
|
+
if (!role) {
|
|
982
|
+
throw new WorkflowProjectorError("gate_target_unresolved", `step "${gatedStep.id}" gates on "${gate.target}" which is neither a human seat session nor a role declared in workflow.roles. Declare the role or use a human seat session (human@kernel form).`, { stepId: gatedStep.id, target: gate.target });
|
|
983
|
+
}
|
|
984
|
+
const handlerTargets = role.preferred_targets ?? [];
|
|
985
|
+
if (handlerTargets.length === 0) {
|
|
986
|
+
// OPR.0.4.6.FAC1 (guard B3 row 2b — the branch that would silently
|
|
987
|
+
// stay old-style if unthreaded): declared preferred_targets remain
|
|
988
|
+
// the override tier byte-identically; when the handler role has NO
|
|
989
|
+
// targets AND the instance is bound, the handler seat resolves by
|
|
990
|
+
// capability on the bound rig (the step's harness pin binds the
|
|
991
|
+
// ACTUAL routed destination — the WF-2 rev1-r2 contract); otherwise
|
|
992
|
+
// the shipped loud failure stands.
|
|
993
|
+
if (roleCtx) {
|
|
994
|
+
const seat = resolveRoleOnBoundRig(gate.target, gatedStep.harness, roleCtx, gatedStep.id);
|
|
995
|
+
return {
|
|
996
|
+
destinationSession: seat,
|
|
997
|
+
tier: undefined,
|
|
998
|
+
summary: gate.summary ?? null,
|
|
999
|
+
evidenceRef: gate.evidence_ref ?? null,
|
|
1000
|
+
kind: "handler-role",
|
|
1001
|
+
parkOn: null,
|
|
1002
|
+
};
|
|
1003
|
+
}
|
|
1004
|
+
throw new WorkflowProjectorError("gate_handler_unresolved", `step "${gatedStep.id}" gates on handler role "${gate.target}" but that role declares no preferred_targets — the gate item has no seat to route to. Add preferred_targets to the role.`, { stepId: gatedStep.id, target: gate.target });
|
|
1005
|
+
}
|
|
1006
|
+
// OPR.0.4.6.WF2 (rev1-r2 blocker): the step's harness pin binds the
|
|
1007
|
+
// packet's ACTUAL routed destination — for a handler gate, the handler
|
|
1008
|
+
// seat. FR-2's contract ("never a silent mis-route to the wrong
|
|
1009
|
+
// harness") admits no gated bypass: a pinned gated step routes to the
|
|
1010
|
+
// first handler preferred_target whose node runtime matches, or fails
|
|
1011
|
+
// structured naming the pin and every candidate's runtime.
|
|
1012
|
+
let seat;
|
|
1013
|
+
if (gatedStep.harness) {
|
|
1014
|
+
const candidates = handlerTargets.map((t) => ({ session: t, runtime: runtimeOf(t) }));
|
|
1015
|
+
const match = candidates.find((c) => c.runtime === gatedStep.harness);
|
|
1016
|
+
if (!match) {
|
|
1017
|
+
throw new WorkflowProjectorError("harness_pin_unsatisfied", `step "${gatedStep.id}" pins harness "${gatedStep.harness}" and gates on handler role "${gate.target}", but no preferred_target of that role runs it. Candidates: ${candidates.map((c) => `${c.session} → ${c.runtime ?? "unknown"}`).join(", ")}. Add a ${gatedStep.harness} seat to the handler role's preferred_targets, or change/remove the pin.`, {
|
|
1018
|
+
stepId: gatedStep.id,
|
|
1019
|
+
harness: gatedStep.harness,
|
|
1020
|
+
gateTarget: gate.target,
|
|
1021
|
+
candidates: candidates.map((c) => ({ session: c.session, runtime: c.runtime })),
|
|
1022
|
+
});
|
|
1023
|
+
}
|
|
1024
|
+
seat = match.session;
|
|
1025
|
+
}
|
|
1026
|
+
else {
|
|
1027
|
+
seat = handlerTargets[0];
|
|
1028
|
+
}
|
|
1029
|
+
return {
|
|
1030
|
+
destinationSession: seat,
|
|
1031
|
+
tier: undefined,
|
|
1032
|
+
summary: gate.summary ?? null,
|
|
1033
|
+
evidenceRef: gate.evidence_ref ?? null,
|
|
1034
|
+
kind: "handler-role",
|
|
1035
|
+
parkOn: null,
|
|
1036
|
+
};
|
|
1037
|
+
}
|
|
416
1038
|
function workflowHandoffBody(input) {
|
|
417
1039
|
const lines = [
|
|
418
|
-
|
|
1040
|
+
input.gate
|
|
1041
|
+
? `### Workflow gate: ${input.spec.id}@${input.spec.version} step ${input.nextStep.id}`
|
|
1042
|
+
: `### Workflow handoff: ${input.spec.id}@${input.spec.version} step ${input.nextStep.id}`,
|
|
419
1043
|
"",
|
|
420
1044
|
`Workflow instance: ${input.instance.instanceId}`,
|
|
421
1045
|
`Prior step: ${input.currentStep.id} (${input.currentStep.actor_role}) closed by ${input.actorSession}`,
|
|
422
1046
|
`This step: ${input.nextStep.id} (${input.nextStep.actor_role})`,
|
|
423
1047
|
];
|
|
1048
|
+
if (input.gate) {
|
|
1049
|
+
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.`);
|
|
1050
|
+
if (input.gate.summary)
|
|
1051
|
+
lines.push(`Ask: ${input.gate.summary}`);
|
|
1052
|
+
if (input.gate.evidenceRef)
|
|
1053
|
+
lines.push(`Evidence: ${input.gate.evidenceRef}`);
|
|
1054
|
+
}
|
|
424
1055
|
if (input.nextStep.objective) {
|
|
425
1056
|
lines.push(`Objective: ${input.nextStep.objective}`);
|
|
426
1057
|
}
|