devrites 7.2.7 → 7.3.0
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/CHANGELOG.md +40 -0
- package/README.md +6 -1
- package/docs/candidate-integrity.md +10 -0
- package/docs/cli.md +7 -1
- package/docs/command-map.md +9 -2
- package/docs/engine/commands.md +50 -4
- package/docs/orchestration.md +35 -13
- package/docs/skills.md +5 -0
- package/engine/internal/acceptance/cli.go +35 -30
- package/engine/internal/acceptance/preconditions.go +1 -1
- package/engine/internal/acceptance/run.go +138 -53
- package/engine/internal/acceptance/transcript.go +236 -45
- package/engine/internal/devritespaths/paths.go +23 -5
- package/engine/internal/gate/gate.go +6 -0
- package/engine/internal/install/apply.go +5 -0
- package/engine/internal/install/merge.go +48 -6
- package/engine/internal/install/preflight.go +21 -3
- package/engine/internal/install/uninstall.go +9 -10
- package/engine/internal/lib/apply_bundle.go +7 -1
- package/engine/internal/lib/candidate.go +20 -7
- package/engine/internal/lib/candidate_coverage.go +2 -19
- package/engine/internal/lib/closeout.go +22 -10
- package/engine/internal/lib/context.go +67 -47
- package/engine/internal/lib/context_io.go +171 -0
- package/engine/internal/lib/detect.go +8 -8
- package/engine/internal/lib/dispatch.go +6 -0
- package/engine/internal/lib/packet.go +37 -7
- package/engine/internal/lib/packet_freeze.go +60 -12
- package/engine/internal/lib/packet_integrity.go +179 -0
- package/engine/internal/lib/perturb.go +3 -3
- package/engine/internal/lib/resolve.go +10 -5
- package/engine/internal/lib/slicecheck.go +7 -1
- package/engine/internal/lib/state_edit.go +9 -2
- package/engine/internal/lib/taskgraph.go +20 -36
- package/engine/internal/lib/triggers.go +30 -31
- package/engine/internal/lib/workspace_pool.go +330 -0
- package/engine/internal/parallel/cli.go +190 -49
- package/engine/internal/parallel/git.go +47 -14
- package/engine/internal/parallel/landing.go +232 -0
- package/engine/internal/parallel/lease.go +97 -18
- package/engine/internal/parallel/ops.go +312 -155
- package/engine/internal/parallel/pathdisjoint.go +48 -4
- package/engine/internal/parallel/prune.go +133 -0
- package/engine/internal/parallel/repository.go +239 -0
- package/engine/internal/parallel/select.go +64 -42
- package/engine/internal/state/cursor.go +5 -5
- package/engine/main.go +2 -1
- package/pack/.claude/agents/devrites-proof-runner.md +16 -9
- package/pack/.claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/.claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/.claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/.claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/.claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/.claude/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/.claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/.claude/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/.claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/.claude/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/.claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/.claude/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/.claude/skills/rite-build/SKILL.md +13 -10
- package/pack/.claude/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/.claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/.claude/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/.claude/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/.claude/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/.claude/skills/rite-clarify/SKILL.md +2 -2
- package/pack/.claude/skills/rite-converge/SKILL.md +4 -4
- package/pack/.claude/skills/rite-define/SKILL.md +7 -3
- package/pack/.claude/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/.claude/skills/rite-plan/SKILL.md +11 -8
- package/pack/.claude/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/.claude/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/.claude/skills/rite-polish/SKILL.md +3 -3
- package/pack/.claude/skills/rite-prove/SKILL.md +9 -5
- package/pack/.claude/skills/rite-resolve/SKILL.md +3 -3
- package/pack/.claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/.claude/skills/rite-review/SKILL.md +4 -3
- package/pack/.claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/.claude/skills/rite-seal/SKILL.md +1 -1
- package/pack/.claude/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/.claude/skills/rite-ship/SKILL.md +2 -2
- package/pack/.claude/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/.claude/skills/rite-spec/SKILL.md +8 -5
- package/pack/.claude/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/.claude/skills/rite-temper/SKILL.md +3 -3
- package/pack/.claude/skills/rite-vet/SKILL.md +9 -5
- package/pack/.claude/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/claude/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/claude/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/claude/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/claude/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/claude/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/claude/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/generated/claude/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/claude/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/claude/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/claude/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/claude/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/claude/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/claude/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/claude/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/claude/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/claude/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/claude/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/claude/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/claude/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/claude/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/claude/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/claude/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/claude/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/claude/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/claude/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/claude/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/claude/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/claude/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/claude/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/claude/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/claude/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/claude/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/claude/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/claude/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/claude/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/claude/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/codex/agents/devrites-proof-runner.toml +16 -9
- package/pack/generated/codex/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/codex/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/codex/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/codex/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/codex/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/generated/codex/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/codex/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/codex/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/codex/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/codex/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/codex/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/codex/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/codex/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/codex/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/codex/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/codex/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/codex/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/codex/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/codex/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/codex/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/codex/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/codex/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/codex/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/codex/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/codex/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/codex/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/codex/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/codex/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/codex/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/codex/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/codex/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/codex/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/codex/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/codex/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/codex/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/devin/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/devin/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/devin/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/devin/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/devin/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/devin/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/generated/devin/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/devin/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/devin/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/devin/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/devin/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/devin/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/devin/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/devin/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/devin/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/devin/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/devin/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/devin/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/devin/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/devin/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/devin/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/devin/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/devin/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/devin/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/devin/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/devin/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/devin/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/devin/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/devin/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/devin/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/devin/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/devin/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/devin/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/devin/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/devin/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/devin/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/dsh/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/dsh/runtime/cordis.patch.yml +1 -1
- package/pack/generated/dsh/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/dsh/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/dsh/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/dsh/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/dsh/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/dsh/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/dsh/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/dsh/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/dsh/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/dsh/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/dsh/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/dsh/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/dsh/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/dsh/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/dsh/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/dsh/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/dsh/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/dsh/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/dsh/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/dsh/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/dsh/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/dsh/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/dsh/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/dsh/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/dsh/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/dsh/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/dsh/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/dsh/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/dsh/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/dsh/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/dsh/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/dsh/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/omp/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/omp/commands/rite-build.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/omp/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/omp/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/omp/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/omp/skills/devrites-lib/reference/standards/agents.md +11 -3
- package/pack/generated/omp/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/omp/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/omp/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/omp/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/omp/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/omp/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/omp/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/omp/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/omp/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/omp/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/omp/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/omp/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/omp/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/omp/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/omp/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/omp/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/omp/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/omp/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/omp/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/omp/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/omp/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/omp/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/omp/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/omp/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/omp/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/omp/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/omp/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/omp/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/omp/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/omp/skills/rite-vet/reference/review-axes.md +12 -5
- package/pack/generated/pi/agents/devrites-proof-runner.md +16 -9
- package/pack/generated/pi/prompts/rite-build.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/SKILL.md +2 -0
- package/pack/generated/pi/skills/devrites-lib/reference/candidate-integrity.md +7 -3
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-dispatch.md +3 -0
- package/pack/generated/pi/skills/devrites-lib/reference/parallel-workspaces.md +202 -0
- package/pack/generated/pi/skills/devrites-lib/reference/standards/afk-hitl.md +9 -12
- package/pack/generated/pi/skills/devrites-lib/reference/standards/agents.md +7 -3
- package/pack/generated/pi/skills/devrites-lib/reference/standards/context-hygiene.md +1 -1
- package/pack/generated/pi/skills/devrites-lib/reference/standards/gates.md +18 -9
- package/pack/generated/pi/skills/devrites-lib/reference/workspace-artifact-schema.md +10 -0
- package/pack/generated/pi/skills/rite-autocomplete/SKILL.md +13 -10
- package/pack/generated/pi/skills/rite-autocomplete/reference/lookahead.md +4 -4
- package/pack/generated/pi/skills/rite-autocomplete/reference/loop.md +13 -9
- package/pack/generated/pi/skills/rite-build/SKILL.md +13 -10
- package/pack/generated/pi/skills/rite-build/reference/afk-discipline.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/checkpoint.md +1 -1
- package/pack/generated/pi/skills/rite-build/reference/parallel-batch.md +60 -39
- package/pack/generated/pi/skills/rite-build/reference/phase-contract.md +8 -5
- package/pack/generated/pi/skills/rite-build/reference/wright-dispatch.md +7 -7
- package/pack/generated/pi/skills/rite-clarify/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-converge/SKILL.md +4 -4
- package/pack/generated/pi/skills/rite-define/SKILL.md +7 -3
- package/pack/generated/pi/skills/rite-define/reference/plan-template.md +4 -2
- package/pack/generated/pi/skills/rite-plan/SKILL.md +11 -8
- package/pack/generated/pi/skills/rite-plan/reference/dependency-graph.md +9 -2
- package/pack/generated/pi/skills/rite-plan/reference/slicing.md +2 -0
- package/pack/generated/pi/skills/rite-polish/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-prove/SKILL.md +9 -5
- package/pack/generated/pi/skills/rite-resolve/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-resolve/reference/answer-protocol.md +2 -0
- package/pack/generated/pi/skills/rite-review/SKILL.md +4 -3
- package/pack/generated/pi/skills/rite-review/reference/feature-scoped-review.md +2 -2
- package/pack/generated/pi/skills/rite-seal/SKILL.md +1 -1
- package/pack/generated/pi/skills/rite-seal/reference/phase-contract.md +1 -1
- package/pack/generated/pi/skills/rite-ship/SKILL.md +2 -2
- package/pack/generated/pi/skills/rite-ship/reference/git-ship.md +2 -2
- package/pack/generated/pi/skills/rite-spec/SKILL.md +8 -5
- package/pack/generated/pi/skills/rite-spec/reference/state-workspace.md +5 -0
- package/pack/generated/pi/skills/rite-temper/SKILL.md +3 -3
- package/pack/generated/pi/skills/rite-vet/SKILL.md +9 -5
- package/pack/generated/pi/skills/rite-vet/reference/review-axes.md +12 -5
- package/package.json +1 -1
- package/scripts/build-host-artifacts.sh +5 -6
- package/scripts/omp-generate.sh +24 -14
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
package lib
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"bytes"
|
|
5
|
+
"crypto/sha256"
|
|
6
|
+
"fmt"
|
|
7
|
+
"io"
|
|
8
|
+
"os"
|
|
9
|
+
"path/filepath"
|
|
10
|
+
"strings"
|
|
11
|
+
|
|
12
|
+
"github.com/devrites/devrites/internal/fsutil"
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
type contextSource struct {
|
|
16
|
+
data []byte
|
|
17
|
+
err error
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// contextSources retains one invocation's bytes, including optional absence.
|
|
21
|
+
type contextSources struct {
|
|
22
|
+
dir string
|
|
23
|
+
files map[string]contextSource
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
func (s *contextSources) read(name string) ([]byte, error) {
|
|
27
|
+
if s.dir == "" {
|
|
28
|
+
return nil, os.ErrNotExist
|
|
29
|
+
}
|
|
30
|
+
name = filepath.Clean(filepath.FromSlash(name))
|
|
31
|
+
if !filepath.IsLocal(name) {
|
|
32
|
+
return nil, fmt.Errorf("workspace artifact %q escapes workspace", name)
|
|
33
|
+
}
|
|
34
|
+
if source, ok := s.files[name]; ok {
|
|
35
|
+
return source.data, source.err
|
|
36
|
+
}
|
|
37
|
+
var source contextSource
|
|
38
|
+
if source.err = contextParents(s.dir, name); source.err == nil {
|
|
39
|
+
source.data, source.err = readBoundedRegularFile(filepath.Join(s.dir, name), workspaceArtifactLimit)
|
|
40
|
+
}
|
|
41
|
+
if source.err != nil {
|
|
42
|
+
source.err = fmt.Errorf("workspace artifact %s: %w", name, source.err)
|
|
43
|
+
}
|
|
44
|
+
if s.files == nil {
|
|
45
|
+
s.files = make(map[string]contextSource)
|
|
46
|
+
}
|
|
47
|
+
s.files[name] = source
|
|
48
|
+
return source.data, source.err
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Reject even in-workspace parent links: managed paths name actual directories.
|
|
52
|
+
func contextParents(dir, rel string) error {
|
|
53
|
+
parent := filepath.Dir(rel)
|
|
54
|
+
if parent == "." {
|
|
55
|
+
return nil
|
|
56
|
+
}
|
|
57
|
+
for _, part := range strings.Split(parent, string(filepath.Separator)) {
|
|
58
|
+
dir = filepath.Join(dir, part)
|
|
59
|
+
info, err := os.Lstat(dir)
|
|
60
|
+
if os.IsNotExist(err) {
|
|
61
|
+
return nil
|
|
62
|
+
}
|
|
63
|
+
if err != nil {
|
|
64
|
+
return err
|
|
65
|
+
}
|
|
66
|
+
if !info.IsDir() {
|
|
67
|
+
return fmt.Errorf("context parent %s is not a directory (symlinks are not allowed)", dir)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
return nil
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
func publishContext(featureDir string, opts contextOpts, body []byte) (out string, unchanged bool, err error) {
|
|
74
|
+
out = opts.out
|
|
75
|
+
managed := featureDir != "" && (out == "" || !filepath.IsAbs(out))
|
|
76
|
+
if out == "" {
|
|
77
|
+
out = filepath.Join("ctx", bundleName(opts))
|
|
78
|
+
}
|
|
79
|
+
base, rel := featureDir, out
|
|
80
|
+
if !managed && featureDir != "" {
|
|
81
|
+
absoluteFeature, absErr := filepath.Abs(featureDir)
|
|
82
|
+
if absErr != nil {
|
|
83
|
+
return out, false, absErr
|
|
84
|
+
}
|
|
85
|
+
if within, relErr := filepath.Rel(absoluteFeature, out); relErr == nil && filepath.IsLocal(within) {
|
|
86
|
+
managed, rel = true, within
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if managed {
|
|
90
|
+
if !filepath.IsLocal(rel) {
|
|
91
|
+
return out, false, fmt.Errorf("context output %q escapes workspace", out)
|
|
92
|
+
}
|
|
93
|
+
if err := contextParents(base, rel); err != nil {
|
|
94
|
+
return out, false, err
|
|
95
|
+
}
|
|
96
|
+
out = filepath.Join(base, rel)
|
|
97
|
+
} else {
|
|
98
|
+
base, rel = filepath.Dir(out), filepath.Base(out)
|
|
99
|
+
if err := os.MkdirAll(base, 0o755); err != nil {
|
|
100
|
+
return out, false, err
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
ws, err := os.OpenRoot(base)
|
|
104
|
+
if err != nil {
|
|
105
|
+
return out, false, err
|
|
106
|
+
}
|
|
107
|
+
defer func() { _ = ws.Close() }()
|
|
108
|
+
if err := ws.MkdirAll(filepath.Dir(rel), 0o755); err != nil {
|
|
109
|
+
return out, false, err
|
|
110
|
+
}
|
|
111
|
+
digest := []byte(fmt.Sprintf("%x\n", sha256.Sum256(body)))
|
|
112
|
+
// Check both destinations before publishing either; never follow a leaf link.
|
|
113
|
+
matches := true
|
|
114
|
+
for _, entry := range []struct {
|
|
115
|
+
name string
|
|
116
|
+
data []byte
|
|
117
|
+
}{{rel, body}, {rel + ".sha256", digest}} {
|
|
118
|
+
info, err := ws.Lstat(entry.name)
|
|
119
|
+
if os.IsNotExist(err) {
|
|
120
|
+
matches = false
|
|
121
|
+
continue
|
|
122
|
+
}
|
|
123
|
+
if err != nil {
|
|
124
|
+
return out, false, err
|
|
125
|
+
}
|
|
126
|
+
if !info.Mode().IsRegular() {
|
|
127
|
+
return out, false, fmt.Errorf("context output %s is not a regular file", entry.name)
|
|
128
|
+
}
|
|
129
|
+
// shortcut: Windows FileMode cannot prove POSIX 0600; rebuild until ACL-aware reuse is needed.
|
|
130
|
+
if info.Mode() != 0o600 || info.Size() != int64(len(entry.data)) {
|
|
131
|
+
matches = false
|
|
132
|
+
continue
|
|
133
|
+
}
|
|
134
|
+
// Open through the confined root; replacement still uses an atomic rename.
|
|
135
|
+
file, err := ws.Open(entry.name)
|
|
136
|
+
if err != nil {
|
|
137
|
+
return out, false, err
|
|
138
|
+
}
|
|
139
|
+
opened, err := file.Stat()
|
|
140
|
+
if err == nil && (opened.Mode() != 0o600 || !os.SameFile(info, opened)) {
|
|
141
|
+
err = fmt.Errorf("context output %s changed while opening", entry.name)
|
|
142
|
+
}
|
|
143
|
+
var data []byte
|
|
144
|
+
if err == nil {
|
|
145
|
+
data, err = io.ReadAll(io.LimitReader(file, int64(len(entry.data))+1))
|
|
146
|
+
}
|
|
147
|
+
if err == nil {
|
|
148
|
+
current, statErr := ws.Lstat(entry.name)
|
|
149
|
+
if statErr != nil {
|
|
150
|
+
err = statErr
|
|
151
|
+
} else if current.Mode() != 0o600 || !os.SameFile(opened, current) {
|
|
152
|
+
err = fmt.Errorf("context output %s changed while reading", entry.name)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
_ = file.Close()
|
|
156
|
+
if err != nil {
|
|
157
|
+
return out, false, err
|
|
158
|
+
}
|
|
159
|
+
matches = matches && bytes.Equal(data, entry.data)
|
|
160
|
+
}
|
|
161
|
+
if matches {
|
|
162
|
+
return out, true, nil
|
|
163
|
+
}
|
|
164
|
+
if err := fsutil.WriteFileAtomicIn(ws, rel, body, 0o600); err != nil {
|
|
165
|
+
return out, false, err
|
|
166
|
+
}
|
|
167
|
+
if err := fsutil.WriteFileAtomicIn(ws, rel+".sha256", digest, 0o600); err != nil {
|
|
168
|
+
return out, false, err
|
|
169
|
+
}
|
|
170
|
+
return out, false, nil
|
|
171
|
+
}
|
|
@@ -251,27 +251,27 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
|
|
|
251
251
|
}
|
|
252
252
|
manifests := []manifest{
|
|
253
253
|
{"go.mod", "go.mod", map[string]string{
|
|
254
|
-
"unit": "go test ./...",
|
|
254
|
+
"unit": "go test ./...",
|
|
255
255
|
"lint": "golangci-lint run", "vet": "go vet ./...", "build": "go build ./...",
|
|
256
256
|
}},
|
|
257
257
|
{"Cargo.toml", "Cargo.toml", map[string]string{
|
|
258
|
-
"unit": "cargo test",
|
|
258
|
+
"unit": "cargo test",
|
|
259
259
|
"lint": "cargo clippy --all-targets", "vet": "cargo check", "build": "cargo build",
|
|
260
260
|
}},
|
|
261
261
|
{"pyproject.toml", "pyproject.toml", map[string]string{
|
|
262
|
-
"unit": "python -m pytest",
|
|
262
|
+
"unit": "python -m pytest",
|
|
263
263
|
"lint": "ruff check", "vet": "mypy .", "build": "python -m build",
|
|
264
264
|
}},
|
|
265
265
|
{"setup.py", "setup.py", map[string]string{
|
|
266
|
-
"unit": "python -m pytest",
|
|
266
|
+
"unit": "python -m pytest",
|
|
267
267
|
"lint": "flake8", "vet": "mypy .",
|
|
268
268
|
}},
|
|
269
269
|
{"pom.xml", "pom.xml", map[string]string{
|
|
270
|
-
"unit": "mvn test",
|
|
271
|
-
"vet":
|
|
270
|
+
"unit": "mvn test",
|
|
271
|
+
"vet": "mvn -q compile", "build": "mvn package",
|
|
272
272
|
}},
|
|
273
273
|
{"build.gradle", "build.gradle", map[string]string{
|
|
274
|
-
"unit": "gradle test",
|
|
274
|
+
"unit": "gradle test",
|
|
275
275
|
"lint": "gradle check", "build": "gradle build",
|
|
276
276
|
}},
|
|
277
277
|
{"composer.json", "composer.json", map[string]string{
|
|
@@ -279,7 +279,7 @@ func manifestFallback(root string, slot string) (detectedCommand, bool) {
|
|
|
279
279
|
"vet": "vendor/bin/phpstan analyse",
|
|
280
280
|
}},
|
|
281
281
|
{"Gemfile", "Gemfile", map[string]string{
|
|
282
|
-
"unit": "bundle exec rspec",
|
|
282
|
+
"unit": "bundle exec rspec",
|
|
283
283
|
"lint": "bundle exec rubocop",
|
|
284
284
|
}},
|
|
285
285
|
{"mix.exs", "mix.exs", map[string]string{
|
|
@@ -50,6 +50,7 @@ For a reviewer role, return --result <file> stores the return verbatim as
|
|
|
50
50
|
packets/<wave>-<role>.return.md (never overwritten), derives the outcome
|
|
51
51
|
from its Outcome/Counts head (malformed records gap), refuses an --outcome
|
|
52
52
|
that disagrees, and records its Finding: keys for metrics summary.
|
|
53
|
+
Reviewer returns require --result or --outcome; omitting both leaves the role pending.
|
|
53
54
|
|
|
54
55
|
A build wave with a wright role must name its slice(s) with --slice; a vet
|
|
55
56
|
wave may (slice-bound recheck), and so may a reviewer-only wave (uncapped),
|
|
@@ -324,6 +325,8 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
324
325
|
if err := lintReviewCandidate(root, slug, packet); err != nil {
|
|
325
326
|
return fail("BLOCKED: %v", err)
|
|
326
327
|
}
|
|
328
|
+
} else if err := lintPacketDiffs(root, slug, packet, false); err != nil {
|
|
329
|
+
return fail("BLOCKED: %v", err)
|
|
327
330
|
}
|
|
328
331
|
break
|
|
329
332
|
}
|
|
@@ -455,6 +458,9 @@ func dispatchLocked(root, slug, sub, phase, wave, role, handle, reason string, r
|
|
|
455
458
|
if r.Returned {
|
|
456
459
|
return fail("role %q already returned", role)
|
|
457
460
|
}
|
|
461
|
+
if isReviewerRole(role) && result == "" && outcome == "" {
|
|
462
|
+
return fail("reviewer %q needs --result <file> or --outcome <findings|no-findings|gap>", role)
|
|
463
|
+
}
|
|
458
464
|
if outcome != "" && !slices.Contains([]string{"findings", "no-findings", "gap"}, outcome) {
|
|
459
465
|
return fail("--outcome %q is not findings, no-findings, or gap", outcome)
|
|
460
466
|
}
|
|
@@ -91,6 +91,21 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
91
91
|
if err != nil {
|
|
92
92
|
return fail(err)
|
|
93
93
|
}
|
|
94
|
+
code := 0
|
|
95
|
+
if err := state.WithFeatureLock(root, slug, func() error {
|
|
96
|
+
code = renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck, wright, doubt, stdout, stderr)
|
|
97
|
+
return nil
|
|
98
|
+
}); err != nil {
|
|
99
|
+
return fail(err)
|
|
100
|
+
}
|
|
101
|
+
return code
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
func renderPacketLocked(root, slug, workspace, wave, phase, role, slice, checks, focus, recheck string, wright, doubt bool, stdout, stderr io.Writer) int {
|
|
105
|
+
fail := func(err error) int {
|
|
106
|
+
fmt.Fprintf(stderr, "packet: %v\n", err)
|
|
107
|
+
return 3
|
|
108
|
+
}
|
|
94
109
|
rel := workspaceRel(root, workspace)
|
|
95
110
|
bundle := "ctx/" + bundleName(contextOpts{phase: phase, role: role, slice: slice, recheck: recheck})
|
|
96
111
|
if info, err := os.Stat(filepath.Join(workspace, filepath.FromSlash(bundle))); err != nil || !info.Mode().IsRegular() {
|
|
@@ -188,16 +203,24 @@ func RunPacket(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
188
203
|
return fail(err)
|
|
189
204
|
}
|
|
190
205
|
// Lint a sibling draft so a refused re-render keeps the packet already on disk.
|
|
191
|
-
|
|
192
|
-
if err
|
|
206
|
+
draftFile, err := os.CreateTemp(filepath.Dir(out), "."+name+".draft-*")
|
|
207
|
+
if err != nil {
|
|
208
|
+
return fail(err)
|
|
209
|
+
}
|
|
210
|
+
draft := draftFile.Name()
|
|
211
|
+
defer func() { _ = os.Remove(draft) }()
|
|
212
|
+
_, writeErr := io.WriteString(draftFile, b.String())
|
|
213
|
+
if writeErr == nil {
|
|
214
|
+
writeErr = draftFile.Sync()
|
|
215
|
+
}
|
|
216
|
+
closeErr := draftFile.Close()
|
|
217
|
+
if err := errors.Join(writeErr, closeErr); err != nil {
|
|
193
218
|
return fail(err)
|
|
194
219
|
}
|
|
195
220
|
if err := lintRenderedPacket(root, slug, draft, role, wright, finalPhase); err != nil {
|
|
196
|
-
_ = os.Remove(draft)
|
|
197
221
|
return fail(fmt.Errorf("BLOCKED: %w", err))
|
|
198
222
|
}
|
|
199
223
|
if err := os.Rename(draft, out); err != nil {
|
|
200
|
-
_ = os.Remove(draft)
|
|
201
224
|
return fail(err)
|
|
202
225
|
}
|
|
203
226
|
fmt.Fprintf(stdout, "packet: %s/packets/%s (%s, %d bytes)\n", rel, name, role, b.Len())
|
|
@@ -216,7 +239,7 @@ func lintRenderedPacket(root, slug, path, role string, wright, finalPhase bool)
|
|
|
216
239
|
if finalPhase {
|
|
217
240
|
return lintReviewCandidate(root, slug, path)
|
|
218
241
|
}
|
|
219
|
-
return
|
|
242
|
+
return lintPacketDiffs(root, slug, path, false)
|
|
220
243
|
}
|
|
221
244
|
|
|
222
245
|
// frozenCandidate returns packets/<wave>.sha256 when candidate freeze wrote
|
|
@@ -231,8 +254,15 @@ func frozenCandidate(workspace, wave string) (sums string, partial bool, err err
|
|
|
231
254
|
if err != nil {
|
|
232
255
|
return "", false, fmt.Errorf("packets/%s.sha256: %w", wave, err)
|
|
233
256
|
}
|
|
234
|
-
|
|
235
|
-
|
|
257
|
+
bindings, err := frozenDiffBindings(raw)
|
|
258
|
+
if err != nil || len(bindings) != 1 || bindings[0].name != "packets/"+wave+".diff" {
|
|
259
|
+
return "", false, fmt.Errorf("packets/%s.sha256 must bind exactly packets/%s.diff with a diff SHA256 checksum", wave, wave)
|
|
260
|
+
}
|
|
261
|
+
if _, _, err := candidatePathInfo(workspace, "packets/"+wave+".diff"); err != nil {
|
|
262
|
+
return "", false, err
|
|
263
|
+
}
|
|
264
|
+
if err := verifyFrozenDiff(filepath.Join(workspace, "packets", wave+".diff"), bindings[0].digest); err != nil {
|
|
265
|
+
return "", false, fmt.Errorf("packets/%s.diff: %w", wave, err)
|
|
236
266
|
}
|
|
237
267
|
if len(raw) <= maxContractBytes/2 {
|
|
238
268
|
return string(raw), false, nil
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
package lib
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"bytes"
|
|
4
5
|
"crypto/sha256"
|
|
5
6
|
"errors"
|
|
6
7
|
"fmt"
|
|
@@ -80,6 +81,22 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
80
81
|
if err != nil {
|
|
81
82
|
return fail(err)
|
|
82
83
|
}
|
|
84
|
+
code := 0
|
|
85
|
+
if err := state.WithFeatureLock(root, slug, func() error {
|
|
86
|
+
code = freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree, paths, stdout, stderr)
|
|
87
|
+
return nil
|
|
88
|
+
}); err != nil {
|
|
89
|
+
return fail(err)
|
|
90
|
+
}
|
|
91
|
+
return code
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
func freezeCandidateLocked(root, slug, workspace, wave, slice, base, worktree string, paths []string, stdout, stderr io.Writer) int {
|
|
95
|
+
fail := func(err error) int {
|
|
96
|
+
fmt.Fprintf(stderr, "candidate freeze: %v\n", err)
|
|
97
|
+
return 3
|
|
98
|
+
}
|
|
99
|
+
var err error
|
|
83
100
|
if slice != "" {
|
|
84
101
|
if paths, err = slicePaths(root, slug, slice); err != nil {
|
|
85
102
|
return fail(err)
|
|
@@ -98,13 +115,10 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
98
115
|
if err := validateCandidateRows(project, rows); err != nil {
|
|
99
116
|
return fail(err)
|
|
100
117
|
}
|
|
101
|
-
|
|
118
|
+
digest, err := hashCandidate(project, rows, true)
|
|
102
119
|
if err != nil {
|
|
103
120
|
return fail(err)
|
|
104
121
|
}
|
|
105
|
-
if len(diff) == 0 {
|
|
106
|
-
return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
|
|
107
|
-
}
|
|
108
122
|
head := ""
|
|
109
123
|
if worktree != "" {
|
|
110
124
|
// Review binds to the transfer commit, so the frozen tree must be exactly it.
|
|
@@ -115,24 +129,27 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
115
129
|
if len(dirty) != 0 {
|
|
116
130
|
return fail(errors.New("the worktree has uncommitted changes on the path set; the frozen candidate must be its HEAD transfer commit"))
|
|
117
131
|
}
|
|
118
|
-
head =
|
|
132
|
+
head = commit
|
|
119
133
|
}
|
|
120
|
-
|
|
134
|
+
diff, baseCommit, err := freezeDiff(project, base, rows)
|
|
121
135
|
if err != nil {
|
|
122
136
|
return fail(err)
|
|
123
137
|
}
|
|
138
|
+
if len(diff) == 0 {
|
|
139
|
+
return fail(errors.New("the path set has no change against the base; there is no candidate to freeze"))
|
|
140
|
+
}
|
|
124
141
|
var sums strings.Builder
|
|
125
|
-
fmt.Fprintf(&sums, "%s %s\nbase %s\n
|
|
142
|
+
fmt.Fprintf(&sums, "%s %s\nbase %s\n", candidateDigestDomain, candidateDigestVersion, baseCommit)
|
|
143
|
+
if head != "" {
|
|
144
|
+
fmt.Fprintf(&sums, "head %s\n", head)
|
|
145
|
+
}
|
|
146
|
+
fmt.Fprintf(&sums, "diff %x packets/%s.diff\n", sha256.Sum256(diff), wave)
|
|
126
147
|
for _, row := range rows {
|
|
127
148
|
if row.state != "present" {
|
|
128
149
|
fmt.Fprintf(&sums, "file absent %s\n", row.path)
|
|
129
150
|
continue
|
|
130
151
|
}
|
|
131
|
-
file
|
|
132
|
-
if err := hashCandidateFile(file, project, row); err != nil {
|
|
133
|
-
return fail(fmt.Errorf("candidate %q: %w", row.path, err))
|
|
134
|
-
}
|
|
135
|
-
fmt.Fprintf(&sums, "file %x %s\n", file.Sum(nil), row.path)
|
|
152
|
+
fmt.Fprintf(&sums, "file %s %s\n", row.fileDigest, row.path)
|
|
136
153
|
}
|
|
137
154
|
fmt.Fprintf(&sums, "aggregate %s\n", digest)
|
|
138
155
|
|
|
@@ -143,6 +160,9 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
143
160
|
} else if err != nil && !errors.Is(err, os.ErrNotExist) {
|
|
144
161
|
return fail(err)
|
|
145
162
|
}
|
|
163
|
+
if err := revalidateFrozenCandidate(project, baseCommit, rows, digest, diff, head); err != nil {
|
|
164
|
+
return fail(err)
|
|
165
|
+
}
|
|
146
166
|
if err := os.MkdirAll(dir, 0o755); err != nil {
|
|
147
167
|
return fail(err)
|
|
148
168
|
}
|
|
@@ -157,6 +177,34 @@ func RunCandidateFreeze(root string, args []string, stdout, stderr io.Writer) in
|
|
|
157
177
|
return 0
|
|
158
178
|
}
|
|
159
179
|
|
|
180
|
+
// Bracket Git's mutable worktree reads; refuse observed drift, not a filesystem snapshot guarantee.
|
|
181
|
+
func revalidateFrozenCandidate(project, base string, rows []candidateRow, digest string, diff []byte, head string) error {
|
|
182
|
+
currentDiff, commit, err := freezeDiff(project, base, rows)
|
|
183
|
+
if err != nil {
|
|
184
|
+
return err
|
|
185
|
+
}
|
|
186
|
+
if commit != base || !bytes.Equal(currentDiff, diff) {
|
|
187
|
+
return errors.New("candidate diff changed during freeze; retry under a new wave")
|
|
188
|
+
}
|
|
189
|
+
current, err := hashCandidate(project, rows, false)
|
|
190
|
+
if err != nil {
|
|
191
|
+
return fmt.Errorf("candidate changed during freeze: %w", err)
|
|
192
|
+
}
|
|
193
|
+
if current != digest {
|
|
194
|
+
return errors.New("candidate bytes changed during freeze; retry under a new wave")
|
|
195
|
+
}
|
|
196
|
+
if head != "" {
|
|
197
|
+
dirty, currentHead, err := freezeDiff(project, "HEAD", rows)
|
|
198
|
+
if err != nil {
|
|
199
|
+
return err
|
|
200
|
+
}
|
|
201
|
+
if currentHead != head || len(dirty) != 0 {
|
|
202
|
+
return errors.New("worktree HEAD or path set changed during freeze; candidate must remain its clean transfer commit")
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
return nil
|
|
206
|
+
}
|
|
207
|
+
|
|
160
208
|
// slicePaths is the slice's tasks.md write set: Writer allowlist, else Files
|
|
161
209
|
// likely touched.
|
|
162
210
|
func slicePaths(root, slug, slice string) ([]string, error) {
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
package lib
|
|
2
|
+
|
|
3
|
+
import (
|
|
4
|
+
"crypto/sha256"
|
|
5
|
+
"encoding/hex"
|
|
6
|
+
"errors"
|
|
7
|
+
"fmt"
|
|
8
|
+
"io"
|
|
9
|
+
"os"
|
|
10
|
+
"path/filepath"
|
|
11
|
+
"strings"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
// Binary patches can exceed the candidate's raw-byte bound; stream verification.
|
|
15
|
+
const maxFrozenDiffBytes = 4 * maxCandidateTotalBytes
|
|
16
|
+
|
|
17
|
+
type frozenDiffBinding struct {
|
|
18
|
+
name string
|
|
19
|
+
digest string
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
func frozenDiffBindings(raw []byte) ([]frozenDiffBinding, error) {
|
|
23
|
+
var bindings []frozenDiffBinding
|
|
24
|
+
for _, line := range strings.Split(string(raw), "\n") {
|
|
25
|
+
fields := strings.Fields(line)
|
|
26
|
+
if len(fields) == 0 || fields[0] != "diff" {
|
|
27
|
+
continue
|
|
28
|
+
}
|
|
29
|
+
if len(fields) != 3 || len(fields[1]) != sha256.Size*2 || diffPathRE.FindString(fields[2]) != fields[2] {
|
|
30
|
+
return nil, errors.New("malformed diff checksum; use diff <SHA256> <path.diff|path.patch>")
|
|
31
|
+
}
|
|
32
|
+
if _, err := hex.DecodeString(fields[1]); err != nil {
|
|
33
|
+
return nil, errors.New("malformed diff SHA256 checksum")
|
|
34
|
+
}
|
|
35
|
+
bindings = append(bindings, frozenDiffBinding{name: fields[2], digest: strings.ToLower(fields[1])})
|
|
36
|
+
}
|
|
37
|
+
return bindings, nil
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
func verifyFrozenDiff(name, expected string) error {
|
|
41
|
+
before, err := os.Lstat(name)
|
|
42
|
+
if err != nil {
|
|
43
|
+
return err
|
|
44
|
+
}
|
|
45
|
+
if !before.Mode().IsRegular() || before.Size() <= 0 {
|
|
46
|
+
return errors.New("frozen candidate diff must be a non-empty regular file")
|
|
47
|
+
}
|
|
48
|
+
if before.Size() > maxFrozenDiffBytes {
|
|
49
|
+
return errors.New("frozen candidate diff exceeds 1 GiB limit")
|
|
50
|
+
}
|
|
51
|
+
// #nosec G304 -- validated regular artifact, bounded streaming read below
|
|
52
|
+
file, err := os.Open(name)
|
|
53
|
+
if err != nil {
|
|
54
|
+
return err
|
|
55
|
+
}
|
|
56
|
+
defer func() { _ = file.Close() }()
|
|
57
|
+
opened, err := file.Stat()
|
|
58
|
+
if err != nil {
|
|
59
|
+
return err
|
|
60
|
+
}
|
|
61
|
+
if !opened.Mode().IsRegular() || !os.SameFile(before, opened) || before.Size() != opened.Size() || !before.ModTime().Equal(opened.ModTime()) {
|
|
62
|
+
return errors.New("frozen candidate diff changed while opening")
|
|
63
|
+
}
|
|
64
|
+
digest := sha256.New()
|
|
65
|
+
count, err := io.Copy(digest, io.LimitReader(file, maxFrozenDiffBytes+1))
|
|
66
|
+
if err != nil {
|
|
67
|
+
return err
|
|
68
|
+
}
|
|
69
|
+
after, err := file.Stat()
|
|
70
|
+
if err != nil {
|
|
71
|
+
return err
|
|
72
|
+
}
|
|
73
|
+
current, err := os.Lstat(name)
|
|
74
|
+
if err != nil {
|
|
75
|
+
return err
|
|
76
|
+
}
|
|
77
|
+
if count != before.Size() || !os.SameFile(opened, current) || after.Size() != count || current.Size() != count || !opened.ModTime().Equal(after.ModTime()) || !opened.ModTime().Equal(current.ModTime()) {
|
|
78
|
+
return errors.New("frozen candidate diff changed while reading")
|
|
79
|
+
}
|
|
80
|
+
if hex.EncodeToString(digest.Sum(nil)) != expected {
|
|
81
|
+
return errors.New("frozen candidate diff SHA256 mismatch; freeze the intended bytes under a new wave")
|
|
82
|
+
}
|
|
83
|
+
return nil
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
func resolvePacketDiff(root, slug, name string) (string, error) {
|
|
87
|
+
clean := filepath.Clean(filepath.FromSlash(name))
|
|
88
|
+
if clean == ".." || strings.HasPrefix(clean, ".."+string(filepath.Separator)) || clean != filepath.FromSlash(name) {
|
|
89
|
+
return "", fmt.Errorf("unsafe frozen candidate diff path %q", name)
|
|
90
|
+
}
|
|
91
|
+
dirs := []string{projectDir(root), featureDir(root, slug)}
|
|
92
|
+
// Freeze's checksum rows use this workspace-relative namespace, never a product packets/ directory.
|
|
93
|
+
if strings.HasPrefix(name, "packets/") {
|
|
94
|
+
dirs = []string{featureDir(root, slug)}
|
|
95
|
+
}
|
|
96
|
+
if filepath.IsAbs(name) {
|
|
97
|
+
parent, err := filepath.EvalSymlinks(filepath.Dir(name))
|
|
98
|
+
if err != nil {
|
|
99
|
+
return "", err
|
|
100
|
+
}
|
|
101
|
+
dirs = []string{parent}
|
|
102
|
+
name = filepath.Base(name)
|
|
103
|
+
}
|
|
104
|
+
for _, dir := range dirs {
|
|
105
|
+
info, exists, err := candidatePathInfo(dir, filepath.ToSlash(name))
|
|
106
|
+
if err != nil {
|
|
107
|
+
return "", err
|
|
108
|
+
}
|
|
109
|
+
if exists {
|
|
110
|
+
if !info.Mode().IsRegular() {
|
|
111
|
+
return "", errors.New("frozen candidate diff is not a regular file")
|
|
112
|
+
}
|
|
113
|
+
parent, err := filepath.EvalSymlinks(dir)
|
|
114
|
+
if err != nil {
|
|
115
|
+
return "", err
|
|
116
|
+
}
|
|
117
|
+
return filepath.Join(parent, filepath.FromSlash(name)), nil
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
return "", fmt.Errorf("frozen candidate diff %q does not exist", name)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Caller may already hold the feature lock (dispatch); never acquire it here.
|
|
124
|
+
func lintPacketDiffs(root, slug, packet string, required bool) error {
|
|
125
|
+
raw, err := readBoundedRegularFile(packet, maxContractBytes)
|
|
126
|
+
if err != nil {
|
|
127
|
+
return fmt.Errorf("reviewer packet %s: %w", packet, err)
|
|
128
|
+
}
|
|
129
|
+
bindings, err := frozenDiffBindings(raw)
|
|
130
|
+
if err != nil {
|
|
131
|
+
return err
|
|
132
|
+
}
|
|
133
|
+
bound := make(map[string]string, len(bindings))
|
|
134
|
+
for _, binding := range bindings {
|
|
135
|
+
path, err := resolvePacketDiff(root, slug, binding.name)
|
|
136
|
+
if err != nil {
|
|
137
|
+
return err
|
|
138
|
+
}
|
|
139
|
+
if prior, ok := bound[path]; ok {
|
|
140
|
+
if prior != binding.digest {
|
|
141
|
+
return fmt.Errorf("conflicting diff checksums for %s", binding.name)
|
|
142
|
+
}
|
|
143
|
+
continue
|
|
144
|
+
}
|
|
145
|
+
if err := verifyFrozenDiff(path, binding.digest); err != nil {
|
|
146
|
+
return fmt.Errorf("%s: %w", binding.name, err)
|
|
147
|
+
}
|
|
148
|
+
bound[path] = binding.digest
|
|
149
|
+
}
|
|
150
|
+
var names []string
|
|
151
|
+
for _, line := range strings.Split(string(raw), "\n") {
|
|
152
|
+
if entry, candidate := strings.CutPrefix(line, "file "); candidate {
|
|
153
|
+
identity, path, complete := strings.Cut(entry, " ")
|
|
154
|
+
// Candidate file identities describe product paths, not reviewer inputs.
|
|
155
|
+
if complete && path != "" {
|
|
156
|
+
if identity == "absent" {
|
|
157
|
+
continue
|
|
158
|
+
}
|
|
159
|
+
if digest, err := hex.DecodeString(identity); err == nil && len(digest) == sha256.Size {
|
|
160
|
+
continue
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
names = append(names, diffPathRE.FindAllString(line, -1)...)
|
|
165
|
+
}
|
|
166
|
+
if required && len(names) == 0 {
|
|
167
|
+
return errors.New("a Review or Seal reviewer packet must name the frozen candidate diff and bind its bytes with diff <SHA256> <path.diff|path.patch>")
|
|
168
|
+
}
|
|
169
|
+
for _, name := range names {
|
|
170
|
+
path, err := resolvePacketDiff(root, slug, name)
|
|
171
|
+
if err != nil {
|
|
172
|
+
return err
|
|
173
|
+
}
|
|
174
|
+
if _, ok := bound[path]; !ok {
|
|
175
|
+
return fmt.Errorf("unbound frozen candidate diff %s; add diff <SHA256> %s to the packet", name, name)
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return nil
|
|
179
|
+
}
|
|
@@ -180,7 +180,7 @@ func pathSetIdentity(project string, paths []string) (candidateIdentity, error)
|
|
|
180
180
|
if err != nil {
|
|
181
181
|
return candidateIdentity{}, err
|
|
182
182
|
}
|
|
183
|
-
digest, err := hashCandidate(project, rows)
|
|
183
|
+
digest, err := hashCandidate(project, rows, false)
|
|
184
184
|
return candidateIdentity{digest: digest, rows: rows}, err
|
|
185
185
|
}
|
|
186
186
|
|
|
@@ -244,7 +244,7 @@ func runPerturbProbe(project string, identity candidateIdentity, gate *acceptanc
|
|
|
244
244
|
probe.reason = "candidate copy failed: " + oneLine(err.Error())
|
|
245
245
|
return probe
|
|
246
246
|
}
|
|
247
|
-
if probe.copyStart, err = hashCandidate(base, rows); err != nil || probe.copyStart != identity.digest {
|
|
247
|
+
if probe.copyStart, err = hashCandidate(base, rows, false); err != nil || probe.copyStart != identity.digest {
|
|
248
248
|
probe.reason = "the isolated copy does not hash to the candidate digest"
|
|
249
249
|
return probe
|
|
250
250
|
}
|
|
@@ -267,7 +267,7 @@ func runPerturbProbe(project string, identity candidateIdentity, gate *acceptanc
|
|
|
267
267
|
return probe
|
|
268
268
|
}
|
|
269
269
|
if err := validateCandidateRows(base, rows); err == nil {
|
|
270
|
-
probe.copyRestored, _ = hashCandidate(base, rows)
|
|
270
|
+
probe.copyRestored, _ = hashCandidate(base, rows, false)
|
|
271
271
|
}
|
|
272
272
|
if probe.copyRestored != identity.digest {
|
|
273
273
|
probe.reason = "the reverted copy does not hash to the candidate digest"
|