@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,484 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Host-neutral execution kernel. Adapters are trusted application code, never
|
|
3
|
+
* commands or capability declarations received from a worker or a document.
|
|
4
|
+
*/
|
|
5
|
+
import { constants } from 'node:fs';
|
|
6
|
+
import { open, readFile, realpath, lstat, rename, writeFile } from 'node:fs/promises';
|
|
7
|
+
import path from 'node:path';
|
|
8
|
+
import { createHash } from 'node:crypto';
|
|
9
|
+
import { reviewPriority } from './gofer-priority-check.mjs';
|
|
10
|
+
import { inspectBlockers } from './gofer-blocker-control.mjs';
|
|
11
|
+
import { capabilityReceiptHash, verifyCapabilityReceipt } from './gofer-host-capability.mjs';
|
|
12
|
+
import { selectCapabilityRoute } from './gofer-live-routing.mjs';
|
|
13
|
+
import { acquireLedgerLock } from './gofer-runtime-ledger.mjs';
|
|
14
|
+
|
|
15
|
+
const contractFiles = ['spec.md', 'plan.md', 'decisions.md', 'priority-plan.json', 'loop-contract.json'];
|
|
16
|
+
const MAX_CONTRACT_FILE_BYTES = 1024 * 1024;
|
|
17
|
+
const positive = value => Number.isSafeInteger(value) && value > 0;
|
|
18
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
19
|
+
function releaseResumeLock(lock) {
|
|
20
|
+
try { lock.exec('ROLLBACK'); } finally { lock.close(); }
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function executionRevision(featureDir) {
|
|
24
|
+
return (await snapshot(featureDir)).revision;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async function snapshot(featureDir) {
|
|
28
|
+
const hash = createHash('sha256');
|
|
29
|
+
const files = {};
|
|
30
|
+
for (const file of contractFiles) {
|
|
31
|
+
files[file] = await readContractFile(featureDir, file);
|
|
32
|
+
hash.update(file).update(files[file]);
|
|
33
|
+
}
|
|
34
|
+
return { files, revision: hash.digest('hex') };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
async function readContractFile(featureDir, name) {
|
|
38
|
+
const root = await realpath(featureDir);
|
|
39
|
+
const target = path.join(root, name);
|
|
40
|
+
// Resolve before opening. A different canonical path proves a supplied link.
|
|
41
|
+
const canonical = await realpath(target);
|
|
42
|
+
if (canonical !== target) throw new Error('UNSAFE_CONTRACT_FILE');
|
|
43
|
+
// Do not follow a link introduced after canonical-path validation on POSIX.
|
|
44
|
+
const flags = constants.O_RDONLY | (process.platform === 'win32' ? 0 : constants.O_NOFOLLOW);
|
|
45
|
+
const file = await open(canonical, flags);
|
|
46
|
+
try {
|
|
47
|
+
const stat = await file.stat();
|
|
48
|
+
if (!stat.isFile() || stat.size > MAX_CONTRACT_FILE_BYTES) throw new Error('UNSAFE_CONTRACT_FILE');
|
|
49
|
+
return await file.readFile({ encoding: 'utf8' });
|
|
50
|
+
} finally {
|
|
51
|
+
await file.close();
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function freeze(value) {
|
|
56
|
+
if (value && typeof value === 'object') { Object.values(value).forEach(freeze); Object.freeze(value); }
|
|
57
|
+
return value;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
async function canonicalScopes(workspace, scopes) {
|
|
61
|
+
const root = await realpath(workspace);
|
|
62
|
+
return Promise.all(scopes.map(async scope => {
|
|
63
|
+
const segments = scope.replace(/\/$/, '').split('/');
|
|
64
|
+
let current = root;
|
|
65
|
+
for (const segment of segments) {
|
|
66
|
+
current = path.join(current, segment);
|
|
67
|
+
try { if ((await lstat(current)).isSymbolicLink()) throw new Error('SYMLINK_SCOPE_UNQUALIFIED'); }
|
|
68
|
+
catch (error) { if (error.code !== 'ENOENT') throw error; }
|
|
69
|
+
}
|
|
70
|
+
return path.relative(root, current).split(path.sep).join('/');
|
|
71
|
+
}));
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function validateWorkGraph(plan, checks) {
|
|
75
|
+
if (![1, 2].includes(plan?.schemaVersion) || !text(plan.revision) || !plan.tasks ||
|
|
76
|
+
!Array.isArray(plan.criticalPath) || !plan.criticalPath.length) throw new Error('INVALID_GRAPH');
|
|
77
|
+
const ids = Object.keys(plan.tasks);
|
|
78
|
+
if (!ids.length || ids.length > 1000 || new Set(plan.criticalPath).size !== plan.criticalPath.length ||
|
|
79
|
+
plan.criticalPath.some(id => !ids.includes(id))) throw new Error('INVALID_GRAPH');
|
|
80
|
+
let edgeCount = 0;
|
|
81
|
+
let scopeCount = 0;
|
|
82
|
+
for (const id of ids) {
|
|
83
|
+
const task = plan.tasks[id];
|
|
84
|
+
if (!/^T\d+$/.test(id) || !task || !Array.isArray(task.dependsOn) ||
|
|
85
|
+
task.dependsOn.length > 256 || task.dependsOn.some(dep => !ids.includes(dep)) || !Array.isArray(task.allowedEditScope) ||
|
|
86
|
+
task.allowedEditScope.length > 256 ||
|
|
87
|
+
task.allowedEditScope.some(scope => !text(scope) || /(^\/|\/\/|\\|:|\0|[*?\[\]]|(^|\/)\.\.?($|\/))/.test(scope)) ||
|
|
88
|
+
!Array.isArray(checks[id]) || !checks[id].length || checks[id].length > 256 || checks[id].some(c => !text(c) || c.length > 1024) ||
|
|
89
|
+
new Set(checks[id]).size !== checks[id].length) throw new Error('INVALID_WORK_ORDER');
|
|
90
|
+
edgeCount += task.dependsOn.length;
|
|
91
|
+
scopeCount += task.allowedEditScope.length;
|
|
92
|
+
if (edgeCount > 10000 || scopeCount > 10000) throw new Error('INVALID_WORK_ORDER');
|
|
93
|
+
}
|
|
94
|
+
const states = new Map(ids.map(id => [id, 0]));
|
|
95
|
+
const visit = id => {
|
|
96
|
+
const state = states.get(id);
|
|
97
|
+
if (state === 1) throw new Error('CYCLIC_GRAPH');
|
|
98
|
+
if (state === 2) return;
|
|
99
|
+
states.set(id, 1);
|
|
100
|
+
for (const dependency of plan.tasks[id].dependsOn) visit(dependency);
|
|
101
|
+
states.set(id, 2);
|
|
102
|
+
};
|
|
103
|
+
ids.forEach(visit);
|
|
104
|
+
return ids;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function overlaps(a, b) {
|
|
108
|
+
// Conservative on case-insensitive hosts and on parent/child directory edits.
|
|
109
|
+
return a.some(left => b.some(right => {
|
|
110
|
+
const x = left.toLowerCase().replace(/\/$/, '');
|
|
111
|
+
const y = right.toLowerCase().replace(/\/$/, '');
|
|
112
|
+
return x === y || x.startsWith(`${y}/`) || y.startsWith(`${x}/`);
|
|
113
|
+
}));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Run a trusted adapter against the existing priority plan. Each required check
|
|
118
|
+
* runs separately from worker output. This library does not provide a sandbox.
|
|
119
|
+
* An existing journal is a reconciliation gate, not permission to replay work.
|
|
120
|
+
*/
|
|
121
|
+
export async function runVerifiedGraph({ featureDir, workspaceRoot, checks, adapter,
|
|
122
|
+
ledger, capabilityReceipt, capabilityPublicKey, requiredCapabilities, benchmarkEvidence, verifyBenchmark,
|
|
123
|
+
advisoryConstraints, approvalReceipt, maxCalls, maxConcurrent = 1, deadlineMs, signal, recovery,
|
|
124
|
+
adapterDrainMs = 5000 }) {
|
|
125
|
+
// Copy before any await: a caller or worker must not remove required checks.
|
|
126
|
+
checks = freeze(structuredClone(checks));
|
|
127
|
+
const journalName = 'verified-execution.jsonl';
|
|
128
|
+
if (!positive(maxCalls) || !positive(maxConcurrent) || maxConcurrent > 8 ||
|
|
129
|
+
!positive(adapterDrainMs) || adapterDrainMs > 10000 ||
|
|
130
|
+
!Number.isFinite(deadlineMs) || deadlineMs <= Date.now()) throw new Error('FINITE_LIMITS_REQUIRED');
|
|
131
|
+
if (!text(approvalReceipt)) throw new Error('APPROVAL_RECEIPT_REQUIRED');
|
|
132
|
+
for (const method of ['execute', 'check', 'inputRevision', 'reserve', 'lease', 'verified']) {
|
|
133
|
+
if (typeof adapter?.[method] !== 'function') throw new Error(`TRUSTED_ADAPTER_REQUIRED:${method}`);
|
|
134
|
+
}
|
|
135
|
+
if (capabilityReceipt?.isolationClass !== 'git-worktree+local-os-sandbox' ||
|
|
136
|
+
typeof ledger?.authorize !== 'function' || typeof ledger?.authorizeCommit !== 'function' || !verifyCapabilityReceipt(capabilityReceipt, {
|
|
137
|
+
publicKey: capabilityPublicKey, requiredCapabilities,
|
|
138
|
+
})) throw new Error('LEDGER_CAPABILITY_AUTHORITY_REQUIRED');
|
|
139
|
+
const route = await selectCapabilityRoute({ receipt: capabilityReceipt, publicKey: capabilityPublicKey,
|
|
140
|
+
host: capabilityReceipt.host, requiredCapabilities, benchmarkEvidence, verifyBenchmark, advisoryConstraints });
|
|
141
|
+
const root = await realpath(featureDir);
|
|
142
|
+
const captured = await snapshot(root);
|
|
143
|
+
const plan = freeze(JSON.parse(captured.files['priority-plan.json']));
|
|
144
|
+
const loop = freeze(JSON.parse(captured.files['loop-contract.json']));
|
|
145
|
+
if (!positive(loop.maxIterations)) throw new Error('FINITE_ATTEMPTS_REQUIRED');
|
|
146
|
+
if (loop.budget?.maxModelSpendUsd != null) throw new Error('SPEND_RESERVATION_NOT_IMPLEMENTED');
|
|
147
|
+
if (loop.budget?.maxWallClockMinutes != null) {
|
|
148
|
+
if (!(loop.budget.maxWallClockMinutes > 0)) throw new Error('INVALID_DEADLINE');
|
|
149
|
+
deadlineMs = Math.min(deadlineMs, Date.now() + loop.budget.maxWallClockMinutes * 60000);
|
|
150
|
+
}
|
|
151
|
+
const ids = validateWorkGraph(plan, checks);
|
|
152
|
+
const initial = await reviewPriority(root);
|
|
153
|
+
if (initial.status !== 'pass') throw new Error(`PRIORITY_BLOCKED:${initial.findings.join(',')}`);
|
|
154
|
+
const revision = captured.revision;
|
|
155
|
+
const receiptHash = capabilityReceiptHash(capabilityReceipt);
|
|
156
|
+
if (await executionRevision(root) !== revision) throw new Error('STALE_DIRECTION');
|
|
157
|
+
const tasksText = await readFile(path.join(root, 'tasks.md'), 'utf8');
|
|
158
|
+
const previouslyComplete = new Set([...tasksText.matchAll(/^\s*-\s+\[[xX]\]\s+(?:\*\*)?(T\d+)\b/gm)].map(m => m[1]));
|
|
159
|
+
const scopes = {};
|
|
160
|
+
for (const id of ids) scopes[id] = await canonicalScopes(workspaceRoot, plan.tasks[id].allowedEditScope);
|
|
161
|
+
let resumeReport;
|
|
162
|
+
let resumeLock;
|
|
163
|
+
const lockPath = path.join(root, 'verified-execution.resume.lock');
|
|
164
|
+
if (recovery) {
|
|
165
|
+
// The transaction excludes other controllers and the operating system
|
|
166
|
+
// releases it if this controller exits before reconciliation finishes.
|
|
167
|
+
resumeLock = await acquireLedgerLock(lockPath).catch(() => { throw new Error('RESUME_LOCKED'); });
|
|
168
|
+
try {
|
|
169
|
+
const { inspectExecutionRecovery } = await import('./gofer-execution-recovery.mjs');
|
|
170
|
+
resumeReport = await inspectExecutionRecovery({ ...recovery, featureDir: root });
|
|
171
|
+
const run = resumeReport.run;
|
|
172
|
+
if (resumeReport.status !== 'reconciled' || !resumeReport.resumeAllowed ||
|
|
173
|
+
resumeReport.uncertainTasks.length || !run ||
|
|
174
|
+
run.revision !== revision || run.maxCalls !== maxCalls || run.maxConcurrent !== maxConcurrent ||
|
|
175
|
+
run.maxIterations !== loop.maxIterations || deadlineMs !== run.deadlineMs ||
|
|
176
|
+
run.capabilityReceiptHash !== receiptHash || run.selectedModel !== route.model.id ||
|
|
177
|
+
run.benchmarkReceipt !== route.benchmarkReceipt || run.approvalReceipt !== approvalReceipt ||
|
|
178
|
+
JSON.stringify(run.requiredChecks) !== JSON.stringify(checks) ||
|
|
179
|
+
(resumeReport.restartableTasks.length > 0 &&
|
|
180
|
+
(resumeReport.restartableTasks.length !== 1 ||
|
|
181
|
+
resumeReport.replacementWorktreeReceipt !== adapter.worktreeReceipt)) ||
|
|
182
|
+
previouslyComplete.size !== resumeReport.reusableTasks.length ||
|
|
183
|
+
[...previouslyComplete].some(id => !resumeReport.reusableTasks.includes(id)) ||
|
|
184
|
+
resumeReport.reusableTasks.length === ids.length) throw new Error('RESUME_RECONCILIATION_REQUIRED');
|
|
185
|
+
for (const taskId of resumeReport.reusableTasks) {
|
|
186
|
+
const input = await adapter.inputRevision({ taskId, revision,
|
|
187
|
+
allowedEditScope: plan.tasks[taskId].allowedEditScope, requiredChecks: checks[taskId] });
|
|
188
|
+
if (input !== resumeReport.verifiedInputs[taskId]) throw new Error('STALE_PREREQUISITE_EVIDENCE');
|
|
189
|
+
}
|
|
190
|
+
} catch (error) {
|
|
191
|
+
releaseResumeLock(resumeLock);
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
} else if (previouslyComplete.size) throw new Error('BASELINE_EVIDENCE_RECONCILIATION_REQUIRED');
|
|
195
|
+
const states = Object.fromEntries(ids.map(id => [id, resumeReport?.reusableTasks.includes(id) ? 'verified' : 'pending']));
|
|
196
|
+
const attempts = Object.fromEntries(ids.map(id => [id, resumeReport?.attemptsConsumed[id] || 0]));
|
|
197
|
+
const verifiedInputs = new Map(Object.entries(resumeReport?.verifiedInputs || {}));
|
|
198
|
+
const controller = new AbortController();
|
|
199
|
+
const cancel = () => controller.abort(new Error('CANCELLED'));
|
|
200
|
+
if (signal?.aborted) cancel();
|
|
201
|
+
signal?.addEventListener('abort', cancel, { once: true });
|
|
202
|
+
const timer = setTimeout(() => controller.abort(new Error('DEADLINE')), Math.min(deadlineMs - Date.now(), 2147483647));
|
|
203
|
+
const file = await open(path.join(root, journalName), recovery ? constants.O_RDWR | constants.O_APPEND | constants.O_NOFOLLOW : 'wx', 0o600).catch(async error => {
|
|
204
|
+
clearTimeout(timer); signal?.removeEventListener('abort', cancel);
|
|
205
|
+
if (resumeLock) releaseResumeLock(resumeLock);
|
|
206
|
+
throw new Error(error.code === 'EEXIST' ? 'RECONCILIATION_REQUIRED' : 'JOURNAL_UNAVAILABLE');
|
|
207
|
+
});
|
|
208
|
+
let calls = resumeReport?.callsConsumed || 0;
|
|
209
|
+
let write = Promise.resolve();
|
|
210
|
+
let checkpointWrite = Promise.resolve();
|
|
211
|
+
let checkpointSequence = 0;
|
|
212
|
+
const pendingAdapterCalls = new Set();
|
|
213
|
+
let drainAttempted = false;
|
|
214
|
+
async function drainAdapterCalls() {
|
|
215
|
+
drainAttempted = true;
|
|
216
|
+
if (!pendingAdapterCalls.size) return true;
|
|
217
|
+
let drainTimer;
|
|
218
|
+
const settled = await Promise.race([
|
|
219
|
+
Promise.allSettled([...pendingAdapterCalls]).then(() => true),
|
|
220
|
+
new Promise(resolve => { drainTimer = setTimeout(() => resolve(false), adapterDrainMs); }),
|
|
221
|
+
]);
|
|
222
|
+
clearTimeout(drainTimer);
|
|
223
|
+
return settled;
|
|
224
|
+
}
|
|
225
|
+
const record = event => {
|
|
226
|
+
write = write.then(async () => { await file.writeFile(`${JSON.stringify({ schemaVersion: 1, revision, time: new Date().toISOString(), ...event })}\n`); await file.sync(); })
|
|
227
|
+
.catch(() => { controller.abort(new Error('JOURNAL_FAILURE')); throw new Error('JOURNAL_FAILURE'); });
|
|
228
|
+
return write;
|
|
229
|
+
};
|
|
230
|
+
const abortCheck = () => {
|
|
231
|
+
if (Date.now() >= deadlineMs && !controller.signal.aborted) controller.abort(new Error('DEADLINE'));
|
|
232
|
+
if (controller.signal.aborted) throw controller.signal.reason;
|
|
233
|
+
};
|
|
234
|
+
// The checkpoint contains only current graph state plus the latest delta.
|
|
235
|
+
// It avoids replaying the entire append-only ledger during operator recovery.
|
|
236
|
+
async function checkpoint(event, taskId = null) {
|
|
237
|
+
// Serialize snapshots so concurrent tasks cannot race the replacement file.
|
|
238
|
+
checkpointWrite = checkpointWrite.then(async () => {
|
|
239
|
+
const payload = JSON.stringify({ schemaVersion: 1, revision, updatedAt: new Date().toISOString(),
|
|
240
|
+
delta: { event, taskId }, states, attempts, calls,
|
|
241
|
+
verifiedInputs: Object.fromEntries(verifiedInputs) });
|
|
242
|
+
const target = path.join(root, 'verified-execution.checkpoint.json');
|
|
243
|
+
const temporary = `${target}.${process.pid}.${++checkpointSequence}.tmp`;
|
|
244
|
+
try {
|
|
245
|
+
await writeFile(temporary, payload, { encoding: 'utf8', mode: 0o600, flag: 'wx' });
|
|
246
|
+
await rename(temporary, target);
|
|
247
|
+
} catch {
|
|
248
|
+
controller.abort(new Error('CHECKPOINT_UNAVAILABLE'));
|
|
249
|
+
throw controller.signal.reason;
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return checkpointWrite;
|
|
253
|
+
}
|
|
254
|
+
async function current() {
|
|
255
|
+
abortCheck();
|
|
256
|
+
if (await executionRevision(root) !== revision) {
|
|
257
|
+
controller.abort(new Error('STALE_DIRECTION'));
|
|
258
|
+
throw controller.signal.reason;
|
|
259
|
+
}
|
|
260
|
+
abortCheck();
|
|
261
|
+
}
|
|
262
|
+
async function invoke(method, request) {
|
|
263
|
+
await current();
|
|
264
|
+
const blockers = await inspectBlockers(root, { taskId: request.taskId });
|
|
265
|
+
if (blockers.blockers.some(b => b.state !== 'ready')) throw new Error('RECORDED_BLOCKER_WAIT');
|
|
266
|
+
if (calls >= maxCalls) throw new Error('CALL_LIMIT');
|
|
267
|
+
// Reserve globally before yielding, so parallel calls cannot overdraw.
|
|
268
|
+
calls++;
|
|
269
|
+
await record({ event: 'call_reserved', task: request.taskId, method, call: calls });
|
|
270
|
+
let listener;
|
|
271
|
+
try {
|
|
272
|
+
await current();
|
|
273
|
+
const operation = Promise.resolve().then(() => { abortCheck(); return adapter[method]({ ...request, signal: controller.signal }); });
|
|
274
|
+
const settled = operation.then(() => {}, () => {}).finally(() => pendingAdapterCalls.delete(settled));
|
|
275
|
+
pendingAdapterCalls.add(settled);
|
|
276
|
+
return await Promise.race([
|
|
277
|
+
operation,
|
|
278
|
+
new Promise((_, reject) => {
|
|
279
|
+
listener = () => reject(controller.signal.reason);
|
|
280
|
+
controller.signal.addEventListener('abort', listener, { once: true });
|
|
281
|
+
if (controller.signal.aborted) listener();
|
|
282
|
+
}),
|
|
283
|
+
]);
|
|
284
|
+
} finally {
|
|
285
|
+
if (listener) controller.signal.removeEventListener('abort', listener);
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
async function runTask(taskId) {
|
|
289
|
+
const task = plan.tasks[taskId];
|
|
290
|
+
const dependencies = task.dependsOn.map(dependency => ({ taskId: dependency,
|
|
291
|
+
inputRevision: verifiedInputs.get(dependency) ?? null }));
|
|
292
|
+
if (dependencies.some(dependency => !text(dependency.inputRevision))) throw new Error('DEPENDENCY_INPUT_REQUIRED');
|
|
293
|
+
const request = { taskId, revision, dependencies, allowedEditScope: task.allowedEditScope, requiredChecks: checks[taskId], approvalReceipt,
|
|
294
|
+
...(text(adapter.worktreeReceipt) ? { worktreeReceipt: adapter.worktreeReceipt } : {}),
|
|
295
|
+
capabilityReceiptHash: receiptHash, selectedModel: route.model.id, benchmarkReceipt: route.benchmarkReceipt };
|
|
296
|
+
let previousChecks = [];
|
|
297
|
+
try {
|
|
298
|
+
while (attempts[taskId] < loop.maxIterations) {
|
|
299
|
+
await current();
|
|
300
|
+
const blockers = await inspectBlockers(root, { taskId });
|
|
301
|
+
if (blockers.blockers.some(b => b.state !== 'ready')) throw new Error('RECORDED_BLOCKER_WAIT');
|
|
302
|
+
const priority = await reviewPriority(root, { task: taskId });
|
|
303
|
+
if (priority.status !== 'pass') throw new Error('PRIORITY_BLOCKED');
|
|
304
|
+
await canonicalScopes(workspaceRoot, task.allowedEditScope);
|
|
305
|
+
const attempt = ++attempts[taskId];
|
|
306
|
+
await record({ event: 'attempt_reserved', task: taskId, attempt });
|
|
307
|
+
const reservation = await invoke('reserve', { ...request, attempt });
|
|
308
|
+
if (reservation?.allowed !== true || !text(reservation.budgetReservation)) throw new Error('BLOCKER_OR_BUDGET_DENIED');
|
|
309
|
+
const lease = await invoke('lease', { ...request, attempt });
|
|
310
|
+
const leaseExpiry = Date.parse(lease?.expiresAt);
|
|
311
|
+
if (!text(lease?.leaseId) || !Number.isFinite(leaseExpiry) || leaseExpiry <= Date.now()) {
|
|
312
|
+
throw new Error('TASK_LEASE_REQUIRED');
|
|
313
|
+
}
|
|
314
|
+
await record({ event: 'lease_granted', task: taskId, attempt, leaseId: lease.leaseId, expiresAt: lease.expiresAt });
|
|
315
|
+
const leasedRequest = { ...request, attempt, budgetReservation: reservation.budgetReservation,
|
|
316
|
+
leaseId: lease.leaseId, leaseExpiresAt: lease.expiresAt };
|
|
317
|
+
const authority = await ledger.authorize(leasedRequest);
|
|
318
|
+
if (authority?.allowed !== true || authority.taskId !== taskId || authority.revision !== revision ||
|
|
319
|
+
authority.attempt !== attempt || authority.capabilityReceiptHash !== receiptHash ||
|
|
320
|
+
authority.budgetReservation !== reservation.budgetReservation || authority.leaseId !== lease.leaseId ||
|
|
321
|
+
authority.worktreeReceipt !== request.worktreeReceipt ||
|
|
322
|
+
authority.leaseExpiresAt !== lease.expiresAt || authority.allowedEditScope?.join('\0') !== request.allowedEditScope.join('\0') ||
|
|
323
|
+
authority.requiredChecks?.join('\0') !== request.requiredChecks.join('\0') || authority.approvalReceipt !== approvalReceipt ||
|
|
324
|
+
!text(authority.receipt)) throw new Error('LEDGER_AUTHORITY_REQUIRED');
|
|
325
|
+
await record({ event: 'ledger_authorized', task: taskId, attempt, leaseId: lease.leaseId,
|
|
326
|
+
budgetReservation: reservation.budgetReservation, receipt: authority.receipt, capabilityReceiptHash: receiptHash,
|
|
327
|
+
worktreeReceipt: request.worktreeReceipt ?? null });
|
|
328
|
+
states[taskId] = 'running';
|
|
329
|
+
await checkpoint('running', taskId);
|
|
330
|
+
// Repair sees measured failures, never just "try again" or prior reasoning.
|
|
331
|
+
const result = await invoke('execute', { ...leasedRequest, ledgerAuthorityReceipt: authority.receipt,
|
|
332
|
+
previousChecks: freeze(structuredClone(previousChecks)) });
|
|
333
|
+
await current();
|
|
334
|
+
if (!result || !Array.isArray(result.changedFiles) || result.changedFiles.some(f => !text(f))) throw new Error('INVALID_WORKER_RESULT');
|
|
335
|
+
const scope = await reviewPriority(root, { task: taskId, changedFiles: result.changedFiles, workspaceRoot });
|
|
336
|
+
if (scope.status !== 'pass') throw new Error('SCOPE_VIOLATION');
|
|
337
|
+
const inputRevision = await invoke('inputRevision', request);
|
|
338
|
+
if (!text(inputRevision)) throw new Error('INPUT_REVISION_REQUIRED');
|
|
339
|
+
states[taskId] = 'awaiting-check';
|
|
340
|
+
let passed = true;
|
|
341
|
+
previousChecks = [];
|
|
342
|
+
for (const check of checks[taskId]) {
|
|
343
|
+
let evidence;
|
|
344
|
+
try {
|
|
345
|
+
evidence = await invoke('check', { ...leasedRequest, check, inputRevision });
|
|
346
|
+
} catch {
|
|
347
|
+
// A failed check can leave local or remote work in an unknown state.
|
|
348
|
+
controller.abort(new Error('CHECK_RECONCILIATION_REQUIRED'));
|
|
349
|
+
throw controller.signal.reason;
|
|
350
|
+
}
|
|
351
|
+
await current();
|
|
352
|
+
const valid = evidence?.taskId === taskId && evidence.revision === revision &&
|
|
353
|
+
evidence.inputRevision === inputRevision && evidence.check === check &&
|
|
354
|
+
evidence.exitCode === 0 && evidence.executed === true && text(evidence.receipt);
|
|
355
|
+
await record({ event: 'check', task: taskId, attempt, check, inputRevision,
|
|
356
|
+
passed: valid, receipt: text(evidence?.receipt) ? evidence.receipt : null });
|
|
357
|
+
if (evidence?.cleanupVerified === false) {
|
|
358
|
+
controller.abort(new Error('CLEANUP_RECONCILIATION_REQUIRED'));
|
|
359
|
+
throw controller.signal.reason;
|
|
360
|
+
}
|
|
361
|
+
previousChecks.push({ check, passed: valid, inputRevision,
|
|
362
|
+
receipt: text(evidence?.receipt) ? evidence.receipt : null });
|
|
363
|
+
passed &&= valid;
|
|
364
|
+
}
|
|
365
|
+
if (await invoke('inputRevision', request) !== inputRevision) throw new Error('STALE_INPUT');
|
|
366
|
+
if (passed) {
|
|
367
|
+
await current();
|
|
368
|
+
const assertCurrent = async () => {
|
|
369
|
+
await current();
|
|
370
|
+
const blockers = await inspectBlockers(root, { taskId });
|
|
371
|
+
if (blockers.blockers.some(b => b.state !== 'ready')) throw new Error('RECORDED_BLOCKER_WAIT');
|
|
372
|
+
if (await invoke('inputRevision', request) !== inputRevision) throw new Error('STALE_INPUT');
|
|
373
|
+
await current();
|
|
374
|
+
};
|
|
375
|
+
await assertCurrent();
|
|
376
|
+
const commitAuthority = await ledger.authorizeCommit({ ...leasedRequest, inputRevision,
|
|
377
|
+
validation: freeze(structuredClone(previousChecks)) });
|
|
378
|
+
if (commitAuthority?.allowed !== true || commitAuthority.taskId !== taskId ||
|
|
379
|
+
commitAuthority.revision !== revision || commitAuthority.inputRevision !== inputRevision ||
|
|
380
|
+
commitAuthority.leaseId !== lease.leaseId || commitAuthority.capabilityReceiptHash !== receiptHash ||
|
|
381
|
+
!text(commitAuthority.receipt)) throw new Error('LEDGER_COMMIT_AUTHORITY_REQUIRED');
|
|
382
|
+
await record({ event: 'commit_authorized', task: taskId, attempt, leaseId: lease.leaseId,
|
|
383
|
+
inputRevision, receipt: commitAuthority.receipt, capabilityReceiptHash: receiptHash });
|
|
384
|
+
// The trusted adapter must compare-and-set, not unconditionally tick tasks.
|
|
385
|
+
const commit = await invoke('verified', { ...leasedRequest, inputRevision,
|
|
386
|
+
commitAuthorityReceipt: commitAuthority.receipt, assertCurrent });
|
|
387
|
+
if (commit?.committed !== true || commit.taskId !== taskId || commit.revision !== revision ||
|
|
388
|
+
commit.inputRevision !== inputRevision || !text(commit.receipt)) throw new Error('COMMIT_RECONCILIATION_REQUIRED');
|
|
389
|
+
await assertCurrent();
|
|
390
|
+
await current();
|
|
391
|
+
states[taskId] = 'verified';
|
|
392
|
+
verifiedInputs.set(taskId, inputRevision);
|
|
393
|
+
await record({ event: 'verified', task: taskId, inputRevision, receipt: commit.receipt });
|
|
394
|
+
await checkpoint('verified', taskId);
|
|
395
|
+
return;
|
|
396
|
+
}
|
|
397
|
+
await record({ event: 'repair_required', task: taskId, attempt });
|
|
398
|
+
}
|
|
399
|
+
throw new Error('ATTEMPT_LIMIT');
|
|
400
|
+
} catch (error) {
|
|
401
|
+
const reason = controller.signal.aborted ? controller.signal.reason.message : error.message;
|
|
402
|
+
states[taskId] = reason.startsWith('STALE') ? 'stale' : reason === 'CLEANUP_RECONCILIATION_REQUIRED' ? 'blocked' : controller.signal.aborted ? 'cancelled' : 'blocked';
|
|
403
|
+
await record({ event: states[taskId], task: taskId, reason, reconciliationRequired: true }).catch(() => {});
|
|
404
|
+
await checkpoint(states[taskId], taskId).catch(() => {});
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
try {
|
|
408
|
+
if (resumeReport) {
|
|
409
|
+
const contents = await file.readFile('utf8');
|
|
410
|
+
if (createHash('sha256').update(contents).digest('hex') !== resumeReport.journalHash) throw new Error('RECOVERY_INPUT_CHANGED');
|
|
411
|
+
await record({ event: 'resumed', previousJournalHash: resumeReport.journalHash,
|
|
412
|
+
callsConsumed: calls, attemptsConsumed: resumeReport.attemptsConsumed });
|
|
413
|
+
await checkpoint('resumed');
|
|
414
|
+
} else {
|
|
415
|
+
await record({ event: 'started', maxCalls, maxConcurrent, maxIterations: loop.maxIterations, capabilityReceiptHash: receiptHash,
|
|
416
|
+
selectedModel: route.model.id, benchmarkReceipt: route.benchmarkReceipt, approvalReceipt,
|
|
417
|
+
requiredChecks: checks, deadlineMs, baselineTasks: [...previouslyComplete],
|
|
418
|
+
worktreeReceipt: adapter.worktreeReceipt ?? null });
|
|
419
|
+
await checkpoint('started');
|
|
420
|
+
}
|
|
421
|
+
const active = new Map();
|
|
422
|
+
while (!controller.signal.aborted) {
|
|
423
|
+
let scheduled = false;
|
|
424
|
+
for (const id of ids) {
|
|
425
|
+
if (active.size === maxConcurrent || states[id] !== 'pending') continue;
|
|
426
|
+
if (!plan.tasks[id].dependsOn.every(dep => states[dep] === 'verified')) continue;
|
|
427
|
+
if ([...active.keys()].some(other => overlaps(scopes[id], scopes[other]))) continue;
|
|
428
|
+
for (const dependency of plan.tasks[id].dependsOn) {
|
|
429
|
+
const expected = verifiedInputs.get(dependency);
|
|
430
|
+
const currentInput = await invoke('inputRevision', {
|
|
431
|
+
taskId: dependency,
|
|
432
|
+
revision,
|
|
433
|
+
allowedEditScope: plan.tasks[dependency].allowedEditScope,
|
|
434
|
+
requiredChecks: checks[dependency],
|
|
435
|
+
});
|
|
436
|
+
if (currentInput !== expected) {
|
|
437
|
+
controller.abort(new Error('STALE_PREREQUISITE_EVIDENCE'));
|
|
438
|
+
break;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
if (controller.signal.aborted) break;
|
|
442
|
+
if ((await reviewPriority(root, { task: id })).status !== 'pass') continue;
|
|
443
|
+
active.set(id, runTask(id).then(() => id));
|
|
444
|
+
scheduled = true;
|
|
445
|
+
}
|
|
446
|
+
if (active.size) {
|
|
447
|
+
const completed = await Promise.race(active.values());
|
|
448
|
+
active.delete(completed);
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (!scheduled) break;
|
|
452
|
+
}
|
|
453
|
+
// Cancellation stops new scheduling, but active work may still be draining
|
|
454
|
+
// its journal and checkpoint operations. Do not close the runtime ledger
|
|
455
|
+
// or return while a sibling can still write into the feature directory.
|
|
456
|
+
if (active.size) await Promise.allSettled([...active.values()]);
|
|
457
|
+
// The abort race stops scheduling promptly, but a trusted adapter may
|
|
458
|
+
// still be delivering cancellation. Give it a bounded drain window and
|
|
459
|
+
// report when it has not settled; recovery must then inspect the worker.
|
|
460
|
+
const adapterCallsSettled = await drainAdapterCalls();
|
|
461
|
+
const verified = ids.filter(id => states[id] === 'verified');
|
|
462
|
+
const status = !controller.signal.aborted && ids.every(id => states[id] === 'verified') ? 'verified' : 'incomplete';
|
|
463
|
+
// Task verification is not the feature/release outcome gate.
|
|
464
|
+
const result = { status, states, verified, calls, attempts, revision, cost: null, adapterCallsSettled,
|
|
465
|
+
nativeQualification: 'not-established', featureComplete: false };
|
|
466
|
+
await record({ event: 'finished', ...result });
|
|
467
|
+
await checkpoint('finished');
|
|
468
|
+
return result;
|
|
469
|
+
} finally {
|
|
470
|
+
controller.abort(new Error('RUN_CLOSED'));
|
|
471
|
+
if (!drainAttempted) await drainAdapterCalls();
|
|
472
|
+
clearTimeout(timer);
|
|
473
|
+
signal?.removeEventListener('abort', cancel);
|
|
474
|
+
// A journal failure must still drain checkpoint writes before callers or
|
|
475
|
+
// tests can remove the feature directory. Preserve the journal failure
|
|
476
|
+
// while ensuring no asynchronous checkpoint writer survives teardown.
|
|
477
|
+
try { await write; } finally {
|
|
478
|
+
try { await checkpointWrite; } finally {
|
|
479
|
+
await file.close();
|
|
480
|
+
if (resumeLock) releaseResumeLock(resumeLock);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Human-run ceremony that creates the benchmark verifier's signing key.
|
|
4
|
+
*
|
|
5
|
+
* It generates a fresh Ed25519 key, encrypts it with a passphrase you type,
|
|
6
|
+
* and stores only the encrypted envelope. It never registers the key: trust
|
|
7
|
+
* comes from a root-owned registry file that only an administrator can write,
|
|
8
|
+
* so this tool prints the exact commands and changes nothing outside your
|
|
9
|
+
* trust folder. Run it in a terminal yourself. Do not run it through an agent.
|
|
10
|
+
*/
|
|
11
|
+
import { createHash, generateKeyPairSync, randomUUID } from 'node:crypto';
|
|
12
|
+
import { constants } from 'node:fs';
|
|
13
|
+
import { lstat, mkdir, open } from 'node:fs/promises';
|
|
14
|
+
import path from 'node:path';
|
|
15
|
+
import { fileURLToPath } from 'node:url';
|
|
16
|
+
import { assertStrongPassphrase, encryptSigningKey } from './gofer-encrypted-key.mjs';
|
|
17
|
+
import { VERIFIER_EVALUATOR, accountTrustRoot, protectedVerifierRegistryPath } from './gofer-trusted-evaluator.mjs';
|
|
18
|
+
import { promptHidden } from './gofer-tty-prompt.mjs';
|
|
19
|
+
|
|
20
|
+
const denied = () => new Error('VERIFIER_KEY_CEREMONY_REQUIRES_REVIEW');
|
|
21
|
+
const shellQuote = value => `'${value.replaceAll("'", "'\"'\"'")}'`;
|
|
22
|
+
|
|
23
|
+
async function writeExclusive(filename, value) {
|
|
24
|
+
const file = await open(filename, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL |
|
|
25
|
+
constants.O_NOFOLLOW, 0o600);
|
|
26
|
+
try { await file.writeFile(value, 'utf8'); await file.sync(); } finally { await file.close(); }
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function requireUninitializedRegistry(registryPath) {
|
|
30
|
+
try {
|
|
31
|
+
await lstat(registryPath);
|
|
32
|
+
} catch (error) {
|
|
33
|
+
if (error?.code === 'ENOENT') return;
|
|
34
|
+
throw denied();
|
|
35
|
+
}
|
|
36
|
+
throw denied();
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/** First-key setup only: an installed registry requires reviewed rotation. */
|
|
40
|
+
export async function runVerifierKeyCeremony({ trustRoot, getPassphrase, existingEntries,
|
|
41
|
+
keyId = `heldout-verifier-${randomUUID()}`, registryPath = protectedVerifierRegistryPath() } = {}) {
|
|
42
|
+
if (!path.isAbsolute(trustRoot ?? '') || typeof getPassphrase !== 'function' ||
|
|
43
|
+
!path.isAbsolute(registryPath) || !/^[a-zA-Z0-9._-]{8,80}$/.test(keyId) ||
|
|
44
|
+
existingEntries !== undefined) throw denied();
|
|
45
|
+
await requireUninitializedRegistry(registryPath);
|
|
46
|
+
const rootInfo = await lstat(trustRoot);
|
|
47
|
+
if (!rootInfo.isDirectory() || rootInfo.uid !== process.getuid() || (rootInfo.mode & 0o077) !== 0) throw denied();
|
|
48
|
+
const active = path.join(trustRoot, 'active-keys');
|
|
49
|
+
await mkdir(active, { mode: 0o700, recursive: true });
|
|
50
|
+
const activeInfo = await lstat(active);
|
|
51
|
+
if (!activeInfo.isDirectory() || activeInfo.uid !== process.getuid() || (activeInfo.mode & 0o077) !== 0) throw denied();
|
|
52
|
+
|
|
53
|
+
const passphrase = await getPassphrase('New verifier passphrase (12+ characters): ');
|
|
54
|
+
assertStrongPassphrase(passphrase);
|
|
55
|
+
if (passphrase !== await getPassphrase('Repeat the passphrase: ')) throw denied();
|
|
56
|
+
await requireUninitializedRegistry(registryPath);
|
|
57
|
+
|
|
58
|
+
const pair = generateKeyPairSync('ed25519');
|
|
59
|
+
const publicKeyPem = pair.publicKey.export({ type: 'spki', format: 'pem' }).toString();
|
|
60
|
+
const envelope = await encryptSigningKey(pair.privateKey, passphrase);
|
|
61
|
+
// O_EXCL: an existing verifier identity is never replaced.
|
|
62
|
+
await writeExclusive(path.join(active, 'heldout-verifier.private.enc.json'), `${JSON.stringify(envelope)}\n`);
|
|
63
|
+
await writeExclusive(path.join(active, 'heldout-verifier.json'), `${JSON.stringify({ schemaVersion: 1,
|
|
64
|
+
host: 'codex', evaluator: VERIFIER_EVALUATOR, keyId })}\n`);
|
|
65
|
+
const pendingPath = path.join(trustRoot, 'verifier-registry.pending.json');
|
|
66
|
+
const pendingBytes = `${JSON.stringify({ schemaVersion: 1, evaluators: [
|
|
67
|
+
{ keyId, host: 'codex', evaluator: VERIFIER_EVALUATOR, publicKeyPem }] }, null, 2)}\n`;
|
|
68
|
+
await writeExclusive(pendingPath, pendingBytes);
|
|
69
|
+
const pendingSha256 = createHash('sha256').update(pendingBytes, 'utf8').digest('hex');
|
|
70
|
+
const directory = path.dirname(registryPath);
|
|
71
|
+
const stageTemplate = path.join(directory, `${path.basename(registryPath)}.${keyId}.XXXXXX`);
|
|
72
|
+
const installScript = [
|
|
73
|
+
'set -eu',
|
|
74
|
+
'PATH=/usr/bin:/bin; export PATH',
|
|
75
|
+
`stage=$(mktemp ${shellQuote(stageTemplate)})`,
|
|
76
|
+
`trap 'rm -f -- "$stage"' EXIT`,
|
|
77
|
+
`install -o root -m 0600 ${shellQuote(pendingPath)} "$stage"`,
|
|
78
|
+
// SECURITY: Check the root-owned copy against ceremony bytes, not the mutable pending file.
|
|
79
|
+
'if command -v sha256sum >/dev/null 2>&1; then staged_hash=$(sha256sum < "$stage"); elif command -v shasum >/dev/null 2>&1; then staged_hash=$(shasum -a 256 < "$stage"); else exit 1; fi',
|
|
80
|
+
`[ "\${staged_hash%% *}" = ${shellQuote(pendingSha256)} ]`,
|
|
81
|
+
'chmod 0644 "$stage"',
|
|
82
|
+
`link "$stage" ${shellQuote(registryPath)}`,
|
|
83
|
+
].join('; ');
|
|
84
|
+
return Object.freeze({ keyId, publicKeyPem, pendingPath, installCommands: Object.freeze([
|
|
85
|
+
`sudo /bin/mkdir -p ${shellQuote(directory)}`,
|
|
86
|
+
`sudo /bin/sh -c ${shellQuote(installScript)}`,
|
|
87
|
+
]) });
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if (process.argv[1] && fileURLToPath(import.meta.url) === path.resolve(process.argv[1])) {
|
|
91
|
+
if (process.argv.length !== 2) throw denied();
|
|
92
|
+
runVerifierKeyCeremony({ trustRoot: accountTrustRoot(), getPassphrase: promptHidden })
|
|
93
|
+
.then(result => {
|
|
94
|
+
process.stdout.write(`\nVerifier key created (encrypted). Key id: ${result.keyId}\n\n` +
|
|
95
|
+
'It is NOT trusted yet. An administrator must install the registry:\n\n' +
|
|
96
|
+
`${result.installCommands.map(command => ` ${command}`).join('\n')}\n\n` +
|
|
97
|
+
'Then delete the old plaintext verifier key, which a worker can read:\n\n' +
|
|
98
|
+
' rm ~/.eai-gofer-trust/staged-keys/heldout-verifier.private.pem\n');
|
|
99
|
+
})
|
|
100
|
+
.catch(error => { process.stderr.write(`${error.message}\n`); process.exitCode = 1; });
|
|
101
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/** Shared checks about where a task worktree and its Git store live. */
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { realpathSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
|
|
6
|
+
const text = value => typeof value === 'string' && value.trim().length > 0;
|
|
7
|
+
const gitEnvironment = Object.fromEntries(Object.entries(process.env)
|
|
8
|
+
.filter(([key]) => !key.startsWith('GIT_')));
|
|
9
|
+
|
|
10
|
+
export const insideDirectory = (parent, child) => {
|
|
11
|
+
const relative = path.relative(parent, child);
|
|
12
|
+
return relative === '' || (relative !== '..' && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
/** The shared Git store of a linked worktree, or null. A plain repository is
|
|
16
|
+
* refused: its Git store lies inside the workspace, so it cannot be protected. */
|
|
17
|
+
export function sharedGitDirectory(workspaceRoot) {
|
|
18
|
+
if (!text(workspaceRoot)) return null;
|
|
19
|
+
const result = spawnSync('/usr/bin/git', ['-C', workspaceRoot, 'rev-parse', '--path-format=absolute',
|
|
20
|
+
'--show-toplevel', '--git-dir', '--git-common-dir'],
|
|
21
|
+
{ encoding: 'utf8', stdio: ['ignore', 'pipe', 'pipe'], timeout: 5000, env: gitEnvironment });
|
|
22
|
+
if (result.status !== 0 || !result.stdout?.trim()) return null;
|
|
23
|
+
const [topLevel, gitDirectory, commonDirectory] = result.stdout.trim().split('\n');
|
|
24
|
+
if (!topLevel || !gitDirectory || !commonDirectory) return null;
|
|
25
|
+
try {
|
|
26
|
+
const workspace = realpathSync(workspaceRoot);
|
|
27
|
+
const common = realpathSync(commonDirectory);
|
|
28
|
+
if (realpathSync(topLevel) !== workspace || realpathSync(gitDirectory) === common ||
|
|
29
|
+
insideDirectory(workspace, common)) return null;
|
|
30
|
+
return common;
|
|
31
|
+
} catch { return null; }
|
|
32
|
+
}
|