devrites 7.2.8 → 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 +32 -0
- package/README.md +1 -1
- package/docs/candidate-integrity.md +10 -0
- package/docs/cli.md +7 -1
- package/docs/command-map.md +5 -2
- package/docs/engine/commands.md +28 -4
- package/docs/orchestration.md +26 -13
- package/docs/skills.md +5 -0
- package/engine/internal/acceptance/cli.go +27 -26
- package/engine/internal/acceptance/preconditions.go +1 -1
- package/engine/internal/acceptance/run.go +42 -16
- package/engine/internal/acceptance/transcript.go +236 -45
- package/engine/internal/devritespaths/paths.go +16 -3
- package/engine/internal/gate/gate.go +6 -0
- package/engine/internal/lib/candidate.go +20 -7
- package/engine/internal/lib/candidate_coverage.go +2 -19
- package/engine/internal/lib/closeout.go +16 -4
- package/engine/internal/lib/context.go +64 -45
- package/engine/internal/lib/context_io.go +171 -0
- package/engine/internal/lib/detect.go +8 -8
- package/engine/internal/lib/dispatch.go +2 -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/slicecheck.go +7 -1
- package/engine/internal/lib/taskgraph.go +20 -36
- package/engine/internal/lib/triggers.go +28 -29
- 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/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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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 +1 -1
- 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 +57 -36
- 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
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
package acceptance
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
+
"errors"
|
|
4
5
|
"fmt"
|
|
6
|
+
"io"
|
|
5
7
|
"os"
|
|
6
8
|
"path/filepath"
|
|
7
9
|
"regexp"
|
|
10
|
+
"strconv"
|
|
8
11
|
"strings"
|
|
9
12
|
"time"
|
|
10
13
|
|
|
14
|
+
"github.com/devrites/devrites/internal/fsutil"
|
|
11
15
|
"github.com/devrites/devrites/internal/secretpattern"
|
|
12
16
|
)
|
|
13
17
|
|
|
@@ -19,7 +23,11 @@ var CandidateDigest func(root, slug string) (string, error)
|
|
|
19
23
|
// maxTranscriptOutput caps the redacted output a transcript keeps.
|
|
20
24
|
const maxTranscriptOutput = 1 << 20
|
|
21
25
|
|
|
26
|
+
// Redaction can expand short credentials in metadata as well as output.
|
|
27
|
+
const maxTranscriptBytes = 2*MaxLedgerBytes + maxTranscriptOutput + (64 << 10)
|
|
28
|
+
|
|
22
29
|
var unsafeNameRE = regexp.MustCompile(`[^A-Za-z0-9._-]`)
|
|
30
|
+
var transcriptDigestRE = regexp.MustCompile(`^[0-9a-f]{64}$`)
|
|
23
31
|
|
|
24
32
|
func candidateLabel(root, slug string) string {
|
|
25
33
|
if CandidateDigest == nil {
|
|
@@ -27,77 +35,260 @@ func candidateLabel(root, slug string) string {
|
|
|
27
35
|
}
|
|
28
36
|
digest, err := CandidateDigest(root, slug)
|
|
29
37
|
if err != nil {
|
|
30
|
-
return "unavailable: " +
|
|
38
|
+
return "unavailable: " + transcriptMetadata(err.Error())
|
|
31
39
|
}
|
|
32
40
|
return digest
|
|
33
41
|
}
|
|
34
42
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
43
|
+
func transcriptMetadata(value string) string {
|
|
44
|
+
redacted, _ := secretpattern.Redact(oneLine(value))
|
|
45
|
+
return redacted
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
func transcriptGateName(id string) string {
|
|
49
|
+
name := unsafeNameRE.ReplaceAllString(transcriptMetadata(id), "_")
|
|
50
|
+
if name == "." || name == ".." || name == "" {
|
|
51
|
+
return "_"
|
|
52
|
+
}
|
|
53
|
+
return name
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// transcriptDirectory opens each directory without accepting symlink parents.
|
|
57
|
+
// Holding the resulting root also confines publication if a parent is renamed.
|
|
58
|
+
func transcriptDirectory(work, id string, create bool) (*os.Root, error) {
|
|
59
|
+
root, err := os.OpenRoot(work)
|
|
60
|
+
if err != nil {
|
|
61
|
+
return nil, err
|
|
62
|
+
}
|
|
63
|
+
for _, name := range []string{"references", "gates", transcriptGateName(id)} {
|
|
64
|
+
info, err := root.Lstat(name)
|
|
65
|
+
if errors.Is(err, os.ErrNotExist) && create {
|
|
66
|
+
if err = root.Mkdir(name, 0o700); err == nil || errors.Is(err, os.ErrExist) {
|
|
67
|
+
info, err = root.Lstat(name)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if err != nil || !info.IsDir() {
|
|
71
|
+
_ = root.Close()
|
|
72
|
+
if err == nil {
|
|
73
|
+
err = fmt.Errorf("unsafe transcript directory %s", name)
|
|
74
|
+
}
|
|
75
|
+
return nil, err
|
|
76
|
+
}
|
|
77
|
+
next, err := root.OpenRoot(name)
|
|
78
|
+
if err == nil {
|
|
79
|
+
var opened os.FileInfo
|
|
80
|
+
opened, err = next.Stat(".")
|
|
81
|
+
if err == nil && !os.SameFile(info, opened) {
|
|
82
|
+
err = fmt.Errorf("transcript directory changed: %s", name)
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
_ = root.Close()
|
|
86
|
+
if err != nil {
|
|
87
|
+
if next != nil {
|
|
88
|
+
_ = next.Close()
|
|
89
|
+
}
|
|
90
|
+
return nil, err
|
|
91
|
+
}
|
|
92
|
+
root = next
|
|
93
|
+
}
|
|
94
|
+
return root, nil
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// writeTranscripts atomically publishes private, complete executed records.
|
|
98
|
+
// Reuse points to the original file, preserving its historical candidate labels.
|
|
99
|
+
// OUTPUT_SHA256 and OUTPUT_BYTES always describe original, not redacted output.
|
|
39
100
|
func writeTranscripts(work string, doc *Document, results []GateResult, before, after string) ([]string, error) {
|
|
40
101
|
gates := map[string]*Gate{}
|
|
41
102
|
for _, gate := range doc.Gates {
|
|
42
103
|
gates[gate.ID] = gate
|
|
43
104
|
}
|
|
44
105
|
prefix := "nodigest"
|
|
45
|
-
if
|
|
106
|
+
if transcriptDigestRE.MatchString(before) {
|
|
46
107
|
prefix = before[:12]
|
|
47
108
|
}
|
|
48
109
|
var paths []string
|
|
49
110
|
for _, result := range results {
|
|
50
111
|
gate := gates[result.ID]
|
|
51
|
-
if gate == nil
|
|
112
|
+
if gate == nil {
|
|
52
113
|
continue
|
|
53
114
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
115
|
+
if result.Reused && result.Origin != "" {
|
|
116
|
+
paths = append(paths, result.Origin)
|
|
117
|
+
continue
|
|
57
118
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
return paths, err
|
|
119
|
+
if !result.Ran {
|
|
120
|
+
continue
|
|
61
121
|
}
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
var file string
|
|
66
|
-
if result.Reused {
|
|
67
|
-
file = filepath.Join(dir, prefix+"-reused.log")
|
|
68
|
-
fmt.Fprintf(&body, "RESULT: reused (inputs unchanged; not executed)\nPRIOR_EVIDENCE: %s\n", gate.Evidence)
|
|
69
|
-
} else {
|
|
70
|
-
attempt := 1
|
|
71
|
-
if entries, err := os.ReadDir(dir); err == nil {
|
|
72
|
-
for _, entry := range entries {
|
|
73
|
-
if strings.HasPrefix(entry.Name(), prefix+"-a") {
|
|
74
|
-
attempt++
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
file = filepath.Join(dir, fmt.Sprintf("%s-a%d.log", prefix, attempt))
|
|
79
|
-
output, masked := secretpattern.Redact(result.Outcome.Output)
|
|
80
|
-
truncated := len(output) > maxTranscriptOutput
|
|
81
|
-
if truncated {
|
|
82
|
-
output = output[:maxTranscriptOutput]
|
|
83
|
-
}
|
|
84
|
-
verdict := "fail"
|
|
85
|
-
if result.Passed {
|
|
86
|
-
verdict = "pass"
|
|
87
|
-
}
|
|
88
|
-
fmt.Fprintf(&body, "ATTEMPT: %d\nSTARTED: %s\nENDED: %s\nEXIT: %s\nEXPECT: %s\nRESULT: %s\n",
|
|
89
|
-
attempt, result.Started.Format(time.RFC3339Nano), result.Ended.Format(time.RFC3339Nano), exitLabel(result.Outcome), gate.Expect, verdict)
|
|
90
|
-
fmt.Fprintf(&body, "OUTPUT_SHA256: %s\nOUTPUT_BYTES: %d\nREDACTIONS: %d\nTRUNCATED: %t\n--- OUTPUT ---\n%s\n--- END OUTPUT ---\n",
|
|
91
|
-
OutputDigest(result.Outcome.Output), result.Outcome.OutputBytes, masked, truncated, output)
|
|
122
|
+
dir, err := transcriptDirectory(work, gate.ID, true)
|
|
123
|
+
if err != nil {
|
|
124
|
+
return paths, err
|
|
92
125
|
}
|
|
93
|
-
|
|
126
|
+
file, err := publishTranscript(dir, gate, result, prefix, before, after)
|
|
127
|
+
_ = dir.Close()
|
|
128
|
+
if err != nil {
|
|
94
129
|
return paths, err
|
|
95
130
|
}
|
|
96
|
-
paths = append(paths, file)
|
|
131
|
+
paths = append(paths, filepath.Join(work, "references", "gates", transcriptGateName(gate.ID), file))
|
|
97
132
|
}
|
|
98
133
|
return paths, nil
|
|
99
134
|
}
|
|
100
135
|
|
|
136
|
+
func publishTranscript(dir *os.Root, gate *Gate, result GateResult, prefix, before, after string) (string, error) {
|
|
137
|
+
attempt := 1
|
|
138
|
+
for {
|
|
139
|
+
file := fmt.Sprintf("%s-a%d.log", prefix, attempt)
|
|
140
|
+
if _, err := dir.Lstat(file); errors.Is(err, os.ErrNotExist) {
|
|
141
|
+
break
|
|
142
|
+
} else if err != nil {
|
|
143
|
+
return "", err
|
|
144
|
+
}
|
|
145
|
+
attempt++
|
|
146
|
+
}
|
|
147
|
+
var body strings.Builder
|
|
148
|
+
fmt.Fprintf(&body, "DEVRITES-GATE-TRANSCRIPT v2\nGATE: %s\nTITLE: %s\nCOMMAND: %s\nCWD: %s\nDEFINITION_SHA256: %s\n",
|
|
149
|
+
transcriptMetadata(gate.ID), transcriptMetadata(gate.Title), transcriptMetadata(gate.Check), transcriptMetadata(valueOr(gate.Cwd, ".")), DefinitionDigest(gate))
|
|
150
|
+
fmt.Fprintf(&body, "INPUTS: %s\nINPUTS_SHA256: %s\nCANDIDATE_BEFORE: %s\nCANDIDATE_AFTER: %s\n",
|
|
151
|
+
transcriptMetadata(valueOr(gate.Inputs, "none")), valueOr(result.Inputs, "none"), transcriptMetadata(before), transcriptMetadata(after))
|
|
152
|
+
output, masked := secretpattern.Redact(result.Outcome.Output)
|
|
153
|
+
truncated := len(output) > maxTranscriptOutput
|
|
154
|
+
if truncated {
|
|
155
|
+
output = output[:maxTranscriptOutput]
|
|
156
|
+
}
|
|
157
|
+
verdict := "fail"
|
|
158
|
+
if result.Passed && !result.Discarded && result.WriteErr == nil {
|
|
159
|
+
verdict = "pass"
|
|
160
|
+
}
|
|
161
|
+
fmt.Fprintf(&body, "ATTEMPT: %d\nSTARTED: %s\nENDED: %s\nEXIT: %s\nEXPECT: %s\nRESULT: %s\n",
|
|
162
|
+
attempt, result.Started.Format(time.RFC3339Nano), result.Ended.Format(time.RFC3339Nano), transcriptMetadata(exitLabel(result.Outcome)), transcriptMetadata(gate.Expect), verdict)
|
|
163
|
+
fmt.Fprintf(&body, "OUTPUT_SHA256: %s\nOUTPUT_BYTES: %d\nREDACTIONS: %d\nTRUNCATED: %t\n--- OUTPUT ---\n%s\n--- END OUTPUT ---\n",
|
|
164
|
+
OutputDigest(result.Outcome.Output), result.Outcome.OutputBytes, masked, truncated, output)
|
|
165
|
+
checksum := OutputDigest(body.String())
|
|
166
|
+
fmt.Fprintf(&body, "TRANSCRIPT_SHA256: %s\n", checksum)
|
|
167
|
+
file := fmt.Sprintf("%s-a%d.log", prefix, attempt)
|
|
168
|
+
return file, fsutil.WriteFileAtomicIn(dir, file, []byte(body.String()), 0o600)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// passingOrigin accepts only an executed, complete pass bound to ledger proof.
|
|
172
|
+
// Candidate labels are historical context, not a dependency of scoped INPUTS.
|
|
173
|
+
func passingOrigin(work string, gate *Gate) string {
|
|
174
|
+
evidence := automaticEvidenceRE.FindStringSubmatch(strings.TrimSpace(gate.Evidence))
|
|
175
|
+
if evidence == nil || evidence[1] != DefinitionDigest(gate) {
|
|
176
|
+
return ""
|
|
177
|
+
}
|
|
178
|
+
dir, err := transcriptDirectory(work, gate.ID, false)
|
|
179
|
+
if err != nil {
|
|
180
|
+
return ""
|
|
181
|
+
}
|
|
182
|
+
defer func() { _ = dir.Close() }()
|
|
183
|
+
listing, err := dir.Open(".")
|
|
184
|
+
if err != nil {
|
|
185
|
+
return ""
|
|
186
|
+
}
|
|
187
|
+
entries, err := listing.ReadDir(-1)
|
|
188
|
+
_ = listing.Close()
|
|
189
|
+
if err != nil {
|
|
190
|
+
return ""
|
|
191
|
+
}
|
|
192
|
+
for _, entry := range entries {
|
|
193
|
+
if !strings.HasSuffix(entry.Name(), ".log") {
|
|
194
|
+
continue
|
|
195
|
+
}
|
|
196
|
+
raw, err := readTranscript(dir, entry.Name())
|
|
197
|
+
if err == nil && validOrigin(string(raw), gate, evidence) {
|
|
198
|
+
return filepath.Join(work, "references", "gates", transcriptGateName(gate.ID), entry.Name())
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
return ""
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
func readTranscript(dir *os.Root, name string) ([]byte, error) {
|
|
205
|
+
info, err := dir.Lstat(name)
|
|
206
|
+
if err != nil {
|
|
207
|
+
return nil, err
|
|
208
|
+
}
|
|
209
|
+
if !info.Mode().IsRegular() || info.Size() > maxTranscriptBytes {
|
|
210
|
+
return nil, fmt.Errorf("unsafe or oversized transcript")
|
|
211
|
+
}
|
|
212
|
+
file, err := dir.Open(name)
|
|
213
|
+
if err != nil {
|
|
214
|
+
return nil, err
|
|
215
|
+
}
|
|
216
|
+
defer func() { _ = file.Close() }()
|
|
217
|
+
opened, err := file.Stat()
|
|
218
|
+
if err != nil {
|
|
219
|
+
return nil, err
|
|
220
|
+
}
|
|
221
|
+
if !os.SameFile(info, opened) {
|
|
222
|
+
return nil, fmt.Errorf("transcript changed while opening")
|
|
223
|
+
}
|
|
224
|
+
raw, err := io.ReadAll(io.LimitReader(file, maxTranscriptBytes+1))
|
|
225
|
+
if err != nil {
|
|
226
|
+
return nil, err
|
|
227
|
+
}
|
|
228
|
+
current, err := dir.Lstat(name)
|
|
229
|
+
if err != nil {
|
|
230
|
+
return nil, err
|
|
231
|
+
}
|
|
232
|
+
if len(raw) > maxTranscriptBytes || !current.Mode().IsRegular() || !os.SameFile(opened, current) || current.Size() != int64(len(raw)) {
|
|
233
|
+
return nil, fmt.Errorf("transcript changed while reading")
|
|
234
|
+
}
|
|
235
|
+
return raw, nil
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
func validOrigin(raw string, gate *Gate, evidence []string) bool {
|
|
239
|
+
const footer = "TRANSCRIPT_SHA256: "
|
|
240
|
+
end := strings.LastIndex(raw, footer)
|
|
241
|
+
if end < 0 || raw[end:] != footer+OutputDigest(raw[:end])+"\n" {
|
|
242
|
+
return false
|
|
243
|
+
}
|
|
244
|
+
header, output, ok := strings.Cut(raw[:end], "\n--- OUTPUT ---\n")
|
|
245
|
+
if !ok || !strings.HasSuffix(output, "\n--- END OUTPUT ---\n") {
|
|
246
|
+
return false
|
|
247
|
+
}
|
|
248
|
+
output = strings.TrimSuffix(output, "\n--- END OUTPUT ---\n")
|
|
249
|
+
lines := strings.Split(header, "\n")
|
|
250
|
+
if lines[0] != "DEVRITES-GATE-TRANSCRIPT v2" {
|
|
251
|
+
return false
|
|
252
|
+
}
|
|
253
|
+
fields := map[string]string{}
|
|
254
|
+
for _, line := range lines[1:] {
|
|
255
|
+
key, value, ok := strings.Cut(line, ": ")
|
|
256
|
+
if _, duplicate := fields[key]; !ok || duplicate {
|
|
257
|
+
return false
|
|
258
|
+
}
|
|
259
|
+
fields[key] = value
|
|
260
|
+
}
|
|
261
|
+
for key, want := range map[string]string{
|
|
262
|
+
"GATE": transcriptMetadata(gate.ID), "TITLE": transcriptMetadata(gate.Title),
|
|
263
|
+
"COMMAND": transcriptMetadata(gate.Check), "CWD": transcriptMetadata(valueOr(gate.Cwd, ".")),
|
|
264
|
+
"EXPECT": transcriptMetadata(gate.Expect), "DEFINITION_SHA256": evidence[1],
|
|
265
|
+
"INPUTS": transcriptMetadata(valueOr(gate.Inputs, "none")), "INPUTS_SHA256": valueOr(evidence[4], "none"),
|
|
266
|
+
"RESULT": "pass", "EXIT": "0", "OUTPUT_SHA256": evidence[2], "OUTPUT_BYTES": evidence[3],
|
|
267
|
+
} {
|
|
268
|
+
if got, exists := fields[key]; !exists || got != want {
|
|
269
|
+
return false
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
started, err := time.Parse(time.RFC3339Nano, fields["STARTED"])
|
|
273
|
+
if err != nil || started.IsZero() {
|
|
274
|
+
return false
|
|
275
|
+
}
|
|
276
|
+
ended, err := time.Parse(time.RFC3339Nano, fields["ENDED"])
|
|
277
|
+
if err != nil || ended.Before(started) {
|
|
278
|
+
return false
|
|
279
|
+
}
|
|
280
|
+
attempt, err := strconv.Atoi(fields["ATTEMPT"])
|
|
281
|
+
if err != nil || attempt < 1 || fields["CANDIDATE_BEFORE"] == "" || fields["CANDIDATE_AFTER"] == "" {
|
|
282
|
+
return false
|
|
283
|
+
}
|
|
284
|
+
redactions, err := strconv.Atoi(fields["REDACTIONS"])
|
|
285
|
+
if err != nil || redactions < 0 {
|
|
286
|
+
return false
|
|
287
|
+
}
|
|
288
|
+
truncated, err := strconv.ParseBool(fields["TRUNCATED"])
|
|
289
|
+
return err == nil && len(output) <= maxTranscriptOutput && (!truncated || len(output) == maxTranscriptOutput)
|
|
290
|
+
}
|
|
291
|
+
|
|
101
292
|
func exitLabel(outcome RunOutcome) string {
|
|
102
293
|
switch {
|
|
103
294
|
case outcome.TimedOut:
|
|
@@ -47,9 +47,13 @@ func WorkspaceOverrideChecked(root, slug string) (string, error) {
|
|
|
47
47
|
if raw == "" {
|
|
48
48
|
return "", nil
|
|
49
49
|
}
|
|
50
|
+
absoluteRoot, err := filepath.Abs(root)
|
|
51
|
+
if err != nil {
|
|
52
|
+
return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DevRites root %q: %w", root, err)
|
|
53
|
+
}
|
|
50
54
|
path := raw
|
|
51
55
|
if !filepath.IsAbs(path) {
|
|
52
|
-
path = filepath.Join(filepath.Dir(
|
|
56
|
+
path = filepath.Join(filepath.Dir(absoluteRoot), path)
|
|
53
57
|
}
|
|
54
58
|
abs, err := filepath.Abs(path)
|
|
55
59
|
if err != nil {
|
|
@@ -59,10 +63,10 @@ func WorkspaceOverrideChecked(root, slug string) (string, error) {
|
|
|
59
63
|
if err != nil {
|
|
60
64
|
return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DEVRITES_WORKSPACE %q: %w", raw, err)
|
|
61
65
|
}
|
|
62
|
-
if !safepath.WithinResolved(path,
|
|
66
|
+
if !safepath.WithinResolved(path, absoluteRoot) {
|
|
63
67
|
return "", fmt.Errorf("DRV-WORKSPACE-OUTSIDE-ROOT: DEVRITES_WORKSPACE %q is outside %q; run `unset DEVRITES_WORKSPACE`", raw, root)
|
|
64
68
|
}
|
|
65
|
-
resolvedRoot, err := safepath.ResolveExisting(
|
|
69
|
+
resolvedRoot, err := safepath.ResolveExisting(absoluteRoot)
|
|
66
70
|
if err != nil {
|
|
67
71
|
return "", fmt.Errorf("DRV-WORKSPACE-INVALID: resolve DevRites root %q: %w", root, err)
|
|
68
72
|
}
|
|
@@ -114,6 +118,15 @@ func ExistingFeatureDirChecked(root, slug string) (string, error) {
|
|
|
114
118
|
}
|
|
115
119
|
return ws, nil
|
|
116
120
|
}
|
|
121
|
+
return ExistingCanonicalFeatureDirChecked(root, slug)
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// ExistingCanonicalFeatureDirChecked resolves an explicit producer workspace
|
|
125
|
+
// inside root without redirecting it through a consumer's workspace override.
|
|
126
|
+
func ExistingCanonicalFeatureDirChecked(root, slug string) (string, error) {
|
|
127
|
+
if !ValidSlug(slug) {
|
|
128
|
+
return "", fmt.Errorf("DRV-WORKSPACE-INVALID: %q is not a feature slug", slug)
|
|
129
|
+
}
|
|
117
130
|
candidate := filepath.Join(root, "work", slug)
|
|
118
131
|
resolved, err := checkedCanonicalDir(root, candidate, "work", slug)
|
|
119
132
|
if err == nil {
|
|
@@ -51,6 +51,12 @@ func Check(kind Kind, root, slug string) (*Result, error) {
|
|
|
51
51
|
if err != nil {
|
|
52
52
|
return nil, fmt.Errorf("gate %s: %w", kind, err)
|
|
53
53
|
}
|
|
54
|
+
return CheckObserved(kind, root, observation)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// CheckObserved checks retained facts acquired from root, so a caller can use
|
|
58
|
+
// those same vetted bytes for subsequent admission without rereading the workspace.
|
|
59
|
+
func CheckObserved(kind Kind, root string, observation *state.WorkspaceObservation) (*Result, error) {
|
|
54
60
|
result, err := checkObservation(kind, observation)
|
|
55
61
|
if err != nil {
|
|
56
62
|
return nil, fmt.Errorf("gate %s: %w", kind, err)
|
|
@@ -38,6 +38,7 @@ type candidateRow struct {
|
|
|
38
38
|
executable bool
|
|
39
39
|
size int64
|
|
40
40
|
info os.FileInfo
|
|
41
|
+
fileDigest string
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
type candidateIdentity struct {
|
|
@@ -72,7 +73,7 @@ func loadCandidateIdentity(root, slug string) (candidateIdentity, error) {
|
|
|
72
73
|
if err := validateCandidateRows(project, rows); err != nil {
|
|
73
74
|
return candidateIdentity{}, err
|
|
74
75
|
}
|
|
75
|
-
digest, err := hashCandidate(project, rows)
|
|
76
|
+
digest, err := hashCandidate(project, rows, false)
|
|
76
77
|
if err != nil {
|
|
77
78
|
return candidateIdentity{}, err
|
|
78
79
|
}
|
|
@@ -407,15 +408,18 @@ func candidatePathInfo(project, name string) (os.FileInfo, bool, error) {
|
|
|
407
408
|
return nil, false, errors.New("path is empty")
|
|
408
409
|
}
|
|
409
410
|
|
|
410
|
-
func hashCandidate(project string, rows []candidateRow) (string, error) {
|
|
411
|
+
func hashCandidate(project string, rows []candidateRow, perFile bool) (string, error) {
|
|
411
412
|
digest := sha256.New()
|
|
412
413
|
writeCandidateField(digest, candidateDigestDomain)
|
|
413
414
|
writeCandidateField(digest, candidateDigestVersion)
|
|
414
415
|
writeCandidateField(digest, fmt.Sprintf("%d", len(rows)))
|
|
415
|
-
for
|
|
416
|
+
for i, row := range rows {
|
|
416
417
|
writeCandidateField(digest, row.state)
|
|
417
418
|
writeCandidateField(digest, row.path)
|
|
418
419
|
if row.state == "deleted" {
|
|
420
|
+
if err := validateCandidateRow(project, &row); err != nil {
|
|
421
|
+
return "", fmt.Errorf("candidate %q: %w", row.path, err)
|
|
422
|
+
}
|
|
419
423
|
writeCandidateField(digest, "absent")
|
|
420
424
|
writeCandidateField(digest, "0")
|
|
421
425
|
writeCandidateLength(digest, 0)
|
|
@@ -428,9 +432,18 @@ func hashCandidate(project string, rows []candidateRow) (string, error) {
|
|
|
428
432
|
writeCandidateField(digest, "0")
|
|
429
433
|
}
|
|
430
434
|
writeCandidateLength(digest, uint64(row.size)) // #nosec G115 -- validateCandidateRow bounds size to 0..64 MiB
|
|
431
|
-
|
|
435
|
+
var writer io.Writer = digest
|
|
436
|
+
var fileDigest hash.Hash
|
|
437
|
+
if perFile {
|
|
438
|
+
fileDigest = sha256.New()
|
|
439
|
+
writer = io.MultiWriter(digest, fileDigest)
|
|
440
|
+
}
|
|
441
|
+
if err := hashCandidateFile(writer, project, row); err != nil {
|
|
432
442
|
return "", fmt.Errorf("candidate %q: %w", row.path, err)
|
|
433
443
|
}
|
|
444
|
+
if perFile {
|
|
445
|
+
rows[i].fileDigest = hex.EncodeToString(fileDigest.Sum(nil))
|
|
446
|
+
}
|
|
434
447
|
}
|
|
435
448
|
return hex.EncodeToString(digest.Sum(nil)), nil
|
|
436
449
|
}
|
|
@@ -446,7 +459,7 @@ func writeCandidateLength(writer hash.Hash, size uint64) {
|
|
|
446
459
|
_, _ = writer.Write(encoded[:])
|
|
447
460
|
}
|
|
448
461
|
|
|
449
|
-
func hashCandidateFile(digest
|
|
462
|
+
func hashCandidateFile(digest io.Writer, project string, row candidateRow) error {
|
|
450
463
|
if _, exists, err := candidatePathInfo(project, row.path); err != nil || !exists {
|
|
451
464
|
if err != nil {
|
|
452
465
|
return err
|
|
@@ -464,7 +477,7 @@ func hashCandidateFile(digest hash.Hash, project string, row candidateRow) error
|
|
|
464
477
|
if err != nil {
|
|
465
478
|
return err
|
|
466
479
|
}
|
|
467
|
-
if !opened.Mode().IsRegular() || opened.Size() != row.size || (opened.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(row.info, opened) {
|
|
480
|
+
if !opened.Mode().IsRegular() || opened.Size() != row.size || (opened.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(row.info, opened) || !row.info.ModTime().Equal(opened.ModTime()) {
|
|
468
481
|
return errors.New("file changed size or type before reading")
|
|
469
482
|
}
|
|
470
483
|
written, err := io.CopyN(digest, file, row.size)
|
|
@@ -483,7 +496,7 @@ func hashCandidateFile(digest hash.Hash, project string, row candidateRow) error
|
|
|
483
496
|
if err != nil {
|
|
484
497
|
return err
|
|
485
498
|
}
|
|
486
|
-
if !exists || !after.Mode().IsRegular() || after.Size() != row.size || (after.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(opened, after) || !os.SameFile(opened, current) {
|
|
499
|
+
if !exists || !after.Mode().IsRegular() || after.Size() != row.size || current.Size() != row.size || (after.Mode().Perm()&0o111 != 0) != row.executable || (current.Mode().Perm()&0o111 != 0) != row.executable || !os.SameFile(opened, after) || !os.SameFile(opened, current) || !opened.ModTime().Equal(after.ModTime()) || !opened.ModTime().Equal(current.ModTime()) {
|
|
487
500
|
return errors.New("file changed size or type while reading")
|
|
488
501
|
}
|
|
489
502
|
return nil
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
package lib
|
|
2
2
|
|
|
3
3
|
import (
|
|
4
|
-
"errors"
|
|
5
4
|
"fmt"
|
|
6
|
-
"os"
|
|
7
5
|
"path/filepath"
|
|
8
6
|
"regexp"
|
|
9
7
|
"sort"
|
|
@@ -118,7 +116,7 @@ func omissionError(omitted []string) error {
|
|
|
118
116
|
len(omitted), strings.Join(shown, ", "), more)
|
|
119
117
|
}
|
|
120
118
|
|
|
121
|
-
var diffPathRE = regexp.MustCompile(`[A-Za-z0-9_./@+-]+\.(?:diff|patch)\b`)
|
|
119
|
+
var diffPathRE = regexp.MustCompile(`(?:[A-Za-z]:/)?[A-Za-z0-9_./@+-]+\.(?:diff|patch)\b`)
|
|
122
120
|
|
|
123
121
|
// lintReviewCandidate gates a Review or Seal reviewer wave: the candidate
|
|
124
122
|
// manifest must cover every path the feature's checkpoints changed, and the
|
|
@@ -128,20 +126,5 @@ func lintReviewCandidate(root, slug, packet string) error {
|
|
|
128
126
|
if err := CheckCandidateCoverage(root, slug); err != nil {
|
|
129
127
|
return err
|
|
130
128
|
}
|
|
131
|
-
|
|
132
|
-
if err != nil {
|
|
133
|
-
return fmt.Errorf("reviewer packet %s: %w", packet, err)
|
|
134
|
-
}
|
|
135
|
-
for _, name := range diffPathRE.FindAllString(string(raw), -1) {
|
|
136
|
-
for _, dir := range []string{projectDir(root), featureDir(root, slug), ""} {
|
|
137
|
-
candidate := name
|
|
138
|
-
if dir != "" {
|
|
139
|
-
candidate = filepath.Join(dir, filepath.FromSlash(name))
|
|
140
|
-
}
|
|
141
|
-
if info, err := os.Stat(candidate); err == nil && info.Mode().IsRegular() && info.Size() > 0 {
|
|
142
|
-
return nil
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
return errors.New("a Review or Seal reviewer packet must name the frozen candidate diff (an existing non-empty .diff or .patch file); reviewers cannot judge a digest alone")
|
|
129
|
+
return lintPacketDiffs(root, slug, packet, true)
|
|
147
130
|
}
|
|
@@ -10,6 +10,7 @@ import (
|
|
|
10
10
|
"runtime"
|
|
11
11
|
|
|
12
12
|
"github.com/devrites/devrites/internal/devritespaths"
|
|
13
|
+
"github.com/devrites/devrites/internal/parallel"
|
|
13
14
|
"github.com/devrites/devrites/internal/state"
|
|
14
15
|
)
|
|
15
16
|
|
|
@@ -78,11 +79,22 @@ func CloseOut(root string, args []string, stdout, stderr io.Writer) int {
|
|
|
78
79
|
// re-create work/<slug>. Windows cannot rename a directory that holds an
|
|
79
80
|
// open file, so there the lock is released just before the rename.
|
|
80
81
|
code := 1
|
|
81
|
-
if err :=
|
|
82
|
-
|
|
83
|
-
|
|
82
|
+
if err := parallel.WithControlLock(dv, func() error {
|
|
83
|
+
leased, err := parallel.WorkspaceLeased(dv, slug)
|
|
84
|
+
if err != nil {
|
|
85
|
+
return err
|
|
86
|
+
}
|
|
87
|
+
if leased {
|
|
88
|
+
fmt.Fprintf(stderr, "state close: BLOCKED: %s is named by a retained parallel lease; finish and clean up the controller lease before archiving\n", slug)
|
|
89
|
+
code = 3
|
|
90
|
+
return nil
|
|
91
|
+
}
|
|
92
|
+
return state.WithFeatureLockRelease(dv, slug, func(release func()) error {
|
|
93
|
+
code = closeOutLocked(release, work, arch, archiveDir, archDisplay, active, slug, stdout, stderr)
|
|
94
|
+
return nil
|
|
95
|
+
})
|
|
84
96
|
}); err != nil {
|
|
85
|
-
fmt.Fprintf(stderr, "state close:
|
|
97
|
+
fmt.Fprintf(stderr, "state close: %v\n", err)
|
|
86
98
|
return 1
|
|
87
99
|
}
|
|
88
100
|
return code
|