oh-my-codex 0.21.0 → 0.21.2
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/Cargo.toml +1 -1
- package/README.md +4 -2
- package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.d.ts +2 -0
- package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.d.ts.map +1 -0
- package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.js +592 -0
- package/dist/cli/__tests__/detached-launch-diagnostics-3578.test.js.map +1 -0
- package/dist/cli/__tests__/detached-leader-teardown.test.js +270 -33
- package/dist/cli/__tests__/detached-leader-teardown.test.js.map +1 -1
- package/dist/cli/__tests__/doctor-warning-copy.test.js +133 -5
- package/dist/cli/__tests__/doctor-warning-copy.test.js.map +1 -1
- package/dist/cli/__tests__/index.test.js +543 -2
- package/dist/cli/__tests__/index.test.js.map +1 -1
- package/dist/cli/__tests__/issue-windows-cancel-identity.test.d.ts +2 -0
- package/dist/cli/__tests__/issue-windows-cancel-identity.test.d.ts.map +1 -0
- package/dist/cli/__tests__/issue-windows-cancel-identity.test.js +17 -0
- package/dist/cli/__tests__/issue-windows-cancel-identity.test.js.map +1 -0
- package/dist/cli/__tests__/launch-fallback.test.js +15 -3
- package/dist/cli/__tests__/launch-fallback.test.js.map +1 -1
- package/dist/cli/__tests__/package-bin-contract.test.js +20 -0
- package/dist/cli/__tests__/package-bin-contract.test.js.map +1 -1
- package/dist/cli/__tests__/plugin-cache-stale-lock.test.d.ts +2 -0
- package/dist/cli/__tests__/plugin-cache-stale-lock.test.d.ts.map +1 -0
- package/dist/cli/__tests__/plugin-cache-stale-lock.test.js +76 -0
- package/dist/cli/__tests__/plugin-cache-stale-lock.test.js.map +1 -0
- package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.d.ts +2 -0
- package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.d.ts.map +1 -0
- package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js +1662 -0
- package/dist/cli/__tests__/plugin-cache-symlink-provenance-3552.test.js.map +1 -0
- package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.d.ts +2 -0
- package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.d.ts.map +1 -0
- package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.js +522 -0
- package/dist/cli/__tests__/plugin-launcher-provenance-3558.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan-advisory.test.d.ts +2 -0
- package/dist/cli/__tests__/ralplan-advisory.test.d.ts.map +1 -0
- package/dist/cli/__tests__/ralplan-advisory.test.js +301 -0
- package/dist/cli/__tests__/ralplan-advisory.test.js.map +1 -0
- package/dist/cli/__tests__/ralplan.test.js +39 -0
- package/dist/cli/__tests__/ralplan.test.js.map +1 -1
- package/dist/cli/__tests__/resume.test.js +81 -6
- package/dist/cli/__tests__/resume.test.js.map +1 -1
- package/dist/cli/__tests__/session-scoped-runtime.test.js +423 -4
- package/dist/cli/__tests__/session-scoped-runtime.test.js.map +1 -1
- package/dist/cli/__tests__/setup-install-mode.test.js +60 -3
- package/dist/cli/__tests__/setup-install-mode.test.js.map +1 -1
- package/dist/cli/__tests__/setup-refresh.test.js +9 -0
- package/dist/cli/__tests__/setup-refresh.test.js.map +1 -1
- package/dist/cli/__tests__/sparkshell-cli.test.js +4 -0
- package/dist/cli/__tests__/sparkshell-cli.test.js.map +1 -1
- package/dist/cli/__tests__/ultragoal.test.js +71 -0
- package/dist/cli/__tests__/ultragoal.test.js.map +1 -1
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js +30 -4
- package/dist/cli/__tests__/windows-popup-loop-contract.test.js.map +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +57 -8
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +113 -7
- package/dist/cli/index.d.ts.map +1 -1
- package/dist/cli/index.js +1850 -185
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/native-assets.d.ts +2 -1
- package/dist/cli/native-assets.d.ts.map +1 -1
- package/dist/cli/native-assets.js +7 -7
- package/dist/cli/native-assets.js.map +1 -1
- package/dist/cli/plugin-marketplace.d.ts +89 -4
- package/dist/cli/plugin-marketplace.d.ts.map +1 -1
- package/dist/cli/plugin-marketplace.js +2135 -118
- package/dist/cli/plugin-marketplace.js.map +1 -1
- package/dist/cli/ralplan.d.ts +2 -1
- package/dist/cli/ralplan.d.ts.map +1 -1
- package/dist/cli/ralplan.js +149 -9
- package/dist/cli/ralplan.js.map +1 -1
- package/dist/cli/setup.d.ts.map +1 -1
- package/dist/cli/setup.js +7 -1
- package/dist/cli/setup.js.map +1 -1
- package/dist/cli/sparkshell.d.ts.map +1 -1
- package/dist/cli/sparkshell.js +9 -10
- package/dist/cli/sparkshell.js.map +1 -1
- package/dist/cli/update-worker.js +9 -0
- package/dist/cli/update-worker.js.map +1 -1
- package/dist/cli/update.d.ts +1 -0
- package/dist/cli/update.d.ts.map +1 -1
- package/dist/cli/update.js +9 -0
- package/dist/cli/update.js.map +1 -1
- package/dist/config/__tests__/generator-idempotent.test.js +118 -0
- package/dist/config/__tests__/generator-idempotent.test.js.map +1 -1
- package/dist/config/generator.d.ts.map +1 -1
- package/dist/config/generator.js +74 -4
- package/dist/config/generator.js.map +1 -1
- package/dist/hooks/__tests__/keyword-detector.test.js +74 -0
- package/dist/hooks/__tests__/keyword-detector.test.js.map +1 -1
- package/dist/hooks/__tests__/ralplan-advisory-keyword.test.d.ts +2 -0
- package/dist/hooks/__tests__/ralplan-advisory-keyword.test.d.ts.map +1 -0
- package/dist/hooks/__tests__/ralplan-advisory-keyword.test.js +250 -0
- package/dist/hooks/__tests__/ralplan-advisory-keyword.test.js.map +1 -0
- package/dist/hooks/__tests__/session.test.js +10 -1
- package/dist/hooks/__tests__/session.test.js.map +1 -1
- package/dist/hooks/keyword-detector.d.ts +8 -1
- package/dist/hooks/keyword-detector.d.ts.map +1 -1
- package/dist/hooks/keyword-detector.js +130 -9
- package/dist/hooks/keyword-detector.js.map +1 -1
- package/dist/hooks/session.d.ts +5 -0
- package/dist/hooks/session.d.ts.map +1 -1
- package/dist/hooks/session.js +17 -3
- package/dist/hooks/session.js.map +1 -1
- package/dist/hud/__tests__/index.test.js +16 -1
- package/dist/hud/__tests__/index.test.js.map +1 -1
- package/dist/hud/__tests__/reconcile.test.js +134 -7
- package/dist/hud/__tests__/reconcile.test.js.map +1 -1
- package/dist/hud/__tests__/render.test.js +57 -0
- package/dist/hud/__tests__/render.test.js.map +1 -1
- package/dist/hud/__tests__/session-attached.test.d.ts +2 -0
- package/dist/hud/__tests__/session-attached.test.d.ts.map +1 -0
- package/dist/hud/__tests__/session-attached.test.js +82 -0
- package/dist/hud/__tests__/session-attached.test.js.map +1 -0
- package/dist/hud/__tests__/state.test.js +94 -2
- package/dist/hud/__tests__/state.test.js.map +1 -1
- package/dist/hud/__tests__/tmux-split-realtmux.test.js +169 -2
- package/dist/hud/__tests__/tmux-split-realtmux.test.js.map +1 -1
- package/dist/hud/__tests__/tmux.test.js +104 -22
- package/dist/hud/__tests__/tmux.test.js.map +1 -1
- package/dist/hud/__tests__/types.test.js +9 -0
- package/dist/hud/__tests__/types.test.js.map +1 -1
- package/dist/hud/__tests__/watch-detached.test.d.ts +2 -0
- package/dist/hud/__tests__/watch-detached.test.d.ts.map +1 -0
- package/dist/hud/__tests__/watch-detached.test.js +383 -0
- package/dist/hud/__tests__/watch-detached.test.js.map +1 -0
- package/dist/hud/__tests__/watch-realtmux.test.d.ts +2 -0
- package/dist/hud/__tests__/watch-realtmux.test.d.ts.map +1 -0
- package/dist/hud/__tests__/watch-realtmux.test.js +322 -0
- package/dist/hud/__tests__/watch-realtmux.test.js.map +1 -0
- package/dist/hud/index.d.ts +3 -0
- package/dist/hud/index.d.ts.map +1 -1
- package/dist/hud/index.js +98 -20
- package/dist/hud/index.js.map +1 -1
- package/dist/hud/reconcile.d.ts.map +1 -1
- package/dist/hud/reconcile.js +68 -24
- package/dist/hud/reconcile.js.map +1 -1
- package/dist/hud/render.d.ts.map +1 -1
- package/dist/hud/render.js +20 -0
- package/dist/hud/render.js.map +1 -1
- package/dist/hud/session-attached.d.ts +15 -0
- package/dist/hud/session-attached.d.ts.map +1 -0
- package/dist/hud/session-attached.js +47 -0
- package/dist/hud/session-attached.js.map +1 -0
- package/dist/hud/state.d.ts +9 -1
- package/dist/hud/state.d.ts.map +1 -1
- package/dist/hud/state.js +140 -3
- package/dist/hud/state.js.map +1 -1
- package/dist/hud/tmux.d.ts +4 -0
- package/dist/hud/tmux.d.ts.map +1 -1
- package/dist/hud/tmux.js +86 -23
- package/dist/hud/tmux.js.map +1 -1
- package/dist/hud/types.d.ts +20 -0
- package/dist/hud/types.d.ts.map +1 -1
- package/dist/hud/types.js +3 -0
- package/dist/hud/types.js.map +1 -1
- package/dist/modes/base.d.ts +15 -2
- package/dist/modes/base.d.ts.map +1 -1
- package/dist/modes/base.js +105 -16
- package/dist/modes/base.js.map +1 -1
- package/dist/question/autopilot-wait.js +2 -2
- package/dist/question/autopilot-wait.js.map +1 -1
- package/dist/question/deep-interview.js +2 -2
- package/dist/question/deep-interview.js.map +1 -1
- package/dist/ralplan/__tests__/advisory-activation.test.d.ts +2 -0
- package/dist/ralplan/__tests__/advisory-activation.test.d.ts.map +1 -0
- package/dist/ralplan/__tests__/advisory-activation.test.js +700 -0
- package/dist/ralplan/__tests__/advisory-activation.test.js.map +1 -0
- package/dist/ralplan/__tests__/advisory-detached-transition.test.d.ts +2 -0
- package/dist/ralplan/__tests__/advisory-detached-transition.test.d.ts.map +1 -0
- package/dist/ralplan/__tests__/advisory-detached-transition.test.js +88 -0
- package/dist/ralplan/__tests__/advisory-detached-transition.test.js.map +1 -0
- package/dist/ralplan/__tests__/advisory.test.d.ts +2 -0
- package/dist/ralplan/__tests__/advisory.test.d.ts.map +1 -0
- package/dist/ralplan/__tests__/advisory.test.js +1087 -0
- package/dist/ralplan/__tests__/advisory.test.js.map +1 -0
- package/dist/ralplan/__tests__/runtime.test.js +416 -7
- package/dist/ralplan/__tests__/runtime.test.js.map +1 -1
- package/dist/ralplan/advisory-activation-verifier.d.ts +19 -0
- package/dist/ralplan/advisory-activation-verifier.d.ts.map +1 -0
- package/dist/ralplan/advisory-activation-verifier.js +77 -0
- package/dist/ralplan/advisory-activation-verifier.js.map +1 -0
- package/dist/ralplan/advisory-activation.d.ts +34 -0
- package/dist/ralplan/advisory-activation.d.ts.map +1 -0
- package/dist/ralplan/advisory-activation.js +205 -0
- package/dist/ralplan/advisory-activation.js.map +1 -0
- package/dist/ralplan/advisory-contract.d.ts +103 -0
- package/dist/ralplan/advisory-contract.d.ts.map +1 -0
- package/dist/ralplan/advisory-contract.js +83 -0
- package/dist/ralplan/advisory-contract.js.map +1 -0
- package/dist/ralplan/advisory-detached-transition.d.ts +11 -0
- package/dist/ralplan/advisory-detached-transition.d.ts.map +1 -0
- package/dist/ralplan/advisory-detached-transition.js +21 -0
- package/dist/ralplan/advisory-detached-transition.js.map +1 -0
- package/dist/ralplan/advisory-evidence.d.ts +91 -0
- package/dist/ralplan/advisory-evidence.d.ts.map +1 -0
- package/dist/ralplan/advisory-evidence.js +393 -0
- package/dist/ralplan/advisory-evidence.js.map +1 -0
- package/dist/ralplan/advisory-lifecycle-validation.d.ts +11 -0
- package/dist/ralplan/advisory-lifecycle-validation.d.ts.map +1 -0
- package/dist/ralplan/advisory-lifecycle-validation.js +72 -0
- package/dist/ralplan/advisory-lifecycle-validation.js.map +1 -0
- package/dist/ralplan/advisory-postconditions.d.ts +3 -0
- package/dist/ralplan/advisory-postconditions.d.ts.map +1 -0
- package/dist/ralplan/advisory-postconditions.js +33 -0
- package/dist/ralplan/advisory-postconditions.js.map +1 -0
- package/dist/ralplan/advisory-recovery-journal.d.ts +56 -0
- package/dist/ralplan/advisory-recovery-journal.d.ts.map +1 -0
- package/dist/ralplan/advisory-recovery-journal.js +136 -0
- package/dist/ralplan/advisory-recovery-journal.js.map +1 -0
- package/dist/ralplan/advisory-storage.d.ts +24 -0
- package/dist/ralplan/advisory-storage.d.ts.map +1 -0
- package/dist/ralplan/advisory-storage.js +106 -0
- package/dist/ralplan/advisory-storage.js.map +1 -0
- package/dist/ralplan/advisory.d.ts +99 -0
- package/dist/ralplan/advisory.d.ts.map +1 -0
- package/dist/ralplan/advisory.js +914 -0
- package/dist/ralplan/advisory.js.map +1 -0
- package/dist/ralplan/documented-leader-preflight.d.ts +8 -0
- package/dist/ralplan/documented-leader-preflight.d.ts.map +1 -1
- package/dist/ralplan/documented-leader-preflight.js +60 -2
- package/dist/ralplan/documented-leader-preflight.js.map +1 -1
- package/dist/ralplan/runtime-advisory-lifecycle.d.ts +74 -0
- package/dist/ralplan/runtime-advisory-lifecycle.d.ts.map +1 -0
- package/dist/ralplan/runtime-advisory-lifecycle.js +352 -0
- package/dist/ralplan/runtime-advisory-lifecycle.js.map +1 -0
- package/dist/ralplan/runtime-contract.d.ts +126 -0
- package/dist/ralplan/runtime-contract.d.ts.map +1 -0
- package/dist/ralplan/runtime-contract.js +2 -0
- package/dist/ralplan/runtime-contract.js.map +1 -0
- package/dist/ralplan/runtime.d.ts +6 -109
- package/dist/ralplan/runtime.d.ts.map +1 -1
- package/dist/ralplan/runtime.js +174 -56
- package/dist/ralplan/runtime.js.map +1 -1
- package/dist/scripts/__tests__/postinstall.test.js +26 -0
- package/dist/scripts/__tests__/postinstall.test.js.map +1 -1
- package/dist/scripts/__tests__/ralplan-advisory-hook.test.d.ts +2 -0
- package/dist/scripts/__tests__/ralplan-advisory-hook.test.d.ts.map +1 -0
- package/dist/scripts/__tests__/ralplan-advisory-hook.test.js +299 -0
- package/dist/scripts/__tests__/ralplan-advisory-hook.test.js.map +1 -0
- package/dist/scripts/__tests__/smoke-packed-install.test.js +102 -8
- package/dist/scripts/__tests__/smoke-packed-install.test.js.map +1 -1
- package/dist/scripts/codex-native-hook.d.ts +20 -0
- package/dist/scripts/codex-native-hook.d.ts.map +1 -1
- package/dist/scripts/codex-native-hook.js +226 -15
- package/dist/scripts/codex-native-hook.js.map +1 -1
- package/dist/scripts/postinstall.d.ts +7 -0
- package/dist/scripts/postinstall.d.ts.map +1 -1
- package/dist/scripts/postinstall.js +40 -0
- package/dist/scripts/postinstall.js.map +1 -1
- package/dist/scripts/smoke-packed-install.d.ts +21 -5
- package/dist/scripts/smoke-packed-install.d.ts.map +1 -1
- package/dist/scripts/smoke-packed-install.js +157 -57
- package/dist/scripts/smoke-packed-install.js.map +1 -1
- package/dist/state/__tests__/mode-binding-lease.test.d.ts +2 -0
- package/dist/state/__tests__/mode-binding-lease.test.d.ts.map +1 -0
- package/dist/state/__tests__/mode-binding-lease.test.js +498 -0
- package/dist/state/__tests__/mode-binding-lease.test.js.map +1 -0
- package/dist/state/__tests__/operations.test.js +1 -0
- package/dist/state/__tests__/operations.test.js.map +1 -1
- package/dist/state/__tests__/pinned-atomic-file.test.d.ts +2 -0
- package/dist/state/__tests__/pinned-atomic-file.test.d.ts.map +1 -0
- package/dist/state/__tests__/pinned-atomic-file.test.js +438 -0
- package/dist/state/__tests__/pinned-atomic-file.test.js.map +1 -0
- package/dist/state/__tests__/skill-active-exact-session-transaction.test.d.ts +2 -0
- package/dist/state/__tests__/skill-active-exact-session-transaction.test.d.ts.map +1 -0
- package/dist/state/__tests__/skill-active-exact-session-transaction.test.js +179 -0
- package/dist/state/__tests__/skill-active-exact-session-transaction.test.js.map +1 -0
- package/dist/state/__tests__/skill-active.test.js +29 -1
- package/dist/state/__tests__/skill-active.test.js.map +1 -1
- package/dist/state/mode-binding-lease-helper.d.ts +2 -0
- package/dist/state/mode-binding-lease-helper.d.ts.map +1 -0
- package/dist/state/mode-binding-lease-helper.js +623 -0
- package/dist/state/mode-binding-lease-helper.js.map +1 -0
- package/dist/state/mode-binding-lease.d.ts +25 -0
- package/dist/state/mode-binding-lease.d.ts.map +1 -0
- package/dist/state/mode-binding-lease.js +380 -0
- package/dist/state/mode-binding-lease.js.map +1 -0
- package/dist/state/operations.d.ts +29 -1
- package/dist/state/operations.d.ts.map +1 -1
- package/dist/state/operations.js +203 -122
- package/dist/state/operations.js.map +1 -1
- package/dist/state/pinned-atomic-file-darwin-client.d.ts +34 -0
- package/dist/state/pinned-atomic-file-darwin-client.d.ts.map +1 -0
- package/dist/state/pinned-atomic-file-darwin-client.js +174 -0
- package/dist/state/pinned-atomic-file-darwin-client.js.map +1 -0
- package/dist/state/pinned-atomic-file-darwin-helper.d.ts +2 -0
- package/dist/state/pinned-atomic-file-darwin-helper.d.ts.map +1 -0
- package/dist/state/pinned-atomic-file-darwin-helper.js +275 -0
- package/dist/state/pinned-atomic-file-darwin-helper.js.map +1 -0
- package/dist/state/pinned-atomic-file-darwin-janitor.d.ts +2 -0
- package/dist/state/pinned-atomic-file-darwin-janitor.d.ts.map +1 -0
- package/dist/state/pinned-atomic-file-darwin-janitor.js +272 -0
- package/dist/state/pinned-atomic-file-darwin-janitor.js.map +1 -0
- package/dist/state/pinned-atomic-file.d.ts +29 -0
- package/dist/state/pinned-atomic-file.d.ts.map +1 -0
- package/dist/state/pinned-atomic-file.js +536 -0
- package/dist/state/pinned-atomic-file.js.map +1 -0
- package/dist/state/process-identity.d.ts +30 -0
- package/dist/state/process-identity.d.ts.map +1 -0
- package/dist/state/process-identity.js +99 -0
- package/dist/state/process-identity.js.map +1 -0
- package/dist/state/skill-active.d.ts +18 -1
- package/dist/state/skill-active.d.ts.map +1 -1
- package/dist/state/skill-active.js +82 -5
- package/dist/state/skill-active.js.map +1 -1
- package/dist/state/workflow-transition-reconcile.js +1 -1
- package/dist/state/workflow-transition-reconcile.js.map +1 -1
- package/dist/team/__tests__/tmux-session.test.js +109 -0
- package/dist/team/__tests__/tmux-session.test.js.map +1 -1
- package/dist/team/tmux-session.d.ts.map +1 -1
- package/dist/team/tmux-session.js +10 -1
- package/dist/team/tmux-session.js.map +1 -1
- package/dist/utils/__tests__/file-durability.test.js +20 -0
- package/dist/utils/__tests__/file-durability.test.js.map +1 -1
- package/dist/utils/file-durability.d.ts +1 -1
- package/dist/utils/file-durability.d.ts.map +1 -1
- package/dist/utils/file-durability.js.map +1 -1
- package/dist/verification/__tests__/ci-rust-gates.test.js +12 -4
- package/dist/verification/__tests__/ci-rust-gates.test.js.map +1 -1
- package/dist/verification/__tests__/ci-trusted-publish.test.d.ts +2 -0
- package/dist/verification/__tests__/ci-trusted-publish.test.d.ts.map +1 -0
- package/dist/verification/__tests__/ci-trusted-publish.test.js +205 -0
- package/dist/verification/__tests__/ci-trusted-publish.test.js.map +1 -0
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js +10 -5
- package/dist/verification/__tests__/explore-harness-release-workflow.test.js.map +1 -1
- package/dist/verification/__tests__/publish-npm-manual-retired.test.d.ts +2 -0
- package/dist/verification/__tests__/publish-npm-manual-retired.test.d.ts.map +1 -0
- package/dist/verification/__tests__/publish-npm-manual-retired.test.js +22 -0
- package/dist/verification/__tests__/publish-npm-manual-retired.test.js.map +1 -0
- package/package.json +1 -1
- package/plugins/oh-my-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/oh-my-codex/skills/doctor/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/omx-setup/SKILL.md +1 -1
- package/plugins/oh-my-codex/skills/ralplan/SKILL.md +10 -1
- package/plugins/oh-my-codex/skills/team/SKILL.md +2 -0
- package/skills/doctor/SKILL.md +1 -1
- package/skills/omx-setup/SKILL.md +1 -1
- package/skills/ralplan/SKILL.md +10 -1
- package/skills/team/SKILL.md +2 -0
- package/src/scripts/__tests__/postinstall.test.ts +28 -0
- package/src/scripts/__tests__/ralplan-advisory-hook.test.ts +320 -0
- package/src/scripts/__tests__/smoke-packed-install.test.ts +120 -7
- package/src/scripts/codex-native-hook.ts +220 -10
- package/src/scripts/postinstall.ts +46 -0
- package/src/scripts/smoke-packed-install.ts +175 -70
|
@@ -0,0 +1,700 @@
|
|
|
1
|
+
import { afterEach, describe, it } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { cp, mkdir, mkdtemp, readFile, readdir, rename, rm, symlink, unlink, writeFile } from 'node:fs/promises';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
import { tmpdir } from 'node:os';
|
|
7
|
+
import { activateOrResumeRalplanAdvisory as activateRalplanAdvisoryWithProvenance, } from '../advisory-activation.js';
|
|
8
|
+
import { ADVISORY_ACTIVATION_VERIFIER_TEST_SEAM, verifyPinnedJsonAndSync, } from '../advisory-activation-verifier.js';
|
|
9
|
+
import { listActiveSkills, listTransitionActiveSkills, writeSkillActiveStateCopiesForStateDir } from '../../state/skill-active.js';
|
|
10
|
+
import { __setCanonicalModeBindingLeaseTestHooksForTests, resolveValidatedCanonicalModeBinding } from '../../state/mode-binding-lease.js';
|
|
11
|
+
import { executeStateOperation, outsideStateFileWriteTransaction, withStateFileWriteTransaction, writeStateFile } from '../../state/operations.js';
|
|
12
|
+
import { administrativelyAbandonRalplanAdvisory, commitPreparedRalplanAdvisoryActivationInternal, prepareAdvisoryCloseout, } from '../advisory.js';
|
|
13
|
+
const roots = [];
|
|
14
|
+
const activateOrResumeRalplanAdvisory = (input) => activateRalplanAdvisoryWithProvenance({ ...input, producer: 'native', threadKind: 'root-or-drift' });
|
|
15
|
+
afterEach(async () => {
|
|
16
|
+
__setCanonicalModeBindingLeaseTestHooksForTests({});
|
|
17
|
+
ADVISORY_ACTIVATION_VERIFIER_TEST_SEAM.fileSync = undefined;
|
|
18
|
+
ADVISORY_ACTIVATION_VERIFIER_TEST_SEAM.directorySync = undefined;
|
|
19
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
20
|
+
});
|
|
21
|
+
describe('central Ralplan Advisory activation owner', () => {
|
|
22
|
+
it('accepts degraded Windows file and directory fsync outcomes', async () => {
|
|
23
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-windows-fsync-'));
|
|
24
|
+
roots.push(cwd);
|
|
25
|
+
const path = join(cwd, 'projection.json');
|
|
26
|
+
await writeFile(path, '{"schema_version":1}\n');
|
|
27
|
+
ADVISORY_ACTIVATION_VERIFIER_TEST_SEAM.fileSync = async () => 'unsupported-windows-eperm';
|
|
28
|
+
ADVISORY_ACTIVATION_VERIFIER_TEST_SEAM.directorySync = async () => 'unsupported-windows-eperm';
|
|
29
|
+
const value = await verifyPinnedJsonAndSync(path, (record) => record.schema_version === 1);
|
|
30
|
+
assert.equal(value.schema_version, 1);
|
|
31
|
+
});
|
|
32
|
+
it('fails Advisory closed on unsupported platforms before creating intent or state', async () => {
|
|
33
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-unsupported-'));
|
|
34
|
+
roots.push(cwd);
|
|
35
|
+
__setCanonicalModeBindingLeaseTestHooksForTests({ platform: 'win32' });
|
|
36
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
37
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
38
|
+
prompt: '$ralplan --advisory unsupported', generationId: 'generation-a',
|
|
39
|
+
}), /unsupported on win32/);
|
|
40
|
+
assert.equal(existsSync(join(cwd, '.omx')), false);
|
|
41
|
+
assert.equal(existsSync(join(cwd, '.omx-state-locks')), false);
|
|
42
|
+
});
|
|
43
|
+
it('rejects a byte-identical state-root replacement before commit and retains the intent', async () => {
|
|
44
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-state-root-swap-'));
|
|
45
|
+
roots.push(cwd);
|
|
46
|
+
const stateRoot = join(cwd, '.omx', 'state');
|
|
47
|
+
const displaced = join(cwd, '.omx', 'state.displaced');
|
|
48
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
49
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
50
|
+
prompt: '$ralplan --advisory state root swap', generationId: 'generation-a',
|
|
51
|
+
failpoint: async (checkpoint) => {
|
|
52
|
+
if (checkpoint !== 'before_commit')
|
|
53
|
+
return;
|
|
54
|
+
await rename(stateRoot, displaced);
|
|
55
|
+
await cp(displaced, stateRoot, { recursive: true });
|
|
56
|
+
},
|
|
57
|
+
}), /state root identity changed/);
|
|
58
|
+
const advisoryRoot = join(stateRoot, 'sessions', 'session-a', 'ralplan-advisory');
|
|
59
|
+
await readFile(join(advisoryRoot, 'rollover-intent.json'));
|
|
60
|
+
assert.equal(existsSync(join(advisoryRoot, 'current.json')), false);
|
|
61
|
+
});
|
|
62
|
+
it('keeps activation preparation and commit imports behind the central owner boundary', async () => {
|
|
63
|
+
const advisorySource = await readFile(join(process.cwd(), 'src', 'ralplan', 'advisory.ts'), 'utf8');
|
|
64
|
+
assert.doesNotMatch(advisorySource, /export async function activateRalplanAdvisory\b/);
|
|
65
|
+
assert.doesNotMatch(advisorySource, /consumeActivationIntent|beforeActivationCommit/);
|
|
66
|
+
for (const relative of [
|
|
67
|
+
['src', 'ralplan', 'runtime.ts'],
|
|
68
|
+
['src', 'hooks', 'keyword-detector.ts'],
|
|
69
|
+
['src', 'scripts', 'codex-native-hook.ts'],
|
|
70
|
+
]) {
|
|
71
|
+
const source = await readFile(join(process.cwd(), ...relative), 'utf8');
|
|
72
|
+
assert.doesNotMatch(source, /prepareRalplanAdvisoryActivationInternal|readAuthorizedPendingRalplanActivation|commitPreparedRalplanAdvisoryActivationInternal/);
|
|
73
|
+
}
|
|
74
|
+
});
|
|
75
|
+
it('holds sanctioned mode writers outside the complete Advisory handoff', async () => {
|
|
76
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-mode-binding-lock-'));
|
|
77
|
+
roots.push(cwd);
|
|
78
|
+
const result = await activateOrResumeRalplanAdvisory({
|
|
79
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
80
|
+
prompt: '$ralplan --advisory serialized handoff', generationId: 'generation-a',
|
|
81
|
+
failpoint: async (checkpoint) => {
|
|
82
|
+
if (checkpoint !== 'after_mode')
|
|
83
|
+
return;
|
|
84
|
+
const competitor = await executeStateOperation('state_write', {
|
|
85
|
+
workingDirectory: cwd,
|
|
86
|
+
session_id: 'session-a',
|
|
87
|
+
mode: 'ralplan',
|
|
88
|
+
state: { active: true, workflow_variant: 'standard', current_phase: 'draft' },
|
|
89
|
+
});
|
|
90
|
+
assert.equal(competitor.isError, true);
|
|
91
|
+
assert.match(String(competitor.payload.error), /timed out waiting for .*lock|lease helper (request timed out|exited)/);
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
assert.equal(result.projection.corruption, null);
|
|
95
|
+
});
|
|
96
|
+
it('releases a failed activation lease and never removes a successor lock', async () => {
|
|
97
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-mode-binding-release-'));
|
|
98
|
+
roots.push(cwd);
|
|
99
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
100
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
101
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
102
|
+
prompt: '$ralplan --advisory failed lease release', generationId: 'generation-a',
|
|
103
|
+
failpoint: (checkpoint) => { if (checkpoint === 'after_mode')
|
|
104
|
+
throw new Error('forced-activation-failure'); },
|
|
105
|
+
}), /forced-activation-failure/);
|
|
106
|
+
await withStateFileWriteTransaction(bindingPath, async () => undefined);
|
|
107
|
+
const lockPath = (await resolveValidatedCanonicalModeBinding(bindingPath)).leasePath;
|
|
108
|
+
const displaced = `${lockPath}.old-owner`;
|
|
109
|
+
await assert.rejects(withStateFileWriteTransaction(bindingPath, async () => {
|
|
110
|
+
const ownerEntry = (await readdir(lockPath)).find((entry) => entry.startsWith('owner-'));
|
|
111
|
+
assert.ok(ownerEntry);
|
|
112
|
+
await rename(lockPath, displaced);
|
|
113
|
+
await mkdir(lockPath);
|
|
114
|
+
await writeFile(join(lockPath, 'owner-successor-token'), 'successor-token');
|
|
115
|
+
}), /ownership lost/);
|
|
116
|
+
assert.equal(await readFile(join(lockPath, 'owner-successor-token'), 'utf8'), 'successor-token');
|
|
117
|
+
assert.ok((await readdir(displaced)).some((entry) => entry.startsWith('owner-')));
|
|
118
|
+
});
|
|
119
|
+
it('rejects a symlinked lock namespace without touching its target or running work', async () => {
|
|
120
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-lock-symlink-'));
|
|
121
|
+
const external = await mkdtemp(join(tmpdir(), 'omx-advisory-lock-external-'));
|
|
122
|
+
roots.push(cwd, external);
|
|
123
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
124
|
+
await symlink(external, join(cwd, '.omx-state-locks'));
|
|
125
|
+
let ran = false;
|
|
126
|
+
await assert.rejects(withStateFileWriteTransaction(bindingPath, async () => { ran = true; }), /real directory/);
|
|
127
|
+
assert.equal(ran, false);
|
|
128
|
+
assert.deepEqual(await readdir(external), []);
|
|
129
|
+
});
|
|
130
|
+
it('rejects unsafe activation identities before creating lease or lifecycle artifacts', async () => {
|
|
131
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-invalid-identity-'));
|
|
132
|
+
roots.push(cwd);
|
|
133
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
134
|
+
cwd, sessionId: '..', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
135
|
+
prompt: '$ralplan --advisory invalid identity', generationId: 'generation-a',
|
|
136
|
+
}), /sessionId_invalid/);
|
|
137
|
+
assert.equal(existsSync(join(cwd, '.omx-state-locks')), false);
|
|
138
|
+
assert.equal(existsSync(join(cwd, '.omx', 'state')), false);
|
|
139
|
+
});
|
|
140
|
+
it('pins the lock namespace and rejects a replacement without admitting a second owner', async () => {
|
|
141
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-lock-namespace-swap-'));
|
|
142
|
+
roots.push(cwd);
|
|
143
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
144
|
+
const namespacePath = join(cwd, '.omx-state-locks');
|
|
145
|
+
const displaced = join(cwd, '.omx-state-locks.displaced');
|
|
146
|
+
let secondRan = false;
|
|
147
|
+
await assert.rejects(withStateFileWriteTransaction(bindingPath, async () => {
|
|
148
|
+
await rename(namespacePath, displaced);
|
|
149
|
+
await mkdir(namespacePath);
|
|
150
|
+
const second = outsideStateFileWriteTransaction(() => withStateFileWriteTransaction(bindingPath, async () => {
|
|
151
|
+
secondRan = true;
|
|
152
|
+
}));
|
|
153
|
+
await assert.rejects(second, /namespace identity mismatch/);
|
|
154
|
+
}), /namespace changed/);
|
|
155
|
+
assert.equal(secondRan, false);
|
|
156
|
+
assert.ok((await readdir(namespacePath)).length <= 1);
|
|
157
|
+
assert.ok((await readdir(displaced)).length <= 1);
|
|
158
|
+
});
|
|
159
|
+
it('reclaims a dead helper lease while the parent process remains alive', async () => {
|
|
160
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-dead-lock-helper-'));
|
|
161
|
+
roots.push(cwd);
|
|
162
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
163
|
+
let spawned = 0;
|
|
164
|
+
let firstChild;
|
|
165
|
+
__setCanonicalModeBindingLeaseTestHooksForTests({
|
|
166
|
+
onHelperSpawn: (child) => {
|
|
167
|
+
spawned += 1;
|
|
168
|
+
if (spawned === 1)
|
|
169
|
+
firstChild = child;
|
|
170
|
+
},
|
|
171
|
+
afterAcquire: () => { firstChild?.kill('SIGKILL'); },
|
|
172
|
+
});
|
|
173
|
+
await assert.rejects(withStateFileWriteTransaction(bindingPath, async () => undefined), /helper/);
|
|
174
|
+
await withStateFileWriteTransaction(bindingPath, async () => undefined);
|
|
175
|
+
assert.equal(spawned, 2);
|
|
176
|
+
});
|
|
177
|
+
it('lets a detached excluded writer acquire a fresh reentrant lease after activation releases', async () => {
|
|
178
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-detached-writer-'));
|
|
179
|
+
roots.push(cwd);
|
|
180
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
181
|
+
let detached;
|
|
182
|
+
await activateOrResumeRalplanAdvisory({
|
|
183
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
184
|
+
prompt: '$ralplan --advisory detached writer', generationId: 'generation-a',
|
|
185
|
+
failpoint: (checkpoint) => {
|
|
186
|
+
if (checkpoint !== 'after_mode')
|
|
187
|
+
return;
|
|
188
|
+
detached = withStateFileWriteTransaction(bindingPath, async () => {
|
|
189
|
+
const bytes = await readFile(bindingPath, 'utf8');
|
|
190
|
+
await writeStateFile(bindingPath, bytes);
|
|
191
|
+
});
|
|
192
|
+
},
|
|
193
|
+
});
|
|
194
|
+
await detached;
|
|
195
|
+
});
|
|
196
|
+
it('keeps one generation recoverable across every publication checkpoint', async () => {
|
|
197
|
+
const checkpoints = [
|
|
198
|
+
'after_intent', 'after_mode', 'after_run_state',
|
|
199
|
+
'before_skill_mirror_commit', 'after_skill_mirror_transaction',
|
|
200
|
+
'after_session_skill', 'after_root_skill', 'before_mode_fsync', 'before_commit',
|
|
201
|
+
];
|
|
202
|
+
for (const checkpoint of checkpoints) {
|
|
203
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-${checkpoint}-`));
|
|
204
|
+
roots.push(cwd);
|
|
205
|
+
const input = {
|
|
206
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a',
|
|
207
|
+
activationTurnId: 'turn-a', prompt: '$ralplan --advisory planificá',
|
|
208
|
+
};
|
|
209
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
210
|
+
...input,
|
|
211
|
+
failpoint: (name) => { if (name === checkpoint)
|
|
212
|
+
throw new Error(`crash:${name}`); },
|
|
213
|
+
}), /crash/);
|
|
214
|
+
const root = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-advisory');
|
|
215
|
+
const intent = JSON.parse(await readFile(join(root, 'rollover-intent.json'), 'utf8'));
|
|
216
|
+
const recovered = await activateOrResumeRalplanAdvisory(input);
|
|
217
|
+
assert.equal(recovered.activation.generation_id, intent.generation_id, checkpoint);
|
|
218
|
+
assert.equal(recovered.projection.corruption, null, checkpoint);
|
|
219
|
+
assert.equal(JSON.parse(await readFile(join(root, 'current.json'), 'utf8')).generation_id, intent.generation_id, checkpoint);
|
|
220
|
+
const binding = JSON.parse(await readFile(join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json'), 'utf8'));
|
|
221
|
+
assert.equal(binding.advisory_generation_id, intent.generation_id, checkpoint);
|
|
222
|
+
assert.equal(binding.execution_handoff_authorized, false, checkpoint);
|
|
223
|
+
assert.equal(binding.host_verified, false, checkpoint);
|
|
224
|
+
}
|
|
225
|
+
});
|
|
226
|
+
it('preserves foreign-session Team root state while repairing Advisory mirrors and retrying', async () => {
|
|
227
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-root-merge-'));
|
|
228
|
+
roots.push(cwd);
|
|
229
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
230
|
+
await mkdir(stateDir, { recursive: true });
|
|
231
|
+
const foreignTeam = {
|
|
232
|
+
version: 1, active: true, skill: 'ralplan', phase: 'architect-review', session_id: 'session-b',
|
|
233
|
+
workflow_variant: 'standard', marker: 'foreign-top-level',
|
|
234
|
+
active_skills: [
|
|
235
|
+
{ skill: 'team', active: true, phase: 'executing', session_id: 'session-b' },
|
|
236
|
+
{ skill: 'ralplan', active: true, phase: 'architect-review', session_id: 'session-b', workflow_variant: 'standard' },
|
|
237
|
+
],
|
|
238
|
+
};
|
|
239
|
+
await writeFile(join(stateDir, 'skill-active-state.json'), `${JSON.stringify(foreignTeam, null, 2)}\n`);
|
|
240
|
+
const input = { cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a', prompt: '$ralplan --advisory planificá' };
|
|
241
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
242
|
+
...input, failpoint: (checkpoint) => { if (checkpoint === 'after_root_skill')
|
|
243
|
+
throw new Error('crash:root'); },
|
|
244
|
+
}), /crash:root/);
|
|
245
|
+
for (const phase of ['failed activation', 'retry']) {
|
|
246
|
+
const root = JSON.parse(await readFile(join(stateDir, 'skill-active-state.json'), 'utf8'));
|
|
247
|
+
assert.ok(root.active_skills.some((entry) => entry.skill === 'team' && entry.session_id === 'session-b'), phase);
|
|
248
|
+
assert.equal(root.skill, foreignTeam.skill, phase);
|
|
249
|
+
assert.equal(root.session_id, foreignTeam.session_id, phase);
|
|
250
|
+
assert.equal(root.workflow_variant, foreignTeam.workflow_variant, phase);
|
|
251
|
+
assert.equal(root.marker, foreignTeam.marker, phase);
|
|
252
|
+
assert.deepEqual(listTransitionActiveSkills(root, 'session-b').map((entry) => [entry.skill, entry.workflow_variant]), [['team', undefined], ['ralplan', 'standard']], phase);
|
|
253
|
+
if (phase === 'failed activation')
|
|
254
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
255
|
+
}
|
|
256
|
+
});
|
|
257
|
+
it('rejects a regular-file replacement between pinned validation and fsync without consuming the intent', async () => {
|
|
258
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-pinned-replace-'));
|
|
259
|
+
roots.push(cwd);
|
|
260
|
+
const input = { cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a', prompt: '$ralplan --advisory planificá' };
|
|
261
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
262
|
+
const replacement = `${bindingPath}.replacement`;
|
|
263
|
+
const replacementBytes = '{"active":true,"mode":"ralplan","session_id":"session-a","workflow_variant":"standard"}\n';
|
|
264
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
265
|
+
...input,
|
|
266
|
+
failpoint: async (checkpoint) => {
|
|
267
|
+
if (checkpoint !== 'before_mode_fsync')
|
|
268
|
+
return;
|
|
269
|
+
await writeFile(replacement, replacementBytes);
|
|
270
|
+
await rename(replacement, bindingPath);
|
|
271
|
+
},
|
|
272
|
+
}), /projection_(?:identity|content)_changed/);
|
|
273
|
+
assert.equal(await readFile(bindingPath, 'utf8'), replacementBytes);
|
|
274
|
+
await readFile(join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-advisory', 'rollover-intent.json'));
|
|
275
|
+
});
|
|
276
|
+
it('rejects a same-inode overwrite after validation without consuming the intent', async () => {
|
|
277
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-pinned-overwrite-'));
|
|
278
|
+
roots.push(cwd);
|
|
279
|
+
const input = { cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a', prompt: '$ralplan --advisory planificá' };
|
|
280
|
+
const bindingPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-state.json');
|
|
281
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
282
|
+
...input,
|
|
283
|
+
failpoint: async (checkpoint) => {
|
|
284
|
+
if (checkpoint === 'before_mode_fsync')
|
|
285
|
+
await writeFile(bindingPath, '{"active":false}\n');
|
|
286
|
+
},
|
|
287
|
+
}), /projection_content_changed/);
|
|
288
|
+
await readFile(join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-advisory', 'rollover-intent.json'));
|
|
289
|
+
});
|
|
290
|
+
it('revalidates every runtime mirror under the activation lock immediately before consuming intent', async () => {
|
|
291
|
+
for (const target of ['run-state.json', 'skill-active-state.json', 'root-skill']) {
|
|
292
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-precommit-${target}-`));
|
|
293
|
+
roots.push(cwd);
|
|
294
|
+
const input = { cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a', prompt: '$ralplan --advisory planificá' };
|
|
295
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
296
|
+
const targetPath = target === 'root-skill'
|
|
297
|
+
? join(stateDir, 'skill-active-state.json')
|
|
298
|
+
: join(stateDir, 'sessions', 'session-a', target);
|
|
299
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
300
|
+
...input,
|
|
301
|
+
failpoint: async (checkpoint) => {
|
|
302
|
+
if (checkpoint === 'before_commit')
|
|
303
|
+
await writeFile(targetPath, '{"active":false}\n');
|
|
304
|
+
},
|
|
305
|
+
}), /projection_mismatch/);
|
|
306
|
+
await readFile(join(stateDir, 'sessions', 'session-a', 'ralplan-advisory', 'rollover-intent.json'));
|
|
307
|
+
const recovered = await activateOrResumeRalplanAdvisory(input);
|
|
308
|
+
assert.equal(recovered.projection.corruption, null, target);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
it('does not prepare or consume activation state without authenticated root provenance', async () => {
|
|
312
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-auth-'));
|
|
313
|
+
roots.push(cwd);
|
|
314
|
+
const input = {
|
|
315
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
316
|
+
prompt: '$ralplan --advisory authenticated activation',
|
|
317
|
+
};
|
|
318
|
+
await assert.rejects(activateRalplanAdvisoryWithProvenance({
|
|
319
|
+
...input, producer: 'native', threadKind: 'unknown',
|
|
320
|
+
}), /activation_authority_required/);
|
|
321
|
+
assert.equal(await activateRalplanAdvisoryWithProvenance({
|
|
322
|
+
...input, producer: 'native', threadKind: 'unknown', resumeOnly: true,
|
|
323
|
+
}), null);
|
|
324
|
+
assert.equal(existsSync(join(cwd, '.omx')), false);
|
|
325
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
326
|
+
...input, failpoint: (checkpoint) => { if (checkpoint === 'after_intent')
|
|
327
|
+
throw new Error('crash:intent'); },
|
|
328
|
+
}), /crash:intent/);
|
|
329
|
+
const intentPath = join(cwd, '.omx', 'state', 'sessions', 'session-a', 'ralplan-advisory', 'rollover-intent.json');
|
|
330
|
+
const before = await readFile(intentPath, 'utf8');
|
|
331
|
+
assert.equal(await activateRalplanAdvisoryWithProvenance({
|
|
332
|
+
...input, producer: 'native', threadKind: 'unknown', resumeOnly: true,
|
|
333
|
+
}), null);
|
|
334
|
+
assert.equal(await readFile(intentPath, 'utf8'), before);
|
|
335
|
+
});
|
|
336
|
+
it('returns the same committed generation after a crash at the post-commit boundary', async () => {
|
|
337
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-postcommit-'));
|
|
338
|
+
roots.push(cwd);
|
|
339
|
+
const input = {
|
|
340
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
341
|
+
prompt: '$ralplan --advisory idempotent activation', generationId: 'generation-a',
|
|
342
|
+
};
|
|
343
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
344
|
+
...input, failpoint: (checkpoint) => { if (checkpoint === 'intent_committed')
|
|
345
|
+
throw new Error('crash:committed'); },
|
|
346
|
+
}), /crash:committed/);
|
|
347
|
+
const retried = await activateOrResumeRalplanAdvisory(input);
|
|
348
|
+
assert.equal(retried.activation.generation_id, 'generation-a');
|
|
349
|
+
assert.equal(retried.projection.activation.generation_id, 'generation-a');
|
|
350
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
351
|
+
for (const path of [
|
|
352
|
+
join(stateDir, 'sessions', 'session-a', 'skill-active-state.json'),
|
|
353
|
+
join(stateDir, 'skill-active-state.json'),
|
|
354
|
+
]) {
|
|
355
|
+
const state = JSON.parse(await readFile(path, 'utf8'));
|
|
356
|
+
state.active_skills = state.active_skills.map((entry) => entry.skill === 'ralplan'
|
|
357
|
+
? Object.fromEntries(Object.entries(entry).filter(([key]) => !['workflow_variant', 'advisory_generation_id'].includes(key)))
|
|
358
|
+
: entry);
|
|
359
|
+
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`);
|
|
360
|
+
}
|
|
361
|
+
const repaired = await activateOrResumeRalplanAdvisory(input);
|
|
362
|
+
assert.equal(repaired.activation.generation_id, 'generation-a');
|
|
363
|
+
for (const path of [
|
|
364
|
+
join(stateDir, 'sessions', 'session-a', 'skill-active-state.json'),
|
|
365
|
+
join(stateDir, 'skill-active-state.json'),
|
|
366
|
+
]) {
|
|
367
|
+
const entry = listActiveSkills(JSON.parse(await readFile(path, 'utf8')))
|
|
368
|
+
.find((candidate) => candidate.skill === 'ralplan' && candidate.session_id === 'session-a');
|
|
369
|
+
assert.equal(entry?.workflow_variant, 'advisory', path);
|
|
370
|
+
assert.equal(entry?.advisory_generation_id, 'generation-a', path);
|
|
371
|
+
}
|
|
372
|
+
const sessionSkillPath = join(stateDir, 'sessions', 'session-a', 'skill-active-state.json');
|
|
373
|
+
const conflicted = JSON.parse(await readFile(sessionSkillPath, 'utf8'));
|
|
374
|
+
conflicted.active_skills = conflicted.active_skills.map((entry) => entry.skill === 'ralplan'
|
|
375
|
+
? { ...entry, advisory_generation_id: 'generation-foreign' }
|
|
376
|
+
: entry);
|
|
377
|
+
await writeFile(sessionSkillPath, `${JSON.stringify(conflicted, null, 2)}\n`);
|
|
378
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /session_skill_binding_conflict/);
|
|
379
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
380
|
+
...input, prompt: '$ralplan --advisory different prompt',
|
|
381
|
+
}), /committed_activation_authority_mismatch/);
|
|
382
|
+
});
|
|
383
|
+
it('rejects an exact-session foreign root generation without mutating root bytes', async () => {
|
|
384
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-root-conflict-'));
|
|
385
|
+
roots.push(cwd);
|
|
386
|
+
const input = {
|
|
387
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
388
|
+
prompt: '$ralplan --advisory root conflict', generationId: 'generation-a',
|
|
389
|
+
};
|
|
390
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
391
|
+
const rootPath = join(cwd, '.omx', 'state', 'skill-active-state.json');
|
|
392
|
+
const root = JSON.parse(await readFile(rootPath, 'utf8'));
|
|
393
|
+
root.active_skills = root.active_skills.map((entry) => (entry.skill === 'ralplan' && entry.session_id === 'session-a'
|
|
394
|
+
? { ...entry, advisory_generation_id: 'generation-foreign' }
|
|
395
|
+
: entry));
|
|
396
|
+
const foreignBytes = `${JSON.stringify(root, null, 2)}\n`;
|
|
397
|
+
await writeFile(rootPath, foreignBytes);
|
|
398
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /root_skill_binding_conflict/);
|
|
399
|
+
assert.equal(await readFile(rootPath, 'utf8'), foreignBytes);
|
|
400
|
+
});
|
|
401
|
+
it('rejects conflicting exact-session root top-level binding metadata byte-exactly', async () => {
|
|
402
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-root-top-conflict-'));
|
|
403
|
+
roots.push(cwd);
|
|
404
|
+
const input = {
|
|
405
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
406
|
+
prompt: '$ralplan --advisory root top conflict', generationId: 'generation-a',
|
|
407
|
+
};
|
|
408
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
409
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
410
|
+
const rootPath = join(stateDir, 'skill-active-state.json');
|
|
411
|
+
const sessionPath = join(stateDir, 'sessions', 'session-a', 'skill-active-state.json');
|
|
412
|
+
const root = JSON.parse(await readFile(rootPath, 'utf8'));
|
|
413
|
+
root.session_id = 'session-a';
|
|
414
|
+
root.workflow_variant = 'advisory';
|
|
415
|
+
root.advisory_generation_id = 'generation-foreign';
|
|
416
|
+
const foreignRootBytes = `${JSON.stringify(root, null, 2)}\n`;
|
|
417
|
+
await writeFile(rootPath, foreignRootBytes);
|
|
418
|
+
const sessionBytes = await readFile(sessionPath, 'utf8');
|
|
419
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /root_skill_binding_conflict/);
|
|
420
|
+
assert.equal(await readFile(rootPath, 'utf8'), foreignRootBytes);
|
|
421
|
+
assert.equal(await readFile(sessionPath, 'utf8'), sessionBytes);
|
|
422
|
+
});
|
|
423
|
+
it('rejects wrong-typed exact-session root top-level binding fields byte-exactly', async () => {
|
|
424
|
+
for (const [field, value] of [
|
|
425
|
+
['workflow_variant', { forged: true }],
|
|
426
|
+
['advisory_generation_id', 42],
|
|
427
|
+
]) {
|
|
428
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-root-top-type-${field}-`));
|
|
429
|
+
roots.push(cwd);
|
|
430
|
+
const input = {
|
|
431
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
432
|
+
prompt: `$ralplan --advisory root top type ${field}`, generationId: 'generation-a',
|
|
433
|
+
};
|
|
434
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
435
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
436
|
+
const rootPath = join(stateDir, 'skill-active-state.json');
|
|
437
|
+
const sessionPath = join(stateDir, 'sessions', 'session-a', 'skill-active-state.json');
|
|
438
|
+
const root = JSON.parse(await readFile(rootPath, 'utf8'));
|
|
439
|
+
root.session_id = 'session-a';
|
|
440
|
+
root[field] = value;
|
|
441
|
+
const foreignRootBytes = `${JSON.stringify(root, null, 2)}\n`;
|
|
442
|
+
await writeFile(rootPath, foreignRootBytes);
|
|
443
|
+
const sessionBytes = await readFile(sessionPath, 'utf8');
|
|
444
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /root_skill_binding_conflict/);
|
|
445
|
+
assert.equal(await readFile(rootPath, 'utf8'), foreignRootBytes);
|
|
446
|
+
assert.equal(await readFile(sessionPath, 'utf8'), sessionBytes);
|
|
447
|
+
}
|
|
448
|
+
});
|
|
449
|
+
it('rejects wrong-typed nested root and session binding fields byte-exactly', async () => {
|
|
450
|
+
for (const [surface, field, value] of [
|
|
451
|
+
['root-entry', 'workflow_variant', false],
|
|
452
|
+
['root-entry', 'advisory_generation_id', 0],
|
|
453
|
+
['session-entry', 'workflow_variant', { forged: true }],
|
|
454
|
+
['session-entry', 'advisory_generation_id', []],
|
|
455
|
+
]) {
|
|
456
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-${surface}-${field}-`));
|
|
457
|
+
roots.push(cwd);
|
|
458
|
+
const input = {
|
|
459
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
460
|
+
prompt: `$ralplan --advisory ${surface} ${field}`, generationId: 'generation-a',
|
|
461
|
+
};
|
|
462
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
463
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
464
|
+
const rootPath = join(stateDir, 'skill-active-state.json');
|
|
465
|
+
const sessionPath = join(stateDir, 'sessions', 'session-a', 'skill-active-state.json');
|
|
466
|
+
const targetPath = surface === 'root-entry' ? rootPath : sessionPath;
|
|
467
|
+
const target = JSON.parse(await readFile(targetPath, 'utf8'));
|
|
468
|
+
const entry = target.active_skills.find((candidate) => candidate.skill === 'ralplan');
|
|
469
|
+
entry[field] = value;
|
|
470
|
+
await writeFile(targetPath, `${JSON.stringify(target, null, 2)}\n`);
|
|
471
|
+
const rootBytes = await readFile(rootPath, 'utf8');
|
|
472
|
+
const sessionBytes = await readFile(sessionPath, 'utf8');
|
|
473
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /skill_binding_conflict/);
|
|
474
|
+
assert.equal(await readFile(rootPath, 'utf8'), rootBytes);
|
|
475
|
+
assert.equal(await readFile(sessionPath, 'utf8'), sessionBytes);
|
|
476
|
+
}
|
|
477
|
+
});
|
|
478
|
+
it('rejects conflicting legacy-unscoped root ownership byte-exactly', async () => {
|
|
479
|
+
for (const surface of ['top-level', 'root-entry']) {
|
|
480
|
+
for (const field of ['workflow_variant', 'advisory_generation_id']) {
|
|
481
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-legacy-owner-${surface}-${field}-`));
|
|
482
|
+
roots.push(cwd);
|
|
483
|
+
const input = {
|
|
484
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
485
|
+
prompt: `$ralplan --advisory legacy owner ${surface} ${field}`, generationId: 'generation-a',
|
|
486
|
+
};
|
|
487
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
488
|
+
const rootPath = join(cwd, '.omx', 'state', 'skill-active-state.json');
|
|
489
|
+
const root = JSON.parse(await readFile(rootPath, 'utf8'));
|
|
490
|
+
const target = surface === 'top-level'
|
|
491
|
+
? root
|
|
492
|
+
: root.active_skills.find((entry) => entry.skill === 'ralplan');
|
|
493
|
+
delete target.session_id;
|
|
494
|
+
target.owner_codex_session_id = 'session-a';
|
|
495
|
+
target[field] = field === 'workflow_variant' ? 'standard' : 'generation-foreign';
|
|
496
|
+
await writeFile(rootPath, `${JSON.stringify(root, null, 2)}\n`);
|
|
497
|
+
const before = await readFile(rootPath, 'utf8');
|
|
498
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /root_skill_binding_conflict/);
|
|
499
|
+
assert.equal(await readFile(rootPath, 'utf8'), before, `${surface}:${field}`);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
});
|
|
503
|
+
it('never follows an initial or validation-time session mirror symlink', async () => {
|
|
504
|
+
for (const timing of ['initial', 'during-validation']) {
|
|
505
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-session-symlink-${timing}-`));
|
|
506
|
+
roots.push(cwd);
|
|
507
|
+
const input = {
|
|
508
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
509
|
+
prompt: `$ralplan --advisory session symlink ${timing}`, generationId: 'generation-a',
|
|
510
|
+
};
|
|
511
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
512
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
513
|
+
const rootPath = join(stateDir, 'skill-active-state.json');
|
|
514
|
+
const sessionPath = join(stateDir, 'sessions', 'session-a', 'skill-active-state.json');
|
|
515
|
+
const externalPath = join(cwd, `external-${timing}.json`);
|
|
516
|
+
const externalBytes = `external-${timing}\n`;
|
|
517
|
+
await writeFile(externalPath, externalBytes);
|
|
518
|
+
const rootBytes = await readFile(rootPath, 'utf8');
|
|
519
|
+
if (timing === 'initial') {
|
|
520
|
+
await unlink(sessionPath);
|
|
521
|
+
await symlink(externalPath, sessionPath);
|
|
522
|
+
}
|
|
523
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
524
|
+
...input,
|
|
525
|
+
failpoint: timing === 'during-validation'
|
|
526
|
+
? async (checkpoint) => {
|
|
527
|
+
if (checkpoint !== 'before_skill_mirror_commit')
|
|
528
|
+
return;
|
|
529
|
+
await unlink(sessionPath);
|
|
530
|
+
await symlink(externalPath, sessionPath);
|
|
531
|
+
}
|
|
532
|
+
: undefined,
|
|
533
|
+
}), /ELOOP|file-changed|session mirror changed|atomic-replace-failed|recovery is ambiguous/);
|
|
534
|
+
assert.equal(await readFile(externalPath, 'utf8'), externalBytes, timing);
|
|
535
|
+
assert.equal(await readFile(rootPath, 'utf8'), rootBytes, timing);
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
it('rejects a Darwin-style parent-directory swap without mutating either directory or root', async () => {
|
|
539
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-session-parent-swap-'));
|
|
540
|
+
roots.push(cwd);
|
|
541
|
+
const input = {
|
|
542
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
543
|
+
prompt: '$ralplan --advisory session parent swap', generationId: 'generation-a',
|
|
544
|
+
};
|
|
545
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
546
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
547
|
+
const sessionDir = join(stateDir, 'sessions', 'session-a');
|
|
548
|
+
const displacedDir = join(stateDir, 'sessions', 'session-a-displaced');
|
|
549
|
+
const externalDir = join(cwd, 'external-session');
|
|
550
|
+
const externalSkillPath = join(externalDir, 'skill-active-state.json');
|
|
551
|
+
const originalSkillPath = join(sessionDir, 'skill-active-state.json');
|
|
552
|
+
const rootPath = join(stateDir, 'skill-active-state.json');
|
|
553
|
+
await mkdir(externalDir);
|
|
554
|
+
const externalBytes = 'external-parent-swap\n';
|
|
555
|
+
await writeFile(externalSkillPath, externalBytes);
|
|
556
|
+
const originalBytes = await readFile(originalSkillPath, 'utf8');
|
|
557
|
+
const rootBytes = await readFile(rootPath, 'utf8');
|
|
558
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
559
|
+
...input,
|
|
560
|
+
failpoint: async (checkpoint) => {
|
|
561
|
+
if (checkpoint !== 'before_skill_mirror_commit')
|
|
562
|
+
return;
|
|
563
|
+
await rename(sessionDir, displacedDir);
|
|
564
|
+
await symlink(externalDir, sessionDir, 'dir');
|
|
565
|
+
},
|
|
566
|
+
}), /parent changed|unsupported|ENOTSUP/);
|
|
567
|
+
assert.equal(await readFile(externalSkillPath, 'utf8'), externalBytes);
|
|
568
|
+
assert.equal(await readFile(join(displacedDir, 'skill-active-state.json'), 'utf8'), originalBytes);
|
|
569
|
+
assert.equal(await readFile(rootPath, 'utf8'), rootBytes);
|
|
570
|
+
});
|
|
571
|
+
it('keeps fast repair atomic when a Standard writer wins immediately after the mirror transaction', async () => {
|
|
572
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-fast-repair-race-'));
|
|
573
|
+
roots.push(cwd);
|
|
574
|
+
const input = {
|
|
575
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
576
|
+
prompt: '$ralplan --advisory repair race', generationId: 'generation-a',
|
|
577
|
+
};
|
|
578
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
579
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
580
|
+
for (const path of [
|
|
581
|
+
join(stateDir, 'sessions', 'session-a', 'skill-active-state.json'),
|
|
582
|
+
join(stateDir, 'skill-active-state.json'),
|
|
583
|
+
]) {
|
|
584
|
+
const state = JSON.parse(await readFile(path, 'utf8'));
|
|
585
|
+
state.active_skills = state.active_skills.map((entry) => entry.skill === 'ralplan'
|
|
586
|
+
? Object.fromEntries(Object.entries(entry).filter(([key]) => !['workflow_variant', 'advisory_generation_id'].includes(key)))
|
|
587
|
+
: entry);
|
|
588
|
+
await writeFile(path, `${JSON.stringify(state, null, 2)}\n`);
|
|
589
|
+
}
|
|
590
|
+
const standardState = {
|
|
591
|
+
version: 1, active: true, skill: 'ralplan', phase: 'planning', session_id: 'session-a',
|
|
592
|
+
active_skills: [{ skill: 'ralplan', active: true, phase: 'planning', session_id: 'session-a' }],
|
|
593
|
+
};
|
|
594
|
+
let competitor;
|
|
595
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
596
|
+
...input,
|
|
597
|
+
failpoint: async (checkpoint) => {
|
|
598
|
+
if (checkpoint === 'before_skill_mirror_commit') {
|
|
599
|
+
competitor = writeSkillActiveStateCopiesForStateDir(stateDir, standardState, 'session-a');
|
|
600
|
+
}
|
|
601
|
+
if (checkpoint === 'after_skill_mirror_transaction')
|
|
602
|
+
await competitor;
|
|
603
|
+
},
|
|
604
|
+
}), /projection_mismatch/);
|
|
605
|
+
for (const path of [
|
|
606
|
+
join(stateDir, 'sessions', 'session-a', 'skill-active-state.json'),
|
|
607
|
+
join(stateDir, 'skill-active-state.json'),
|
|
608
|
+
]) {
|
|
609
|
+
const entries = listActiveSkills(JSON.parse(await readFile(path, 'utf8')));
|
|
610
|
+
const entry = entries.find((candidate) => candidate.skill === 'ralplan' && candidate.session_id === 'session-a');
|
|
611
|
+
assert.ok(entry, path);
|
|
612
|
+
assert.equal(entry.workflow_variant, undefined, path);
|
|
613
|
+
assert.equal(entry.advisory_generation_id, undefined, path);
|
|
614
|
+
}
|
|
615
|
+
});
|
|
616
|
+
it('rejects fast-path success when mode or run state drifts during the skill mirror commit', async () => {
|
|
617
|
+
for (const surface of ['mode', 'run']) {
|
|
618
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-fast-${surface}-drift-`));
|
|
619
|
+
roots.push(cwd);
|
|
620
|
+
const input = {
|
|
621
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
622
|
+
prompt: `$ralplan --advisory fast ${surface} drift`, generationId: 'generation-a',
|
|
623
|
+
};
|
|
624
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
625
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
626
|
+
const sessionDir = join(stateDir, 'sessions', 'session-a');
|
|
627
|
+
const targetPath = join(sessionDir, surface === 'mode' ? 'ralplan-state.json' : 'run-state.json');
|
|
628
|
+
const advisoryRoot = join(sessionDir, 'ralplan-advisory');
|
|
629
|
+
const currentPath = join(advisoryRoot, 'current.json');
|
|
630
|
+
const currentBytes = await readFile(currentPath, 'utf8');
|
|
631
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
632
|
+
...input,
|
|
633
|
+
failpoint: async (checkpoint) => {
|
|
634
|
+
if (checkpoint !== 'before_skill_mirror_commit')
|
|
635
|
+
return;
|
|
636
|
+
const state = JSON.parse(await readFile(targetPath, 'utf8'));
|
|
637
|
+
if (surface === 'mode')
|
|
638
|
+
delete state.workflow_variant;
|
|
639
|
+
else
|
|
640
|
+
state.active = false;
|
|
641
|
+
await writeFile(targetPath, `${JSON.stringify(state, null, 2)}\n`);
|
|
642
|
+
},
|
|
643
|
+
}), /projection_mismatch/);
|
|
644
|
+
assert.equal(await readFile(currentPath, 'utf8'), currentBytes, surface);
|
|
645
|
+
assert.equal(existsSync(join(advisoryRoot, 'rollover-intent.json')), false, surface);
|
|
646
|
+
const drifted = JSON.parse(await readFile(targetPath, 'utf8'));
|
|
647
|
+
assert.equal(surface === 'mode' ? drifted.workflow_variant : drifted.active, surface === 'mode' ? undefined : false);
|
|
648
|
+
}
|
|
649
|
+
});
|
|
650
|
+
it('rejects a terminal generation behind a stale active binding without repairing skill mirrors', async () => {
|
|
651
|
+
const cwd = await mkdtemp(join(tmpdir(), 'omx-advisory-activation-terminal-stale-binding-'));
|
|
652
|
+
roots.push(cwd);
|
|
653
|
+
const input = {
|
|
654
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
655
|
+
prompt: '$ralplan --advisory terminal stale binding', generationId: 'generation-a',
|
|
656
|
+
};
|
|
657
|
+
await activateOrResumeRalplanAdvisory(input);
|
|
658
|
+
await prepareAdvisoryCloseout({
|
|
659
|
+
cwd, sessionId: 'session-a', generationId: 'generation-a', closingTurnId: 'turn-close', iteration: 1,
|
|
660
|
+
});
|
|
661
|
+
await administrativelyAbandonRalplanAdvisory({
|
|
662
|
+
cwd, sessionId: 'session-a', generationId: 'generation-a', rootThreadId: 'root-a', turnId: 'turn-close',
|
|
663
|
+
});
|
|
664
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
665
|
+
const skillPaths = [
|
|
666
|
+
join(stateDir, 'sessions', 'session-a', 'skill-active-state.json'),
|
|
667
|
+
join(stateDir, 'skill-active-state.json'),
|
|
668
|
+
];
|
|
669
|
+
const before = await Promise.all(skillPaths.map((path) => readFile(path, 'utf8')));
|
|
670
|
+
await assert.rejects(activateOrResumeRalplanAdvisory(input), /not_precloseout/);
|
|
671
|
+
assert.deepEqual(await Promise.all(skillPaths.map((path) => readFile(path, 'utf8'))), before);
|
|
672
|
+
});
|
|
673
|
+
it('rejects direct commit when any required mirror is missing and retains the intent', async () => {
|
|
674
|
+
for (const target of ['ralplan-state.json', 'run-state.json', 'session-skill', 'root-skill']) {
|
|
675
|
+
const cwd = await mkdtemp(join(tmpdir(), `omx-advisory-activation-commit-bypass-${target}-`));
|
|
676
|
+
roots.push(cwd);
|
|
677
|
+
const input = {
|
|
678
|
+
cwd, sessionId: 'session-a', rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
679
|
+
prompt: '$ralplan --advisory commit boundary',
|
|
680
|
+
};
|
|
681
|
+
await assert.rejects(activateOrResumeRalplanAdvisory({
|
|
682
|
+
...input, failpoint: (checkpoint) => { if (checkpoint === 'after_root_skill')
|
|
683
|
+
throw new Error('crash:prepared'); },
|
|
684
|
+
}), /crash:prepared/);
|
|
685
|
+
const stateDir = join(cwd, '.omx', 'state');
|
|
686
|
+
const targetPath = target === 'root-skill'
|
|
687
|
+
? join(stateDir, 'skill-active-state.json')
|
|
688
|
+
: target === 'session-skill'
|
|
689
|
+
? join(stateDir, 'sessions', 'session-a', 'skill-active-state.json')
|
|
690
|
+
: join(stateDir, 'sessions', 'session-a', target);
|
|
691
|
+
await rm(targetPath);
|
|
692
|
+
await assert.rejects(commitPreparedRalplanAdvisoryActivationInternal({
|
|
693
|
+
cwd, sessionId: 'session-a', producer: 'native', threadKind: 'root-or-drift',
|
|
694
|
+
rootThreadId: 'root-a', activationTurnId: 'turn-a',
|
|
695
|
+
}), /binding_conflict|ENOENT|projection_/);
|
|
696
|
+
await readFile(join(stateDir, 'sessions', 'session-a', 'ralplan-advisory', 'rollover-intent.json'));
|
|
697
|
+
}
|
|
698
|
+
});
|
|
699
|
+
});
|
|
700
|
+
//# sourceMappingURL=advisory-activation.test.js.map
|