@enterpriseai/cli 3.19.0 → 3.19.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/package.json +3 -3
- package/resources/deploy/eai-app-template/producer-pin.json +5 -5
- package/resources/deploy/eai-app-template/tests/source-unknown-deployment-evidence.test.mjs +2141 -1
- package/resources/gofer/.gofer-version +4 -357
- package/resources/gofer/agents-skills/eai/SKILL.md +365 -0
- package/resources/gofer/agents-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/agents-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/bash-scripts/create-new-feature.sh +3 -1
- package/resources/gofer/bash-scripts/sync-implementation-status.sh +4 -3
- package/resources/gofer/bash-scripts/update-agent-context.sh +7 -8
- package/resources/gofer/bash-scripts/validate-artifact.sh +10 -6
- package/resources/gofer/bash-scripts/verify-object-type-routing-workspace.sh +184 -0
- package/resources/gofer/claude-commands/eai-update.md +46 -0
- package/resources/gofer/claude-commands/eai.md +355 -0
- package/resources/gofer/claude-skills/eai/SKILL.md +365 -0
- package/resources/gofer/claude-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/claude-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/commands/0_gofer_start.md +440 -99
- package/resources/gofer/commands/0a_problem_validation.md +187 -27
- package/resources/gofer/commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/commands/1_gofer_research.md +239 -30
- package/resources/gofer/commands/2_gofer_specify.md +239 -35
- package/resources/gofer/commands/3_gofer_plan.md +259 -43
- package/resources/gofer/commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/commands/5_gofer_implement.md +375 -84
- package/resources/gofer/commands/6_gofer_validate.md +284 -53
- package/resources/gofer/commands/7_gofer_save.md +187 -26
- package/resources/gofer/commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/commands/8_gofer_branding.md +183 -23
- package/resources/gofer/commands/9_gofer_tests.md +212 -23
- package/resources/gofer/commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/commands/gofer_constitution.md +183 -23
- package/resources/gofer/commands/gofer_diagnose.md +183 -23
- package/resources/gofer/commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/commands/gofer_hydrate.md +183 -23
- package/resources/gofer/commands/gofer_personality.md +117 -6
- package/resources/gofer/commands/gofer_plan.md +117 -6
- package/resources/gofer/commands/gofer_side.md +117 -6
- package/resources/gofer/commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/commands/gofer_tdd.md +183 -23
- package/resources/gofer/commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/config/typesafe-learning-review.json +16 -0
- package/resources/gofer/config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/copilot-instructions/gofer-cost.instructions.md +15 -6
- package/resources/gofer/copilot-instructions/typescript.instructions.md +1 -1
- package/resources/gofer/copilot-prompts/eai-update.prompt.md +58 -0
- package/resources/gofer/copilot-prompts/eai.prompt.md +367 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.md +22 -0
- package/resources/gofer/gemini/commands/gofer/eai-update.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/eai.md +357 -0
- package/resources/gofer/gemini/commands/gofer/eai.toml +2 -0
- package/resources/gofer/gemini/commands/gofer/manifest.json +3 -27
- package/resources/gofer/gemini/extension.json +4 -4
- package/resources/gofer/github-agents/eai.agent.md +83 -0
- package/resources/gofer/github-agents/gofer-business.agent.md +73 -4
- package/resources/gofer/github-agents/gofer-implement.agent.md +71 -2
- package/resources/gofer/github-agents/gofer-plan.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-research.agent.md +72 -2
- package/resources/gofer/github-agents/gofer-validate.agent.md +72 -2
- package/resources/gofer/github-skills/eai/SKILL.md +365 -0
- package/resources/gofer/github-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/github-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/grok-skills/eai/SKILL.md +267 -14
- package/resources/gofer/grok-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/hook-scripts/user-prompt-submit.mjs +58 -13
- package/resources/gofer/instruction-templates/base/agents-base.md +8 -4
- package/resources/gofer/instruction-templates/gofer/gofer-claude.md +7 -5
- package/resources/gofer/instruction-templates/gofer/gofer-copilot.md +10 -8
- package/resources/gofer/memory/constitution.md +9 -7
- package/resources/gofer/memory/gofer-model-policy.yaml +23 -1
- package/resources/gofer/memory/lessons.md +0 -31
- package/resources/gofer/node-scripts/canonical-descriptions.mjs +1 -1
- package/resources/gofer/node-scripts/check-version-alignment.mjs +4 -7
- package/resources/gofer/node-scripts/delivery-lineage-corpus.mjs +156 -0
- package/resources/gofer/node-scripts/generate-commands.mjs +1073 -265
- package/resources/gofer/node-scripts/gofer-acceptance-check.mjs +143 -0
- package/resources/gofer/node-scripts/gofer-agent-catalog.mjs +271 -0
- package/resources/gofer/node-scripts/gofer-benchmark-executor.mjs +123 -0
- package/resources/gofer/node-scripts/gofer-benchmark-sign.mjs +62 -0
- package/resources/gofer/node-scripts/gofer-benchmark-signer.mjs +89 -0
- package/resources/gofer/node-scripts/gofer-benchmark.mjs +76 -0
- package/resources/gofer/node-scripts/gofer-blocker-control.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-claude-adapter.mjs +154 -0
- package/resources/gofer/node-scripts/gofer-closed-loop-audit.mjs +19 -2
- package/resources/gofer/node-scripts/gofer-delivery-check.mjs +121 -0
- package/resources/gofer/node-scripts/gofer-encrypted-key.mjs +58 -0
- package/resources/gofer/node-scripts/gofer-execution-metrics.mjs +477 -0
- package/resources/gofer/node-scripts/gofer-execution-recovery.mjs +334 -0
- package/resources/gofer/node-scripts/gofer-grok-adapter.mjs +184 -0
- package/resources/gofer/node-scripts/gofer-heldout-corpus.mjs +73 -0
- package/resources/gofer/node-scripts/gofer-heldout-snapshot.mjs +286 -0
- package/resources/gofer/node-scripts/gofer-heldout-verifier.mjs +211 -0
- package/resources/gofer/node-scripts/gofer-host-capability.mjs +335 -0
- package/resources/gofer/node-scripts/gofer-independent-reviewer.mjs +130 -0
- package/resources/gofer/node-scripts/gofer-live-routing.mjs +109 -0
- package/resources/gofer/node-scripts/gofer-local-capability-issuer.mjs +71 -0
- package/resources/gofer/node-scripts/gofer-local-isolation.mjs +175 -0
- package/resources/gofer/node-scripts/gofer-local-settings-cleanup.mjs +388 -0
- package/resources/gofer/node-scripts/gofer-loop-audit.mjs +54 -3
- package/resources/gofer/node-scripts/gofer-native-adapter.mjs +567 -0
- package/resources/gofer/node-scripts/gofer-native-benchmark-dispatch.mjs +145 -0
- package/resources/gofer/node-scripts/gofer-native-runtime.mjs +169 -0
- package/resources/gofer/node-scripts/gofer-pre-edit-check.mjs +204 -0
- package/resources/gofer/node-scripts/gofer-priority-check.mjs +214 -0
- package/resources/gofer/node-scripts/gofer-response-check.mjs +56 -0
- package/resources/gofer/node-scripts/gofer-reviewed-learning.mjs +1232 -0
- package/resources/gofer/node-scripts/gofer-run-verified-task.mjs +330 -0
- package/resources/gofer/node-scripts/gofer-runtime-ledger.mjs +289 -0
- package/resources/gofer/node-scripts/gofer-semantic-drift.mjs +584 -0
- package/resources/gofer/node-scripts/gofer-surface-update.mjs +1178 -0
- package/resources/gofer/node-scripts/gofer-trust-bootstrap.mjs +77 -0
- package/resources/gofer/node-scripts/gofer-trusted-benchmark.mjs +102 -0
- package/resources/gofer/node-scripts/gofer-trusted-evaluator.mjs +274 -0
- package/resources/gofer/node-scripts/gofer-tty-prompt.mjs +34 -0
- package/resources/gofer/node-scripts/gofer-typesafe-credentials.mjs +435 -0
- package/resources/gofer/node-scripts/gofer-ui-preview.mjs +1212 -0
- package/resources/gofer/node-scripts/gofer-verified-execution.mjs +484 -0
- package/resources/gofer/node-scripts/gofer-verifier-key-ceremony.mjs +101 -0
- package/resources/gofer/node-scripts/gofer-worktree-layout.mjs +32 -0
- package/resources/gofer/node-scripts/object-type-routing-phase-bundle.mjs +360 -0
- package/resources/gofer/node-scripts/package-agent-plugin.mjs +574 -82
- package/resources/gofer/node-scripts/parse-stage-command.mjs +1 -1
- package/resources/gofer/node-scripts/render-delivery-lineage.mjs +178 -0
- package/resources/gofer/node-scripts/sync-extension-resources.mjs +78 -2
- package/resources/gofer/node-scripts/validate-object-type-identifiers.mjs +767 -0
- package/resources/gofer/node-scripts/validate-object-type-routing-workspace.mjs +1325 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.d.mts +44 -0
- package/resources/gofer/node-scripts/validate-v4-resource-contract.mjs +957 -0
- package/resources/gofer/node-scripts/workspace-bootstrap-lib.mjs +685 -116
- package/resources/gofer/references/agent-catalog.json +126 -0
- package/resources/gofer/references/blocker-mediation.md +156 -0
- package/resources/gofer/references/business-updates-and-goal-checks.md +109 -0
- package/resources/gofer/references/delivery-lineage.md +48 -0
- package/resources/gofer/references/mvp-capability-validation.md +102 -0
- package/resources/gofer/references/platform/README.md +1 -1
- package/resources/gofer/references/platform/eai-app-template.md +15 -13
- package/resources/gofer/references/platform/eai-auth-access.md +104 -0
- package/resources/gofer/references/platform/eai-config-driven-ui.md +5 -5
- package/resources/gofer/references/platform/eai-error-catalog.yaml +77 -19
- package/resources/gofer/references/platform/eai-repo-contract.md +61 -23
- package/resources/gofer/references/platform/eai-service-patterns.md +102 -105
- package/resources/gofer/references/platform/eai.md +44 -8
- package/resources/gofer/references/platform/vertical-template.md +10 -9
- package/resources/gofer/references/priority-outcome-protection.md +152 -0
- package/resources/gofer/references/verified-agent-execution.md +179 -0
- package/resources/gofer/schemas/gofer-learning-trace-v1.json +70 -0
- package/resources/gofer/specify-commands/0_gofer_start.md +440 -99
- package/resources/gofer/specify-commands/0a_problem_validation.md +187 -27
- package/resources/gofer/specify-commands/10_gofer_cloud.md +183 -23
- package/resources/gofer/specify-commands/1_gofer_research.md +239 -30
- package/resources/gofer/specify-commands/2_gofer_specify.md +239 -35
- package/resources/gofer/specify-commands/3_gofer_plan.md +259 -43
- package/resources/gofer/specify-commands/4_gofer_tasks.md +401 -81
- package/resources/gofer/specify-commands/5_gofer_implement.md +375 -84
- package/resources/gofer/specify-commands/6_gofer_validate.md +284 -53
- package/resources/gofer/specify-commands/7_gofer_save.md +187 -26
- package/resources/gofer/specify-commands/7a_stakeholder_comms.md +183 -23
- package/resources/gofer/specify-commands/8_gofer_branding.md +183 -23
- package/resources/gofer/specify-commands/9_gofer_tests.md +212 -23
- package/resources/gofer/specify-commands/gofer_bootstrap_workspace.md +121 -8
- package/resources/gofer/specify-commands/gofer_check_workspace.md +123 -8
- package/resources/gofer/specify-commands/gofer_constitution.md +183 -23
- package/resources/gofer/specify-commands/gofer_diagnose.md +183 -23
- package/resources/gofer/specify-commands/gofer_eai_first_run.md +254 -25
- package/resources/gofer/specify-commands/gofer_hydrate.md +183 -23
- package/resources/gofer/specify-commands/gofer_personality.md +117 -6
- package/resources/gofer/specify-commands/gofer_plan.md +117 -6
- package/resources/gofer/specify-commands/gofer_side.md +117 -6
- package/resources/gofer/specify-commands/gofer_spec_summary.md +183 -23
- package/resources/gofer/specify-commands/gofer_tdd.md +183 -23
- package/resources/gofer/specify-commands/gofer_vocabulary.md +185 -25
- package/resources/gofer/specify-commands/gofer_zoom_out.md +183 -23
- package/resources/gofer/specify-config/object-type-routing.json +24 -0
- package/resources/gofer/specify-config/typesafe-learning-review.json +16 -0
- package/resources/gofer/specify-config/typesafe-semantic-review.json +9 -0
- package/resources/gofer/system-skills/eai/SKILL.md +365 -0
- package/resources/gofer/system-skills/eai-update/SKILL.md +56 -0
- package/resources/gofer/system-skills/gofer-documentation/SKILL.md +93 -0
- package/resources/gofer/templates/build-map-template.md +64 -0
- package/resources/gofer/templates/business-scenarios-template.json +14 -0
- package/resources/gofer/templates/ciso-security-summary-template.md +3 -3
- package/resources/gofer/templates/context-bundle-template.md +4 -3
- package/resources/gofer/templates/contract-pack-template.md +8 -8
- package/resources/gofer/templates/cto-architecture-summary-template.md +3 -3
- package/resources/gofer/templates/delivery-lineage-template.json +8 -0
- package/resources/gofer/templates/eai-preflight-template.md +109 -68
- package/resources/gofer/templates/goal-ledger-template.json +1 -1
- package/resources/gofer/templates/gofer-model-policy.yaml +28 -6
- package/resources/gofer/templates/loop-contract-template.json +3 -1
- package/resources/gofer/templates/plan-template.md +8 -7
- package/resources/gofer/templates/priority-plan-template.json +29 -0
- package/resources/gofer/templates/proposal-review-template.md +6 -3
- package/resources/gofer/templates/release-capability-ledger-template.md +24 -0
- package/resources/gofer/templates/research-template.md +7 -6
- package/resources/gofer/templates/spec-summary-template.md +1 -1
- package/resources/gofer/templates/spec-template.md +45 -6
- package/resources/gofer/templates/tasks-template.md +12 -3
- package/resources/gofer/templates/ui-preview-brief-template.md +55 -5
- package/resources/gofer/templates/ui-review-log-template.md +21 -3
- package/resources/gofer/templates/ui-show-and-tell-template.md +46 -0
- package/resources/gofer/templates/visuals/capability-heatmap-template.md +2 -2
- package/resources/gofer/templates/working-backwards-prfaq-template.md +9 -8
- package/resources/linked-sources.json +4 -4
- package/resources/gofer/agents-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/agents-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/agents-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/agents-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/agents-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/agents-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/agents-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/agents-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/agents-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/agents-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/agents-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/agents-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/agents-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/agents-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/agents-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/agents-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/agents-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/agents-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/agents-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/agents-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/agents-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/agents-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/agents-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/agents-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/claude-commands/0_gofer_start.md +0 -1282
- package/resources/gofer/claude-commands/0a_problem_validation.md +0 -424
- package/resources/gofer/claude-commands/10_gofer_cloud.md +0 -630
- package/resources/gofer/claude-commands/1_gofer_research.md +0 -1154
- package/resources/gofer/claude-commands/2_gofer_specify.md +0 -855
- package/resources/gofer/claude-commands/3_gofer_plan.md +0 -894
- package/resources/gofer/claude-commands/4_gofer_tasks.md +0 -755
- package/resources/gofer/claude-commands/5_gofer_implement.md +0 -939
- package/resources/gofer/claude-commands/6_gofer_validate.md +0 -2297
- package/resources/gofer/claude-commands/7_gofer_save.md +0 -418
- package/resources/gofer/claude-commands/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/claude-commands/8_gofer_branding.md +0 -310
- package/resources/gofer/claude-commands/9_gofer_tests.md +0 -553
- package/resources/gofer/claude-commands/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/claude-commands/gofer_check_workspace.md +0 -114
- package/resources/gofer/claude-commands/gofer_constitution.md +0 -607
- package/resources/gofer/claude-commands/gofer_diagnose.md +0 -109
- package/resources/gofer/claude-commands/gofer_eai_first_run.md +0 -392
- package/resources/gofer/claude-commands/gofer_hydrate.md +0 -556
- package/resources/gofer/claude-commands/gofer_personality.md +0 -39
- package/resources/gofer/claude-commands/gofer_plan.md +0 -35
- package/resources/gofer/claude-commands/gofer_side.md +0 -40
- package/resources/gofer/claude-commands/gofer_spec_summary.md +0 -107
- package/resources/gofer/claude-commands/gofer_tdd.md +0 -107
- package/resources/gofer/claude-commands/gofer_vocabulary.md +0 -107
- package/resources/gofer/claude-commands/gofer_zoom_out.md +0 -107
- package/resources/gofer/claude-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/copilot-prompts/0_gofer_start.prompt.md +0 -1308
- package/resources/gofer/copilot-prompts/0a_problem_validation.prompt.md +0 -429
- package/resources/gofer/copilot-prompts/10_gofer_cloud.prompt.md +0 -644
- package/resources/gofer/copilot-prompts/1_gofer_research.prompt.md +0 -1177
- package/resources/gofer/copilot-prompts/2_gofer_specify.prompt.md +0 -878
- package/resources/gofer/copilot-prompts/3_gofer_plan.prompt.md +0 -917
- package/resources/gofer/copilot-prompts/4_gofer_tasks.prompt.md +0 -778
- package/resources/gofer/copilot-prompts/5_gofer_implement.prompt.md +0 -962
- package/resources/gofer/copilot-prompts/6_gofer_validate.prompt.md +0 -2310
- package/resources/gofer/copilot-prompts/7_gofer_save.prompt.md +0 -433
- package/resources/gofer/copilot-prompts/7a_stakeholder_comms.prompt.md +0 -516
- package/resources/gofer/copilot-prompts/8_gofer_branding.prompt.md +0 -323
- package/resources/gofer/copilot-prompts/9_gofer_tests.prompt.md +0 -566
- package/resources/gofer/copilot-prompts/gofer_bootstrap_workspace.prompt.md +0 -161
- package/resources/gofer/copilot-prompts/gofer_check_workspace.prompt.md +0 -132
- package/resources/gofer/copilot-prompts/gofer_constitution.prompt.md +0 -621
- package/resources/gofer/copilot-prompts/gofer_diagnose.prompt.md +0 -127
- package/resources/gofer/copilot-prompts/gofer_eai_first_run.prompt.md +0 -410
- package/resources/gofer/copilot-prompts/gofer_hydrate.prompt.md +0 -571
- package/resources/gofer/copilot-prompts/gofer_personality.prompt.md +0 -57
- package/resources/gofer/copilot-prompts/gofer_plan.prompt.md +0 -53
- package/resources/gofer/copilot-prompts/gofer_side.prompt.md +0 -58
- package/resources/gofer/copilot-prompts/gofer_spec_summary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_tdd.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_vocabulary.prompt.md +0 -125
- package/resources/gofer/copilot-prompts/gofer_zoom_out.prompt.md +0 -125
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.md +0 -1282
- package/resources/gofer/gemini/commands/gofer/0_gofer_start.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.md +0 -424
- package/resources/gofer/gemini/commands/gofer/0a_problem_validation.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.md +0 -630
- package/resources/gofer/gemini/commands/gofer/10_gofer_cloud.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.md +0 -1154
- package/resources/gofer/gemini/commands/gofer/1_gofer_research.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.md +0 -855
- package/resources/gofer/gemini/commands/gofer/2_gofer_specify.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.md +0 -894
- package/resources/gofer/gemini/commands/gofer/3_gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.md +0 -755
- package/resources/gofer/gemini/commands/gofer/4_gofer_tasks.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.md +0 -939
- package/resources/gofer/gemini/commands/gofer/5_gofer_implement.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.md +0 -2297
- package/resources/gofer/gemini/commands/gofer/6_gofer_validate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.md +0 -418
- package/resources/gofer/gemini/commands/gofer/7_gofer_save.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.md +0 -503
- package/resources/gofer/gemini/commands/gofer/7a_stakeholder_comms.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.md +0 -310
- package/resources/gofer/gemini/commands/gofer/8_gofer_branding.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.md +0 -553
- package/resources/gofer/gemini/commands/gofer/9_gofer_tests.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.md +0 -143
- package/resources/gofer/gemini/commands/gofer/gofer_bootstrap_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.md +0 -114
- package/resources/gofer/gemini/commands/gofer/gofer_check_workspace.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.md +0 -607
- package/resources/gofer/gemini/commands/gofer/gofer_constitution.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.md +0 -109
- package/resources/gofer/gemini/commands/gofer/gofer_diagnose.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.md +0 -392
- package/resources/gofer/gemini/commands/gofer/gofer_eai_first_run.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.md +0 -556
- package/resources/gofer/gemini/commands/gofer/gofer_hydrate.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_personality.md +0 -39
- package/resources/gofer/gemini/commands/gofer/gofer_personality.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_plan.md +0 -35
- package/resources/gofer/gemini/commands/gofer/gofer_plan.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_side.md +0 -40
- package/resources/gofer/gemini/commands/gofer/gofer_side.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_spec_summary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_tdd.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_vocabulary.toml +0 -2
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.md +0 -107
- package/resources/gofer/gemini/commands/gofer/gofer_zoom_out.toml +0 -2
- package/resources/gofer/github-skills/eai-gofer/SKILL.md +0 -56
- package/resources/gofer/system-skills/0_gofer_start/SKILL.md +0 -1287
- package/resources/gofer/system-skills/0a_problem_validation/SKILL.md +0 -429
- package/resources/gofer/system-skills/10_gofer_cloud/SKILL.md +0 -635
- package/resources/gofer/system-skills/1_gofer_research/SKILL.md +0 -1159
- package/resources/gofer/system-skills/2_gofer_specify/SKILL.md +0 -860
- package/resources/gofer/system-skills/3_gofer_plan/SKILL.md +0 -899
- package/resources/gofer/system-skills/4_gofer_tasks/SKILL.md +0 -760
- package/resources/gofer/system-skills/5_gofer_implement/SKILL.md +0 -944
- package/resources/gofer/system-skills/6_gofer_validate/SKILL.md +0 -2302
- package/resources/gofer/system-skills/7_gofer_save/SKILL.md +0 -423
- package/resources/gofer/system-skills/7a_stakeholder_comms/SKILL.md +0 -508
- package/resources/gofer/system-skills/8_gofer_branding/SKILL.md +0 -315
- package/resources/gofer/system-skills/9_gofer_tests/SKILL.md +0 -558
- package/resources/gofer/system-skills/gofer_bootstrap_workspace/SKILL.md +0 -148
- package/resources/gofer/system-skills/gofer_check_workspace/SKILL.md +0 -119
- package/resources/gofer/system-skills/gofer_constitution/SKILL.md +0 -612
- package/resources/gofer/system-skills/gofer_diagnose/SKILL.md +0 -114
- package/resources/gofer/system-skills/gofer_eai_first_run/SKILL.md +0 -397
- package/resources/gofer/system-skills/gofer_hydrate/SKILL.md +0 -561
- package/resources/gofer/system-skills/gofer_personality/SKILL.md +0 -44
- package/resources/gofer/system-skills/gofer_plan/SKILL.md +0 -40
- package/resources/gofer/system-skills/gofer_side/SKILL.md +0 -45
- package/resources/gofer/system-skills/gofer_spec_summary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_tdd/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_vocabulary/SKILL.md +0 -112
- package/resources/gofer/system-skills/gofer_zoom_out/SKILL.md +0 -112
- package/resources/gofer/templates/ui-approval-template.md +0 -32
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Production entrypoint for the verified native runtime. Composes the real
|
|
4
|
+
* capability issuer, the real append-only ledger, and `createVerifiedNativeRuntime`
|
|
5
|
+
* to run one bounded, ledger-authorized native task in an isolated worktree.
|
|
6
|
+
*
|
|
7
|
+
* This is deliberately the only command that calls `createVerifiedNativeRuntime`
|
|
8
|
+
* outside of a test. It fails closed with TRUSTED_EVALUATOR_REQUIRED when no
|
|
9
|
+
* local trust identity is registered — that is expected until the account
|
|
10
|
+
* trust store is provisioned; it is not a sign the wiring is missing.
|
|
11
|
+
*/
|
|
12
|
+
import { execFileSync } from 'node:child_process';
|
|
13
|
+
import { createHash } from 'node:crypto';
|
|
14
|
+
import { constants } from 'node:fs';
|
|
15
|
+
import { promises as fs } from 'node:fs';
|
|
16
|
+
import { fileURLToPath } from 'node:url';
|
|
17
|
+
import path from 'node:path';
|
|
18
|
+
import process from 'process';
|
|
19
|
+
import { createVerifiedNativeRuntime } from './gofer-native-runtime.mjs';
|
|
20
|
+
import { createVerifiedWorktree, disposeVerifiedWorktree } from './gofer-native-adapter.mjs';
|
|
21
|
+
import { createRuntimeLedger } from './gofer-runtime-ledger.mjs';
|
|
22
|
+
import { issueLocalCapabilityReceipt } from './gofer-local-capability-issuer.mjs';
|
|
23
|
+
import { inspectEaiLocalIsolation } from './gofer-local-isolation.mjs';
|
|
24
|
+
|
|
25
|
+
const SMOKE_TASK_ID = 'T001';
|
|
26
|
+
const SMOKE_CHECK_NAME = 'smoke-file-check';
|
|
27
|
+
const SMOKE_FILE_NAME = 'NATIVE_SMOKE_PROOF.md';
|
|
28
|
+
const SMOKE_FILE_CONTENT = 'native wiring smoke test passed.\n';
|
|
29
|
+
const noFollowFlag = process.platform === 'win32' ? 0 : constants.O_NOFOLLOW;
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
const args = { workspace: '', featureDir: '', capabilityReceipt: '', benchmark: '' };
|
|
33
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
34
|
+
const arg = argv[index];
|
|
35
|
+
if (arg === '--workspace') args.workspace = argv[++index] || '';
|
|
36
|
+
else if (arg === '--feature-dir') args.featureDir = argv[++index] || '';
|
|
37
|
+
else if (arg === '--capability-receipt') args.capabilityReceipt = argv[++index] || '';
|
|
38
|
+
else if (arg === '--benchmark') args.benchmark = argv[++index] || '';
|
|
39
|
+
else throw new Error(`Unknown argument: ${arg}`);
|
|
40
|
+
}
|
|
41
|
+
if (!path.isAbsolute(args.workspace)) throw new Error('--workspace <absolute-path> is required');
|
|
42
|
+
// A receipt and a signed benchmark only make sense together.
|
|
43
|
+
if (Boolean(args.capabilityReceipt) !== Boolean(args.benchmark) ||
|
|
44
|
+
[args.capabilityReceipt, args.benchmark].some(value => value && !path.isAbsolute(value))) {
|
|
45
|
+
throw new Error('--capability-receipt and --benchmark need absolute paths and must be given together');
|
|
46
|
+
}
|
|
47
|
+
return args;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function readJsonFile(filename) {
|
|
51
|
+
const file = await fs.open(filename, constants.O_RDONLY | constants.O_NOFOLLOW);
|
|
52
|
+
try {
|
|
53
|
+
const info = await file.stat();
|
|
54
|
+
if (!info.isFile() || info.size > 1024 * 1024) throw new Error('JSON input must be a regular file under 1 MiB');
|
|
55
|
+
return JSON.parse(await file.readFile('utf8'));
|
|
56
|
+
} finally { await file.close(); }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function gitHead(workspaceRoot) {
|
|
60
|
+
return execFileSync('git', ['-C', workspaceRoot, 'rev-parse', 'HEAD'], { encoding: 'utf8' }).trim();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async function confinedFeatureDirectory(workspace, requested) {
|
|
64
|
+
const root = await fs.realpath(workspace);
|
|
65
|
+
const target = path.resolve(requested || path.join(root, '.specify', 'specs', 'native-runtime-smoke'));
|
|
66
|
+
const relative = path.relative(root, target);
|
|
67
|
+
if (relative === '..' || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
|
|
68
|
+
throw new Error('FEATURE_DIRECTORY_OUTSIDE_WORKSPACE');
|
|
69
|
+
}
|
|
70
|
+
let current = root;
|
|
71
|
+
for (const component of relative.split(path.sep).filter(Boolean)) {
|
|
72
|
+
current = path.join(current, component);
|
|
73
|
+
const info = await fs.lstat(current).catch((error) => {
|
|
74
|
+
if (error?.code === 'ENOENT') return null;
|
|
75
|
+
throw error;
|
|
76
|
+
});
|
|
77
|
+
if (!info) break;
|
|
78
|
+
if (info.isSymbolicLink()) throw new Error('FEATURE_DIRECTORY_SYMLINK');
|
|
79
|
+
}
|
|
80
|
+
return target;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Build the minimal set of controller documents `reviewPriority` requires,
|
|
84
|
+
* describing exactly one bounded, inert smoke task. Kept in the controller's
|
|
85
|
+
* source-side feature directory, never the isolated task worktree (D029). */
|
|
86
|
+
export async function prepareSmokeFeature(featureDir, revision) {
|
|
87
|
+
await fs.mkdir(featureDir, { recursive: true, mode: 0o700 });
|
|
88
|
+
await fs.writeFile(path.join(featureDir, 'spec.md'),
|
|
89
|
+
'# Native Runtime Smoke Task\n\n' +
|
|
90
|
+
'- FR-001: Create one fixed-content proof file and change nothing else.\n', { mode: 0o600 });
|
|
91
|
+
await fs.writeFile(path.join(featureDir, 'decisions.md'),
|
|
92
|
+
'## D001 - Run one bounded native smoke task\n\n' +
|
|
93
|
+
'The user approved wiring the verified native runtime into a real command ' +
|
|
94
|
+
'and running one bounded, inert smoke task to prove the dispatch path.\n', { mode: 0o600 });
|
|
95
|
+
await fs.writeFile(path.join(featureDir, 'plan.md'),
|
|
96
|
+
'# Plan\n\nRun one bounded native task in a verified local worktree.\n', { mode: 0o600 });
|
|
97
|
+
await fs.writeFile(path.join(featureDir, 'loop-contract.json'), `${JSON.stringify({
|
|
98
|
+
schemaVersion: 1, requirePriorityPlan: true, loopId: 'native-runtime-smoke',
|
|
99
|
+
profile: 'standard', objective: 'Prove the verified native runtime dispatches one real task end to end.',
|
|
100
|
+
entryStage: '0_gofer_start', maxIterations: 2, budget: {}, evalCommands: [],
|
|
101
|
+
successCriteria: [], stopConditions: [],
|
|
102
|
+
humanEscalation: { maxFailedIterations: 1, owner: 'Gofer maintainers', escalateWhen: ['native host authority is unavailable'] },
|
|
103
|
+
}, null, 2)}\n`, { mode: 0o600 });
|
|
104
|
+
await fs.writeFile(path.join(featureDir, 'tasks.md'),
|
|
105
|
+
`- [ ] **${SMOKE_TASK_ID}** Create ${SMOKE_FILE_NAME} with fixed content.\n`, { mode: 0o600 });
|
|
106
|
+
await fs.writeFile(path.join(featureDir, 'traceability.md'),
|
|
107
|
+
'| Requirement | Task |\n| --- | --- |\n' +
|
|
108
|
+
`| FR-001 | ${SMOKE_TASK_ID} |\n`, { mode: 0o600 });
|
|
109
|
+
const plan = {
|
|
110
|
+
schemaVersion: 2,
|
|
111
|
+
revision,
|
|
112
|
+
objective: 'Prove the verified native runtime dispatches one real task end to end.',
|
|
113
|
+
lastInstruction: { id: 'D001', text: 'The user approved wiring the verified native runtime into a real command and running one bounded, inert smoke task to prove the dispatch path.' },
|
|
114
|
+
decisionPolicy: { mode: 'goal-led', askOnlyFor: ['goal-change', 'irreversible-action', 'missing-authority'] },
|
|
115
|
+
criticalPath: [SMOKE_TASK_ID],
|
|
116
|
+
tasks: { [SMOKE_TASK_ID]: { dependsOn: [], allowedEditScope: [SMOKE_FILE_NAME] } },
|
|
117
|
+
outcome: {
|
|
118
|
+
id: 'OUTCOME-SMOKE-001',
|
|
119
|
+
statement: 'A native host performs one scoped, ledger-governed smoke task in verified isolation.',
|
|
120
|
+
requirements: ['FR-001'],
|
|
121
|
+
target: { environment: 'local-native-host', revision },
|
|
122
|
+
receipt: 'evidence/outcome.json',
|
|
123
|
+
},
|
|
124
|
+
};
|
|
125
|
+
await fs.writeFile(path.join(featureDir, 'priority-plan.json'), `${JSON.stringify(plan, null, 2)}\n`, { mode: 0o600 });
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/** The worker-facing adapter. `reserve`/`lease` delegate straight to the same
|
|
129
|
+
* ledger the graph engine authorizes and commits against; `check`/`verified`
|
|
130
|
+
* inspect the isolated worktree directly rather than trusting worker claims. */
|
|
131
|
+
export function createSmokeAdapter({ ledger }) {
|
|
132
|
+
return {
|
|
133
|
+
bindWorkspace({ workspaceRoot }) {
|
|
134
|
+
return {
|
|
135
|
+
workspaceRoot,
|
|
136
|
+
reserve: request => ledger.reserve(request),
|
|
137
|
+
lease: request => ledger.lease(request),
|
|
138
|
+
async inputRevision() { return gitHead(workspaceRoot); },
|
|
139
|
+
async check({ taskId, revision, inputRevision, check }) {
|
|
140
|
+
const target = path.join(workspaceRoot, SMOKE_FILE_NAME);
|
|
141
|
+
const actual = await fs.readFile(target, 'utf8').catch(() => null);
|
|
142
|
+
const executed = true;
|
|
143
|
+
const exitCode = actual === SMOKE_FILE_CONTENT ? 0 : 1;
|
|
144
|
+
return { taskId, revision, inputRevision, check, exitCode, executed,
|
|
145
|
+
receipt: createHash('sha256').update(`${taskId}:${inputRevision}:${actual ?? ''}`).digest('hex') };
|
|
146
|
+
},
|
|
147
|
+
async verified({ taskId, revision, inputRevision }) {
|
|
148
|
+
const target = path.join(workspaceRoot, SMOKE_FILE_NAME);
|
|
149
|
+
const actual = await fs.readFile(target, 'utf8').catch(() => null);
|
|
150
|
+
if (actual !== SMOKE_FILE_CONTENT) throw new Error('SMOKE_TASK_OUTPUT_MISMATCH');
|
|
151
|
+
return { committed: true, taskId, revision, inputRevision,
|
|
152
|
+
receipt: createHash('sha256').update(`verified:${taskId}:${inputRevision}`).digest('hex') };
|
|
153
|
+
},
|
|
154
|
+
};
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Capability issuance needs an already-isolated worktree to probe, but the
|
|
160
|
+
* receipt itself attests host/model capability, not a specific worktree's
|
|
161
|
+
* identity. Issue it against a disposable probe worktree, exactly like the
|
|
162
|
+
* live capability diagnostic already recorded for this feature, then reuse
|
|
163
|
+
* the receipt for the separate, real task worktree below. */
|
|
164
|
+
async function issueDisposableCapabilityReceipt(workspace, host) {
|
|
165
|
+
const probe = await createVerifiedWorktree({ workspaceRoot: workspace, host,
|
|
166
|
+
localIsolation: inspectEaiLocalIsolation });
|
|
167
|
+
try {
|
|
168
|
+
return await issueLocalCapabilityReceipt({ workspaceRoot: probe.isolatedWorkspace });
|
|
169
|
+
} finally {
|
|
170
|
+
await disposeVerifiedWorktree({ workspaceRoot: probe.workspace,
|
|
171
|
+
isolatedWorkspace: probe.isolatedWorkspace, revision: probe.revision,
|
|
172
|
+
receipt: probe.receipt }).catch(() => {});
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/** `capabilityReceipt` and `benchmark` ({ evidence, attestation }) may be supplied
|
|
177
|
+
* together to route by a signed independent benchmark. Without them the runner
|
|
178
|
+
* issues its own receipt and the routing gate correctly refuses to route. */
|
|
179
|
+
export async function runVerifiedSmokeTask({ workspace, featureDir, host = 'codex',
|
|
180
|
+
capabilityReceipt: suppliedReceipt, benchmark }) {
|
|
181
|
+
const revision = gitHead(workspace);
|
|
182
|
+
const resolvedFeatureDir = await confinedFeatureDirectory(workspace, featureDir);
|
|
183
|
+
// Complete every path-based controller write before any native worker can
|
|
184
|
+
// start. The worker receives only its isolated worktree and cannot reach the
|
|
185
|
+
// controller feature directory.
|
|
186
|
+
await prepareSmokeFeature(resolvedFeatureDir, revision);
|
|
187
|
+
// Bind every controller-side read and write to the same canonical directory.
|
|
188
|
+
// This prevents a caller-controlled symlink from redirecting evidence after
|
|
189
|
+
// the runtime has validated its feature root.
|
|
190
|
+
const controllerFeatureDir = await fs.realpath(resolvedFeatureDir);
|
|
191
|
+
const capabilityReceipt = suppliedReceipt ?? await issueDisposableCapabilityReceipt(workspace, host);
|
|
192
|
+
const ledger = await createRuntimeLedger({ ledgerPath: path.join(controllerFeatureDir, 'runtime-ledger.jsonl') });
|
|
193
|
+
const runtime = await createVerifiedNativeRuntime({
|
|
194
|
+
workspaceRoot: workspace,
|
|
195
|
+
host,
|
|
196
|
+
localIsolation: inspectEaiLocalIsolation,
|
|
197
|
+
capabilityReceipt,
|
|
198
|
+
requiredCapabilities: { isolationClass: 'git-worktree+local-os-sandbox' },
|
|
199
|
+
ledger,
|
|
200
|
+
promptForRequest: async () => `Create a file named ${SMOKE_FILE_NAME} in the repository root ` +
|
|
201
|
+
`containing exactly this one line: "${SMOKE_FILE_CONTENT.trim()}". Make no other change.`,
|
|
202
|
+
adapter: createSmokeAdapter({ ledger }),
|
|
203
|
+
});
|
|
204
|
+
// Create and retain the final evidence file descriptor before dispatch.
|
|
205
|
+
// Retirement writes through this descriptor and never resolves a path that
|
|
206
|
+
// a concurrent process could replace after the worker starts.
|
|
207
|
+
const evidenceSink = await openEvidenceSink(controllerFeatureDir);
|
|
208
|
+
let result;
|
|
209
|
+
try {
|
|
210
|
+
result = await runtime.run({
|
|
211
|
+
featureDir: controllerFeatureDir,
|
|
212
|
+
checks: { [SMOKE_TASK_ID]: [SMOKE_CHECK_NAME] },
|
|
213
|
+
approvalReceipt: 'local-smoke-approval',
|
|
214
|
+
benchmarkEvidence: benchmark?.evidence,
|
|
215
|
+
benchmarkAttestation: benchmark?.attestation,
|
|
216
|
+
maxCalls: 12,
|
|
217
|
+
maxConcurrent: 1,
|
|
218
|
+
deadlineMs: Date.now() + 300_000,
|
|
219
|
+
});
|
|
220
|
+
} catch (error) {
|
|
221
|
+
await evidenceSink.close().catch(() => {});
|
|
222
|
+
// A failed run keeps its worktree for recovery; say where it is instead of hiding it.
|
|
223
|
+
await runtime.dispose().catch(disposeError => process.stderr.write(
|
|
224
|
+
`Worktree kept at ${runtime.isolation.isolatedWorkspace}: ${disposeError.message}\n`));
|
|
225
|
+
throw error;
|
|
226
|
+
}
|
|
227
|
+
// A verified run must not leave a worktree behind, so disposal errors surface.
|
|
228
|
+
if (result.status !== 'verified' || result.adapterCallsSettled !== true) {
|
|
229
|
+
await evidenceSink.close().catch(() => {});
|
|
230
|
+
const location = runtime.isolation.isolatedWorkspace;
|
|
231
|
+
process.stderr.write(`Worktree kept at ${location}: native task requires recovery\n`);
|
|
232
|
+
throw new Error(`NATIVE_RUNTIME_REQUIRES_RECOVERY:${location}`);
|
|
233
|
+
}
|
|
234
|
+
try {
|
|
235
|
+
await retireVerifiedOutput({ isolatedWorkspace: runtime.isolation.isolatedWorkspace,
|
|
236
|
+
evidenceSink });
|
|
237
|
+
await evidenceSink.close();
|
|
238
|
+
await runtime.dispose();
|
|
239
|
+
} catch (error) {
|
|
240
|
+
await evidenceSink.close().catch(() => {});
|
|
241
|
+
process.stderr.write(
|
|
242
|
+
`Worktree kept at ${runtime.isolation.isolatedWorkspace}: ${error.message}\n`);
|
|
243
|
+
throw error;
|
|
244
|
+
}
|
|
245
|
+
return result;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function sameIdentity(left, right) {
|
|
249
|
+
if (left.dev === undefined || left.ino === undefined ||
|
|
250
|
+
right.dev === undefined || right.ino === undefined) return true;
|
|
251
|
+
return left.dev === right.dev && left.ino === right.ino;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
async function openEvidenceSink(featureDirectory) {
|
|
255
|
+
const evidenceDirectory = path.join(featureDirectory, 'evidence');
|
|
256
|
+
const featureHandle = await fs.open(featureDirectory,
|
|
257
|
+
constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
|
|
258
|
+
let evidenceHandle;
|
|
259
|
+
try {
|
|
260
|
+
const featureIdentity = await featureHandle.stat();
|
|
261
|
+
const featureAfterOpen = await fs.lstat(featureDirectory);
|
|
262
|
+
if (!featureIdentity.isDirectory() || featureAfterOpen.isSymbolicLink() ||
|
|
263
|
+
!sameIdentity(featureIdentity, featureAfterOpen)) {
|
|
264
|
+
throw new Error('CONTROLLER_FEATURE_DIRECTORY_CHANGED');
|
|
265
|
+
}
|
|
266
|
+
// Exclusive creation rejects every pre-existing path, including a symlink.
|
|
267
|
+
await fs.mkdir(evidenceDirectory, { mode: 0o700 });
|
|
268
|
+
evidenceHandle = await fs.open(evidenceDirectory,
|
|
269
|
+
constants.O_RDONLY | (constants.O_DIRECTORY ?? 0) | noFollowFlag);
|
|
270
|
+
const evidenceIdentity = await evidenceHandle.stat();
|
|
271
|
+
const [featureAfterCreate, evidenceAfterOpen] = await Promise.all([
|
|
272
|
+
fs.lstat(featureDirectory), fs.lstat(evidenceDirectory),
|
|
273
|
+
]);
|
|
274
|
+
if (!evidenceIdentity.isDirectory() || evidenceAfterOpen.isSymbolicLink() ||
|
|
275
|
+
!sameIdentity(evidenceIdentity, evidenceAfterOpen) ||
|
|
276
|
+
!sameIdentity(featureIdentity, featureAfterCreate)) {
|
|
277
|
+
throw new Error('CONTROLLER_EVIDENCE_DIRECTORY_CHANGED');
|
|
278
|
+
}
|
|
279
|
+
const evidencePath = path.join(evidenceDirectory, SMOKE_FILE_NAME);
|
|
280
|
+
const sink = await fs.open(evidencePath,
|
|
281
|
+
constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | noFollowFlag, 0o600);
|
|
282
|
+
const [featureAfterSinkOpen, evidenceAfterSinkOpen] = await Promise.all([
|
|
283
|
+
fs.lstat(featureDirectory), fs.lstat(evidenceDirectory),
|
|
284
|
+
]);
|
|
285
|
+
if (!sameIdentity(featureIdentity, featureAfterSinkOpen) ||
|
|
286
|
+
!sameIdentity(evidenceIdentity, evidenceAfterSinkOpen)) {
|
|
287
|
+
await sink.close();
|
|
288
|
+
throw new Error('CONTROLLER_EVIDENCE_DIRECTORY_CHANGED');
|
|
289
|
+
}
|
|
290
|
+
return sink;
|
|
291
|
+
} finally {
|
|
292
|
+
await evidenceHandle?.close();
|
|
293
|
+
await featureHandle.close();
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/** Keep the verified proof file as controller evidence, then remove it from the task
|
|
298
|
+
* worktree so the strict clean-state disposal check stays unchanged. */
|
|
299
|
+
async function retireVerifiedOutput({ isolatedWorkspace, evidenceSink }) {
|
|
300
|
+
const source = path.join(isolatedWorkspace, SMOKE_FILE_NAME);
|
|
301
|
+
const sourceHandle = await fs.open(source, constants.O_RDONLY | noFollowFlag);
|
|
302
|
+
let actual;
|
|
303
|
+
try {
|
|
304
|
+
const sourceInfo = await sourceHandle.stat();
|
|
305
|
+
if (!sourceInfo.isFile()) throw new Error('SMOKE_TASK_OUTPUT_INVALID');
|
|
306
|
+
actual = await sourceHandle.readFile('utf8');
|
|
307
|
+
} finally { await sourceHandle.close(); }
|
|
308
|
+
if (actual !== SMOKE_FILE_CONTENT) throw new Error('SMOKE_TASK_OUTPUT_MISMATCH');
|
|
309
|
+
const sinkInfo = await evidenceSink.stat();
|
|
310
|
+
if (!sinkInfo.isFile()) throw new Error('CONTROLLER_EVIDENCE_FILE_INVALID');
|
|
311
|
+
await evidenceSink.writeFile(actual);
|
|
312
|
+
await evidenceSink.sync();
|
|
313
|
+
await fs.rm(source);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
async function main(argv) {
|
|
317
|
+
const args = parseArgs(argv);
|
|
318
|
+
const capabilityReceipt = args.capabilityReceipt ? await readJsonFile(args.capabilityReceipt) : undefined;
|
|
319
|
+
const benchmark = args.benchmark ? await readJsonFile(args.benchmark) : undefined;
|
|
320
|
+
const result = await runVerifiedSmokeTask({ workspace: args.workspace,
|
|
321
|
+
featureDir: args.featureDir || undefined, capabilityReceipt, benchmark });
|
|
322
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
326
|
+
main(process.argv.slice(2)).catch(error => {
|
|
327
|
+
process.stderr.write(`${error.message}\n`);
|
|
328
|
+
process.exitCode = 1;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
/** Durable, append-only authority ledger for a single verified runtime. */
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { chmod, link, lstat, mkdir, open, readFile, unlink } from 'node:fs/promises';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { DatabaseSync } from 'node:sqlite';
|
|
6
|
+
|
|
7
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
8
|
+
const digest = value => createHash('sha256').update(JSON.stringify(value)).digest('hex');
|
|
9
|
+
const sameList = (left, right) => Array.isArray(left) && Array.isArray(right) &&
|
|
10
|
+
left.length === right.length && left.every((value, index) => value === right[index]);
|
|
11
|
+
|
|
12
|
+
export async function acquireLedgerLock(lockPath) {
|
|
13
|
+
let metadata = await lstat(lockPath).catch(error => {
|
|
14
|
+
if (error.code === 'ENOENT') return null;
|
|
15
|
+
throw error;
|
|
16
|
+
});
|
|
17
|
+
if (!metadata) {
|
|
18
|
+
const temporary = `${lockPath}.${randomUUID()}.init`;
|
|
19
|
+
try {
|
|
20
|
+
const initial = new DatabaseSync(temporary, { timeout: 0 });
|
|
21
|
+
try { initial.exec('CREATE TABLE lock_identity (id INTEGER PRIMARY KEY)'); }
|
|
22
|
+
finally { initial.close(); }
|
|
23
|
+
await chmod(temporary, 0o600);
|
|
24
|
+
try { await link(temporary, lockPath); }
|
|
25
|
+
catch (error) { if (error.code !== 'EEXIST') throw error; }
|
|
26
|
+
} finally { await unlink(temporary).catch(() => {}); }
|
|
27
|
+
metadata = await lstat(lockPath);
|
|
28
|
+
}
|
|
29
|
+
// A legacy, empty .lock file may still belong to an older live controller.
|
|
30
|
+
// Do not reinterpret it as an unlocked database during an upgrade.
|
|
31
|
+
if (!metadata.isFile() || metadata.size === 0 ||
|
|
32
|
+
(process.platform !== 'win32' && (metadata.mode & 0o077) !== 0)) {
|
|
33
|
+
throw new Error('LEDGER_LOCK_MIGRATION_REQUIRED');
|
|
34
|
+
}
|
|
35
|
+
let database;
|
|
36
|
+
try {
|
|
37
|
+
database = new DatabaseSync(lockPath, { timeout: 0 });
|
|
38
|
+
database.exec('BEGIN IMMEDIATE');
|
|
39
|
+
return database;
|
|
40
|
+
} catch (error) {
|
|
41
|
+
database?.close();
|
|
42
|
+
throw new Error(/database is locked|SQLITE_BUSY/i.test(error.message) ? 'LEDGER_BUSY' : 'LEDGER_LOCK_UNAVAILABLE');
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export async function createRuntimeLedger({ ledgerPath, now = () => new Date(), verifyCancellation,
|
|
47
|
+
verifyBenchmarkCapture } = {}) {
|
|
48
|
+
if (!path.isAbsolute(ledgerPath)) throw new Error('LEDGER_PATH_REQUIRED');
|
|
49
|
+
await mkdir(path.dirname(ledgerPath), { recursive: true, mode: 0o700 });
|
|
50
|
+
const lockPath = `${ledgerPath}.lock`;
|
|
51
|
+
const events = async () => (await readFile(ledgerPath, 'utf8').catch(error => {
|
|
52
|
+
if (error.code === 'ENOENT') return '';
|
|
53
|
+
throw error;
|
|
54
|
+
})).split('\n')
|
|
55
|
+
.filter(Boolean).map(line => JSON.parse(line));
|
|
56
|
+
const transact = async (event, evaluate) => {
|
|
57
|
+
const lock = await acquireLedgerLock(lockPath);
|
|
58
|
+
try {
|
|
59
|
+
const history = await events();
|
|
60
|
+
const result = await evaluate(history);
|
|
61
|
+
if (result?.allowed !== true) return result;
|
|
62
|
+
const record = { schemaVersion: 1, eventId: randomUUID(), at: now().toISOString(), ...event, ...result };
|
|
63
|
+
const append = await open(ledgerPath, 'a', 0o600);
|
|
64
|
+
try { await append.writeFile(`${JSON.stringify(record)}\n`); await append.sync(); } finally { await append.close(); }
|
|
65
|
+
return result;
|
|
66
|
+
} finally {
|
|
67
|
+
try { lock.exec('ROLLBACK'); } finally { lock.close(); }
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
const reservation = request => ({ taskId: request?.taskId, revision: request?.revision, attempt: request?.attempt,
|
|
71
|
+
budgetReservation: `reservation:${digest(request).slice(0, 24)}` });
|
|
72
|
+
return Object.freeze({
|
|
73
|
+
reserve: request => transact({ type: 'reserve', request }, async history => text(request?.taskId) && text(request?.revision) &&
|
|
74
|
+
Number.isSafeInteger(request?.attempt) && request.attempt > 0 &&
|
|
75
|
+
!history.some(item => item.type === 'reserve' && item.taskId === request.taskId &&
|
|
76
|
+
item.revision === request.revision && item.attempt >= request.attempt) &&
|
|
77
|
+
!history.some(item => item.type === 'lease' && item.taskId === request.taskId &&
|
|
78
|
+
!history.some(closed => closed.type === 'cancel-reconciled' && closed.leaseId === item.leaseId))
|
|
79
|
+
? { allowed: true, ...reservation(request) } : { allowed: false }),
|
|
80
|
+
lease: request => transact({ type: 'lease', request }, async history => {
|
|
81
|
+
const reserved = reservation(request);
|
|
82
|
+
if (!history.some(item => item.type === 'reserve' && item.budgetReservation === reserved.budgetReservation) ||
|
|
83
|
+
history.some(item => item.type === 'lease' && item.budgetReservation === reserved.budgetReservation)) return { allowed: false };
|
|
84
|
+
if (history.some(item => item.type === 'lease' && item.taskId === request.taskId &&
|
|
85
|
+
!history.some(closed => closed.type === 'cancel-reconciled' && closed.leaseId === item.leaseId))) return { allowed: false };
|
|
86
|
+
return { allowed: true, taskId: request.taskId, revision: request.revision, attempt: request.attempt,
|
|
87
|
+
budgetReservation: reserved.budgetReservation,
|
|
88
|
+
leaseId: `lease:${randomUUID()}`, expiresAt: new Date(now().getTime() + 300000).toISOString() };
|
|
89
|
+
}),
|
|
90
|
+
authorize: request => transact({ type: 'authorize', request }, async history => {
|
|
91
|
+
if (!text(request?.leaseId) || !text(request?.budgetReservation) || !text(request?.approvalReceipt) ||
|
|
92
|
+
!Array.isArray(request?.dependencies)) return { allowed: false };
|
|
93
|
+
const lease = history.find(item => item.type === 'lease' && item.leaseId === request.leaseId);
|
|
94
|
+
const reserved = history.find(item => item.type === 'reserve' &&
|
|
95
|
+
item.budgetReservation === request.budgetReservation);
|
|
96
|
+
const replacement = history.filter(item => item.type === 'cancel-reconciled' &&
|
|
97
|
+
item.taskId === request.taskId && item.revision === request.revision).at(-1);
|
|
98
|
+
if (!lease || !reserved || lease.taskId !== request.taskId || lease.revision !== request.revision ||
|
|
99
|
+
lease.attempt !== request.attempt || lease.budgetReservation !== request.budgetReservation ||
|
|
100
|
+
reserved.taskId !== request.taskId || reserved.revision !== request.revision ||
|
|
101
|
+
reserved.attempt !== request.attempt ||
|
|
102
|
+
digest(reserved.request?.dependencies) !== digest(request.dependencies) ||
|
|
103
|
+
reserved.request?.worktreeReceipt !== request.worktreeReceipt ||
|
|
104
|
+
!sameList(reserved.request?.allowedEditScope, request.allowedEditScope) ||
|
|
105
|
+
!sameList(reserved.request?.requiredChecks, request.requiredChecks) ||
|
|
106
|
+
reserved.request?.capabilityReceiptHash !== request.capabilityReceiptHash ||
|
|
107
|
+
reserved.request?.approvalReceipt !== request.approvalReceipt ||
|
|
108
|
+
reserved.request?.selectedModel !== request.selectedModel ||
|
|
109
|
+
reserved.request?.benchmarkReceipt !== request.benchmarkReceipt ||
|
|
110
|
+
Date.parse(lease.expiresAt) <= now().getTime() ||
|
|
111
|
+
(replacement && request.worktreeReceipt !== replacement.replacementWorktreeReceipt) ||
|
|
112
|
+
history.some(item => item.leaseId === request.leaseId &&
|
|
113
|
+
['authorize', 'cancel-reconciled'].includes(item.type))) return { allowed: false };
|
|
114
|
+
return { allowed: true, ...request, receipt: `authority:${digest(request).slice(0, 32)}` };
|
|
115
|
+
}),
|
|
116
|
+
authorizeNative: request => transact({ type: 'native-authorize', request }, async history => {
|
|
117
|
+
const authority = history.find(item => item.type === 'authorize' && item.leaseId === request?.leaseId && item.receipt === request?.ledgerAuthorityReceipt);
|
|
118
|
+
const lease = history.find(item => item.type === 'lease' && item.leaseId === request?.leaseId);
|
|
119
|
+
if (!authority || !lease || Date.parse(lease.expiresAt) <= now().getTime() ||
|
|
120
|
+
history.some(item => item.leaseId === request?.leaseId &&
|
|
121
|
+
['native-authorize', 'commit-authorize', 'cancel-reconciled'].includes(item.type)) ||
|
|
122
|
+
authority.taskId !== request.taskId || authority.revision !== request.objectiveRevision ||
|
|
123
|
+
authority.attempt !== request.attempt ||
|
|
124
|
+
authority.budgetReservation !== request.budgetReservation || authority.approvalReceipt !== request.approvalReceipt ||
|
|
125
|
+
authority.worktreeReceipt !== request.worktreeReceipt ||
|
|
126
|
+
authority.capabilityReceiptHash !== request.capabilityReceiptHash || digest(authority.dependencies) !== digest(request.dependencies) ||
|
|
127
|
+
authority.selectedModel !== request.selectedModel ||
|
|
128
|
+
!sameList(authority.allowedEditScope, request.allowedWriteScope)) return { allowed: false };
|
|
129
|
+
return { allowed: true, ...request, isolation: 'git-worktree+local-os-sandbox', receipt: authority.receipt };
|
|
130
|
+
}),
|
|
131
|
+
authorizeCommit: request => transact({ type: 'commit-authorize', request }, async history => {
|
|
132
|
+
const authority = history.find(item => item.type === 'authorize' && item.leaseId === request?.leaseId);
|
|
133
|
+
const lease = history.find(item => item.type === 'lease' && item.leaseId === request?.leaseId);
|
|
134
|
+
const native = history.find(item => item.type === 'native-authorize' && item.leaseId === request?.leaseId);
|
|
135
|
+
const requiredChecks = authority?.requiredChecks;
|
|
136
|
+
const validation = request?.validation;
|
|
137
|
+
if (!authority || !lease || !native || Date.parse(lease.expiresAt) <= now().getTime() ||
|
|
138
|
+
history.some(item => item.leaseId === request?.leaseId &&
|
|
139
|
+
['commit-authorize', 'cancel-reconciled'].includes(item.type)) ||
|
|
140
|
+
authority.taskId !== request.taskId || authority.revision !== request.revision ||
|
|
141
|
+
authority.attempt !== request.attempt || authority.budgetReservation !== request.budgetReservation ||
|
|
142
|
+
authority.approvalReceipt !== request.approvalReceipt || authority.capabilityReceiptHash !== request.capabilityReceiptHash ||
|
|
143
|
+
authority.worktreeReceipt !== request.worktreeReceipt ||
|
|
144
|
+
digest(authority.dependencies) !== digest(request.dependencies) || !text(request.inputRevision) ||
|
|
145
|
+
!Array.isArray(requiredChecks) || !requiredChecks.length ||
|
|
146
|
+
!Array.isArray(validation) || validation.length !== requiredChecks.length ||
|
|
147
|
+
new Set(validation.map(item => item?.check)).size !== requiredChecks.length ||
|
|
148
|
+
validation.some(item => !requiredChecks.includes(item?.check) || item?.passed !== true ||
|
|
149
|
+
item?.inputRevision !== request.inputRevision || !text(item?.receipt))) return { allowed: false };
|
|
150
|
+
return { allowed: true, taskId: request.taskId, revision: request.revision, inputRevision: request.inputRevision,
|
|
151
|
+
leaseId: request.leaseId, capabilityReceiptHash: request.capabilityReceiptHash, receipt: `commit:${digest(request).slice(0, 32)}` };
|
|
152
|
+
}),
|
|
153
|
+
authorizeBenchmarkCapture: request => transact({ type: 'benchmark-capture-authorize', request }, async history => {
|
|
154
|
+
const runs = request?.runs;
|
|
155
|
+
if (typeof verifyBenchmarkCapture !== 'function' || !text(request?.revision) ||
|
|
156
|
+
!text(request?.journalHash) || !text(request?.workerStopReceipt) ||
|
|
157
|
+
!/^[a-f0-9]{64}$/.test(request?.corpusHash ?? '') ||
|
|
158
|
+
!/^[a-f0-9]{64}$/.test(request?.reportSha256 ?? '') ||
|
|
159
|
+
!Array.isArray(runs) || runs.length < 12 || runs.length > 3000 ||
|
|
160
|
+
history.some(item => item.type === 'benchmark-capture-authorize' &&
|
|
161
|
+
item.corpusHash === request.corpusHash && item.reportSha256 === request.reportSha256)) return { allowed: false };
|
|
162
|
+
const cases = new Map();
|
|
163
|
+
const leases = new Set();
|
|
164
|
+
for (const run of runs) {
|
|
165
|
+
if (!text(run?.caseId) || ![1, 2, 3].includes(run?.run) ||
|
|
166
|
+
![run.taskId, run.leaseId, run.worktreeReceipt, run.capabilityReceiptHash,
|
|
167
|
+
run.ledgerAuthorityReceipt, run.budgetReservation, run.approvalReceipt,
|
|
168
|
+
run.isolatedWorkspace].every(text) || leases.has(run.leaseId)) return { allowed: false };
|
|
169
|
+
leases.add(run.leaseId);
|
|
170
|
+
const repetitions = cases.get(run.caseId) ?? new Set();
|
|
171
|
+
if (repetitions.has(run.run)) return { allowed: false };
|
|
172
|
+
repetitions.add(run.run);
|
|
173
|
+
cases.set(run.caseId, repetitions);
|
|
174
|
+
const lease = history.find(item => item.type === 'lease' && item.leaseId === run.leaseId);
|
|
175
|
+
const authority = history.find(item => item.type === 'authorize' && item.leaseId === run.leaseId &&
|
|
176
|
+
item.receipt === run.ledgerAuthorityReceipt);
|
|
177
|
+
const native = history.find(item => item.type === 'native-authorize' && item.leaseId === run.leaseId &&
|
|
178
|
+
item.receipt === run.ledgerAuthorityReceipt);
|
|
179
|
+
if (!lease || !authority || !native || lease.taskId !== run.taskId ||
|
|
180
|
+
lease.revision !== request.revision || authority.taskId !== run.taskId ||
|
|
181
|
+
authority.revision !== request.revision || authority.worktreeReceipt !== run.worktreeReceipt ||
|
|
182
|
+
authority.capabilityReceiptHash !== run.capabilityReceiptHash ||
|
|
183
|
+
authority.budgetReservation !== run.budgetReservation ||
|
|
184
|
+
authority.approvalReceipt !== run.approvalReceipt ||
|
|
185
|
+
native.worktreeReceipt !== run.worktreeReceipt ||
|
|
186
|
+
native.capabilityReceiptHash !== run.capabilityReceiptHash ||
|
|
187
|
+
native.isolation !== 'git-worktree+local-os-sandbox' ||
|
|
188
|
+
history.some(item => item.type === 'cancel-reconciled' && item.leaseId === run.leaseId)) {
|
|
189
|
+
return { allowed: false };
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (cases.size < 4 || [...cases.values()].some(repetitions => repetitions.size !== 3)) return { allowed: false };
|
|
193
|
+
const proof = await verifyBenchmarkCapture(structuredClone(request));
|
|
194
|
+
if (proof?.allStopped !== true || proof.revision !== request.revision ||
|
|
195
|
+
proof.journalHash !== request.journalHash || proof.workerCount !== runs.length ||
|
|
196
|
+
proof.receipt !== request.workerStopReceipt ||
|
|
197
|
+
!Array.isArray(proof.cancelledLeases) || proof.cancelledLeases.length !== 0) return { allowed: false };
|
|
198
|
+
return { allowed: true, revision: request.revision, corpusHash: request.corpusHash,
|
|
199
|
+
reportSha256: request.reportSha256, workerStopReceipt: request.workerStopReceipt,
|
|
200
|
+
runs, receipt: `benchmark-capture:${digest(request).slice(0, 32)}` };
|
|
201
|
+
}),
|
|
202
|
+
inspectBenchmarkCapture: async request => {
|
|
203
|
+
if (!text(request?.receipt) || !text(request?.revision) ||
|
|
204
|
+
!/^[a-f0-9]{64}$/.test(request?.corpusHash ?? '') ||
|
|
205
|
+
!/^[a-f0-9]{64}$/.test(request?.reportSha256 ?? '') ||
|
|
206
|
+
!text(request?.workerStopReceipt)) return { valid: false };
|
|
207
|
+
const history = await events();
|
|
208
|
+
const record = history.find(item => item.type === 'benchmark-capture-authorize' &&
|
|
209
|
+
item.receipt === request.receipt && item.revision === request.revision &&
|
|
210
|
+
item.corpusHash === request.corpusHash && item.reportSha256 === request.reportSha256 &&
|
|
211
|
+
item.workerStopReceipt === request.workerStopReceipt);
|
|
212
|
+
return record ? { valid: true, receipt: record.receipt, runs: record.runs } : { valid: false };
|
|
213
|
+
},
|
|
214
|
+
recordBenchmarkSnapshot: request => transact({ type: 'benchmark-snapshot', request }, async history => {
|
|
215
|
+
if (!text(request?.captureReceipt) || !/^[a-f0-9]{64}$/.test(request?.corpusHash ?? '') ||
|
|
216
|
+
!/^[a-f0-9]{64}$/.test(request?.reportSha256 ?? '') ||
|
|
217
|
+
!/^[a-f0-9]{64}$/.test(request?.snapshotId ?? '')) return { allowed: false };
|
|
218
|
+
const capture = history.find(item => item.type === 'benchmark-capture-authorize' &&
|
|
219
|
+
item.receipt === request.captureReceipt && item.corpusHash === request.corpusHash &&
|
|
220
|
+
item.reportSha256 === request.reportSha256);
|
|
221
|
+
if (!capture || history.some(item => item.type === 'benchmark-snapshot' &&
|
|
222
|
+
item.captureReceipt === request.captureReceipt)) return { allowed: false };
|
|
223
|
+
return { allowed: true, ...request, receipt: `benchmark-snapshot:${digest(request).slice(0, 32)}` };
|
|
224
|
+
}),
|
|
225
|
+
reconcileCancelledLease: request => transact({ type: 'cancel-reconciled', request }, async history => {
|
|
226
|
+
if (typeof verifyCancellation !== 'function' || !text(request?.taskId) || !text(request?.revision) ||
|
|
227
|
+
!Number.isSafeInteger(request?.attempt) || !text(request?.leaseId) || !text(request?.journalHash) ||
|
|
228
|
+
!text(request?.workerStopReceipt) || !text(request?.abandonedWorktreeReceipt) ||
|
|
229
|
+
!text(request?.replacementWorktreeReceipt) ||
|
|
230
|
+
request.abandonedWorktreeReceipt === request.replacementWorktreeReceipt ||
|
|
231
|
+
!text(request?.inputRevision) || !text(request?.approvalReceipt) ||
|
|
232
|
+
!text(request?.capabilityReceiptHash) || !text(request?.budgetReservation)) return { allowed: false };
|
|
233
|
+
const lease = history.find(item => item.type === 'lease' && item.leaseId === request.leaseId);
|
|
234
|
+
const authority = history.find(item => item.type === 'authorize' && item.leaseId === request.leaseId);
|
|
235
|
+
const native = history.find(item => item.type === 'native-authorize' && item.leaseId === request.leaseId);
|
|
236
|
+
if (!lease || !authority || !native || lease.taskId !== request.taskId ||
|
|
237
|
+
lease.revision !== request.revision || lease.attempt !== request.attempt ||
|
|
238
|
+
authority.budgetReservation !== request.budgetReservation ||
|
|
239
|
+
authority.approvalReceipt !== request.approvalReceipt ||
|
|
240
|
+
authority.capabilityReceiptHash !== request.capabilityReceiptHash ||
|
|
241
|
+
authority.worktreeReceipt !== request.abandonedWorktreeReceipt ||
|
|
242
|
+
native.worktreeReceipt !== request.abandonedWorktreeReceipt ||
|
|
243
|
+
history.some(item => item.leaseId === request.leaseId &&
|
|
244
|
+
['commit-authorize', 'cancel-reconciled'].includes(item.type))) return { allowed: false };
|
|
245
|
+
const proof = await verifyCancellation(structuredClone(request));
|
|
246
|
+
if (proof?.valid !== true || Object.entries(request).some(([key, value]) => proof[key] !== value)) return { allowed: false };
|
|
247
|
+
return { allowed: true, ...request, receipt: `cancellation:${digest(request).slice(0, 32)}` };
|
|
248
|
+
}),
|
|
249
|
+
inspectCancellation: async request => {
|
|
250
|
+
if (!text(request?.taskId) || !text(request?.revision) || !text(request?.leaseId) ||
|
|
251
|
+
!text(request?.receipt) || !text(request?.journalHash)) return { valid: false };
|
|
252
|
+
const history = await events();
|
|
253
|
+
const valid = history.some(item => item.type === 'cancel-reconciled' &&
|
|
254
|
+
item.taskId === request.taskId && item.revision === request.revision &&
|
|
255
|
+
item.leaseId === request.leaseId && item.receipt === request.receipt &&
|
|
256
|
+
item.journalHash === request.journalHash &&
|
|
257
|
+
Object.entries(request).every(([key, value]) => key === 'receipt' || item.request?.[key] === value));
|
|
258
|
+
return { valid, ...request };
|
|
259
|
+
},
|
|
260
|
+
readCancellation: async request => {
|
|
261
|
+
if (!text(request?.taskId) || !text(request?.revision) || !text(request?.leaseId) ||
|
|
262
|
+
!text(request?.journalHash)) return { found: false };
|
|
263
|
+
const history = await events();
|
|
264
|
+
const record = history.find(item => item.type === 'cancel-reconciled' &&
|
|
265
|
+
item.taskId === request.taskId && item.revision === request.revision &&
|
|
266
|
+
item.leaseId === request.leaseId && item.journalHash === request.journalHash);
|
|
267
|
+
return record ? { found: true, receipt: record.receipt, request: record.request } : { found: false };
|
|
268
|
+
},
|
|
269
|
+
// Recovery is read-only. It proves that journaled execution facts came from
|
|
270
|
+
// this ledger before a caller may consider any work reusable.
|
|
271
|
+
inspectRecovery: async request => {
|
|
272
|
+
if (!text(request?.revision) || !text(request?.journalHash) || !Array.isArray(request?.authorizations) ||
|
|
273
|
+
!Array.isArray(request?.commits)) return { allowed: false };
|
|
274
|
+
const history = await events();
|
|
275
|
+
const authorized = request.authorizations.every(proof => text(proof?.taskId) && text(proof?.leaseId) &&
|
|
276
|
+
text(proof?.receipt) && text(proof?.capabilityReceiptHash) && history.some(event =>
|
|
277
|
+
event.type === 'authorize' && event.taskId === proof.taskId && event.revision === request.revision &&
|
|
278
|
+
event.leaseId === proof.leaseId && event.receipt === proof.receipt &&
|
|
279
|
+
event.capabilityReceiptHash === proof.capabilityReceiptHash));
|
|
280
|
+
const committed = request.commits.every(proof => text(proof?.taskId) && text(proof?.leaseId) &&
|
|
281
|
+
text(proof?.receipt) && text(proof?.inputRevision) && text(proof?.capabilityReceiptHash) && history.some(event =>
|
|
282
|
+
event.type === 'commit-authorize' && event.taskId === proof.taskId && event.revision === request.revision &&
|
|
283
|
+
event.leaseId === proof.leaseId && event.receipt === proof.receipt &&
|
|
284
|
+
event.inputRevision === proof.inputRevision &&
|
|
285
|
+
event.capabilityReceiptHash === proof.capabilityReceiptHash));
|
|
286
|
+
return { allowed: authorized && committed, revision: request.revision, journalHash: request.journalHash };
|
|
287
|
+
},
|
|
288
|
+
});
|
|
289
|
+
}
|