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
|
@@ -28,10 +28,11 @@ type GateResult struct {
|
|
|
28
28
|
Ran bool
|
|
29
29
|
Passed bool
|
|
30
30
|
Detail string // exit/match/timeout/approval diagnostic
|
|
31
|
-
Discarded bool // result dropped:
|
|
31
|
+
Discarded bool // result dropped: bound oracle or inputs changed during the run
|
|
32
32
|
WriteErr error // evidence writeback failed for a reason other than drift
|
|
33
|
-
Reused bool // met gate kept
|
|
33
|
+
Reused bool // met gate kept under the selected reuse policy
|
|
34
34
|
Inputs string // INPUTS content digest at run time, when declared
|
|
35
|
+
Origin string // complete executed transcript retained when proof is reused
|
|
35
36
|
// Outcome, Started and Ended describe the execution this result judged;
|
|
36
37
|
// they are zero when the gate did not execute.
|
|
37
38
|
Outcome RunOutcome
|
|
@@ -63,27 +64,52 @@ func ReadLedgerFile(ledgerPath string) (string, error) {
|
|
|
63
64
|
return string(raw), nil
|
|
64
65
|
}
|
|
65
66
|
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
//
|
|
67
|
+
// RunOptions controls evidence reuse and short ledger writeback locking.
|
|
68
|
+
// TranscriptWork requires a usable executed origin before a met gate can skip.
|
|
69
|
+
// The caller holds the gates execution lock through transcript publication.
|
|
70
|
+
type RunOptions struct {
|
|
71
|
+
WriteLock func(func() error) error
|
|
72
|
+
TranscriptWork string
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// RunGates executes the runnable gates of ledgerPath. reverify also runs met
|
|
76
|
+
// gates without INPUTS; met gates with unchanged INPUTS reuse their proof in
|
|
77
|
+
// either mode, subject to options.TranscriptWork's executed-origin requirement.
|
|
78
|
+
// approved is the vetted command set from test-plan.md; a
|
|
69
79
|
// CHECK outside it never executes, and neither does one outside a surface that
|
|
70
80
|
// could not be determined — an absent preflight is a refusal, not permission.
|
|
71
81
|
// Each pass writes definition-bound evidence; each failure clears the box and
|
|
72
82
|
// restores EVIDENCE: pending.
|
|
73
83
|
//
|
|
74
84
|
// Gates sharing one CHECK and CWD execute once per call; each matches its own
|
|
75
|
-
// EXPECT against that shared output.
|
|
85
|
+
// EXPECT against that shared output. options.WriteLock, when non-nil, wraps every
|
|
76
86
|
// ledger writeback so the caller need not hold the feature lock while checks
|
|
77
87
|
// execute; writeResult re-verifies the gate's oracle before it writes.
|
|
78
|
-
func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, approved []ApprovedCommand, reverify bool, timeout time.Duration,
|
|
88
|
+
func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, approved []ApprovedCommand, reverify bool, timeout time.Duration, options *RunOptions) []GateResult {
|
|
89
|
+
var writeLock func(func() error) error
|
|
90
|
+
transcriptWork := ""
|
|
91
|
+
if options != nil {
|
|
92
|
+
writeLock, transcriptWork = options.WriteLock, options.TranscriptWork
|
|
93
|
+
}
|
|
79
94
|
var results []GateResult
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
95
|
+
executions := map[string]struct {
|
|
96
|
+
ran bool
|
|
97
|
+
outcome RunOutcome
|
|
98
|
+
span [2]time.Time
|
|
99
|
+
inputs map[string]string
|
|
100
|
+
}{}
|
|
101
|
+
// Collect every dependent INPUTS spec before any shared command can run.
|
|
102
|
+
for _, gate := range doc.Gates {
|
|
103
|
+
if gate.Check == "" || gate.Inputs == "" {
|
|
104
|
+
continue
|
|
85
105
|
}
|
|
86
|
-
|
|
106
|
+
key := gate.Check + "\x00" + filepath.Join(root, filepath.FromSlash(gate.Cwd))
|
|
107
|
+
execution := executions[key]
|
|
108
|
+
if execution.inputs == nil {
|
|
109
|
+
execution.inputs = map[string]string{}
|
|
110
|
+
}
|
|
111
|
+
execution.inputs[gate.Inputs] = ""
|
|
112
|
+
executions[key] = execution
|
|
87
113
|
}
|
|
88
114
|
for _, gate := range doc.Gates {
|
|
89
115
|
if _, abandoned := doc.Abandoned[gate.ID]; abandoned {
|
|
@@ -94,24 +120,35 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
|
|
|
94
120
|
results = append(results, GateResult{ID: gate.ID, Detail: "manual gate; evidence is judged, not executed"})
|
|
95
121
|
continue
|
|
96
122
|
}
|
|
97
|
-
|
|
98
|
-
if
|
|
99
|
-
|
|
100
|
-
|
|
123
|
+
isApproved := Approved(gate.Check, gate.Cwd, approved)
|
|
124
|
+
if transcriptWork != "" && !isApproved {
|
|
125
|
+
results = append(results, GateResult{ID: gate.ID, Detail: "CHECK/CWD is not an approved test-plan.md preflight row; not executed"})
|
|
126
|
+
continue
|
|
101
127
|
}
|
|
128
|
+
inputs := ""
|
|
102
129
|
if GateState(gate, doc) == StateMet {
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
130
|
+
canReuse := gate.Inputs == "" && !reverify
|
|
131
|
+
if gate.Inputs != "" {
|
|
132
|
+
// An unreadable or unmatched spec never matches recorded evidence.
|
|
133
|
+
inputs, _ = InputsDigest(root, gate.Inputs)
|
|
134
|
+
canReuse = inputs != "" && inputs == recordedInputs(gate)
|
|
106
135
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
136
|
+
if canReuse {
|
|
137
|
+
origin := ""
|
|
138
|
+
if transcriptWork != "" {
|
|
139
|
+
origin = passingOrigin(transcriptWork, gate)
|
|
140
|
+
}
|
|
141
|
+
if transcriptWork == "" || origin != "" {
|
|
142
|
+
detail := "already met"
|
|
143
|
+
if gate.Inputs != "" {
|
|
144
|
+
detail = "reused: inputs unchanged"
|
|
145
|
+
}
|
|
146
|
+
results = append(results, GateResult{ID: gate.ID, Passed: true, Reused: gate.Inputs != "" || origin != "", Inputs: inputs, Origin: origin, Detail: detail})
|
|
147
|
+
continue
|
|
148
|
+
}
|
|
112
149
|
}
|
|
113
150
|
}
|
|
114
|
-
if !
|
|
151
|
+
if !isApproved {
|
|
115
152
|
results = append(results, GateResult{ID: gate.ID, Detail: "CHECK/CWD is not an approved test-plan.md preflight row; not executed"})
|
|
116
153
|
continue
|
|
117
154
|
}
|
|
@@ -120,36 +157,84 @@ func RunGates(ctx context.Context, root, ledgerPath string, doc *Document, appro
|
|
|
120
157
|
results = append(results, GateResult{ID: gate.ID, Detail: err.Error()})
|
|
121
158
|
continue
|
|
122
159
|
}
|
|
123
|
-
cwd := root
|
|
124
|
-
if gate.Cwd != "" {
|
|
125
|
-
cwd = filepath.Join(root, filepath.FromSlash(gate.Cwd))
|
|
126
|
-
}
|
|
160
|
+
cwd := filepath.Join(root, filepath.FromSlash(gate.Cwd))
|
|
127
161
|
key := gate.Check + "\x00" + cwd
|
|
128
|
-
|
|
129
|
-
if !ran {
|
|
162
|
+
execution := executions[key]
|
|
163
|
+
if !execution.ran {
|
|
164
|
+
for spec := range execution.inputs {
|
|
165
|
+
execution.inputs[spec], _ = InputsDigest(root, spec)
|
|
166
|
+
}
|
|
130
167
|
started := time.Now().UTC()
|
|
131
|
-
outcome = Execute(ctx, gate.Check, cwd, timeout)
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
168
|
+
execution.outcome = Execute(ctx, gate.Check, cwd, timeout)
|
|
169
|
+
execution.span = [2]time.Time{started, time.Now().UTC()}
|
|
170
|
+
for spec, before := range execution.inputs {
|
|
171
|
+
after, _ := InputsDigest(root, spec)
|
|
172
|
+
if after != before {
|
|
173
|
+
execution.inputs[spec] = ""
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
execution.ran = true
|
|
177
|
+
executions[key] = execution
|
|
178
|
+
}
|
|
179
|
+
outcome := execution.outcome
|
|
180
|
+
inputs = execution.inputs[gate.Inputs]
|
|
181
|
+
passed := outcome.Err == nil && outcome.ExitCode == 0 && expectation.Matches(outcome.Output)
|
|
182
|
+
detail := "pass"
|
|
183
|
+
if !passed {
|
|
184
|
+
detail = failDetail(outcome, expectation)
|
|
185
|
+
}
|
|
186
|
+
write := func() error {
|
|
187
|
+
if passed && gate.Inputs != "" {
|
|
188
|
+
// Earlier writebacks may have moved files since execution.
|
|
189
|
+
current, _ := InputsDigest(root, gate.Inputs)
|
|
190
|
+
if inputs == "" || current != inputs {
|
|
191
|
+
passed = false
|
|
192
|
+
detail = "INPUTS changed or could not be read during the run"
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
evidence := "pending"
|
|
196
|
+
if passed {
|
|
197
|
+
evidence = withInputs(FormatEvidence(gate, OutputDigest(outcome.Output), outcome.OutputBytes, time.Now().UTC().Format(time.RFC3339)), inputs)
|
|
198
|
+
}
|
|
199
|
+
return writeResult(ledgerPath, gate, evidence, passed)
|
|
200
|
+
}
|
|
201
|
+
var writeErr error
|
|
202
|
+
if writeLock == nil {
|
|
203
|
+
writeErr = write()
|
|
145
204
|
} else {
|
|
146
|
-
writeErr
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
205
|
+
writeErr = writeLock(write)
|
|
206
|
+
}
|
|
207
|
+
results = append(results, GateResult{
|
|
208
|
+
ID: gate.ID, Ran: true, Passed: passed,
|
|
209
|
+
Discarded: errors.Is(writeErr, ErrOracleMoved), WriteErr: writeErr,
|
|
210
|
+
Detail: resultDetail(writeErr, detail),
|
|
211
|
+
Inputs: inputs, Outcome: outcome, Started: execution.span[0], Ended: execution.span[1],
|
|
212
|
+
})
|
|
213
|
+
}
|
|
214
|
+
// Later checks may invalidate an earlier execution or reused pass.
|
|
215
|
+
for i, gate := range doc.Gates {
|
|
216
|
+
result := &results[i]
|
|
217
|
+
if !result.Passed || result.Discarded || result.WriteErr != nil || gate.Inputs == "" {
|
|
218
|
+
continue
|
|
219
|
+
}
|
|
220
|
+
validate := func() error {
|
|
221
|
+
current, _ := InputsDigest(root, gate.Inputs)
|
|
222
|
+
if current != "" && current == result.Inputs {
|
|
223
|
+
return nil
|
|
224
|
+
}
|
|
225
|
+
result.Passed, result.Reused, result.Discarded = false, false, true
|
|
226
|
+
result.Detail = "INPUTS changed or could not be read after a later check"
|
|
227
|
+
return writeResult(ledgerPath, gate, "pending", false)
|
|
228
|
+
}
|
|
229
|
+
var err error
|
|
230
|
+
if writeLock == nil {
|
|
231
|
+
err = validate()
|
|
232
|
+
} else {
|
|
233
|
+
err = writeLock(validate)
|
|
234
|
+
}
|
|
235
|
+
if err != nil {
|
|
236
|
+
result.WriteErr = err
|
|
237
|
+
result.Detail = resultDetail(err, result.Detail)
|
|
153
238
|
}
|
|
154
239
|
}
|
|
155
240
|
return results
|
|
@@ -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 {
|
|
@@ -194,14 +207,19 @@ func checkedCanonicalDir(root, candidate, parent, slug string) (string, error) {
|
|
|
194
207
|
return filepath.Clean(candidate), nil
|
|
195
208
|
}
|
|
196
209
|
|
|
197
|
-
// ActiveSlug
|
|
198
|
-
// is not an error; it yields the empty slug.
|
|
210
|
+
// ActiveSlug resolves the workspace override, or reads the active-feature pointer.
|
|
211
|
+
// A missing pointer file is not an error; it yields the empty slug.
|
|
199
212
|
func ActiveSlug(root string) (string, error) {
|
|
200
213
|
if ws, err := WorkspaceOverrideChecked(root, ""); err != nil {
|
|
201
214
|
return "", err
|
|
202
215
|
} else if ws != "" {
|
|
203
216
|
return filepath.Base(ws), nil
|
|
204
217
|
}
|
|
218
|
+
return PhysicalActiveSlug(root)
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// PhysicalActiveSlug reads the validated ACTIVE pointer without workspace overrides.
|
|
222
|
+
func PhysicalActiveSlug(root string) (string, error) {
|
|
205
223
|
activePath := filepath.Join(root, "ACTIVE")
|
|
206
224
|
if info, err := os.Lstat(activePath); err == nil && info.Mode()&os.ModeSymlink != 0 {
|
|
207
225
|
return "", fmt.Errorf("DRV-ACTIVE-SYMLINK: ACTIVE is a symlink; run `rm -f %q`", activePath)
|
|
@@ -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)
|
|
@@ -231,6 +231,11 @@ func (r *runner) writeConfined(dest string, data []byte, perm fs.FileMode) error
|
|
|
231
231
|
return err
|
|
232
232
|
}
|
|
233
233
|
defer func() { _ = root.Close() }()
|
|
234
|
+
if info, err := root.Stat(rel); err == nil {
|
|
235
|
+
perm = info.Mode().Perm()
|
|
236
|
+
} else if !os.IsNotExist(err) {
|
|
237
|
+
return err
|
|
238
|
+
}
|
|
234
239
|
return fsutil.WriteFileAtomicIn(root, rel, data, perm)
|
|
235
240
|
}
|
|
236
241
|
|