dsh-compound-engineering 0.0.0-stage → 0.1.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/LICENSE +21 -0
- package/README.md +160 -2
- package/README.zh.md +161 -0
- package/cordis.patch.yml +6 -0
- package/lib/frontmatter.js +167 -0
- package/lib/index.js +46 -0
- package/lib/provider.js +201 -0
- package/package.json +62 -4
- package/skills/CODING_STANDARDS.md +68 -0
- package/skills/ce-babysit-pr/SKILL.md +59 -0
- package/skills/ce-babysit-pr/references/branch-currency.md +22 -0
- package/skills/ce-babysit-pr/references/envelope.md +44 -0
- package/skills/ce-babysit-pr/references/pipeline.md +15 -0
- package/skills/ce-babysit-pr/references/report.md +20 -0
- package/skills/ce-babysit-pr/references/settle.md +37 -0
- package/skills/ce-babysit-pr/references/setup.md +33 -0
- package/skills/ce-babysit-pr/references/stack-commands.md +39 -0
- package/skills/ce-babysit-pr/references/stack.md +43 -0
- package/skills/ce-babysit-pr/references/tick.md +102 -0
- package/skills/ce-babysit-pr/references/watch-loop.md +222 -0
- package/skills/ce-babysit-pr/scripts/pr-snapshot +3656 -0
- package/skills/ce-bakeoff/SKILL.md +48 -0
- package/skills/ce-bakeoff/references/candidates.md +37 -0
- package/skills/ce-bakeoff/references/judging.md +19 -0
- package/skills/ce-bakeoff/references/output.md +11 -0
- package/skills/ce-bakeoff/references/verification.md +15 -0
- package/skills/ce-brainstorm/SKILL.md +69 -0
- package/skills/ce-brainstorm/references/agents/slack-researcher.md +127 -0
- package/skills/ce-brainstorm/references/approaches.md +57 -0
- package/skills/ce-brainstorm/references/bakeoff.md +9 -0
- package/skills/ce-brainstorm/references/blindspot-pass.md +70 -0
- package/skills/ce-brainstorm/references/brainstorm-sections.md +458 -0
- package/skills/ce-brainstorm/references/dialogue.md +89 -0
- package/skills/ce-brainstorm/references/handoff.md +187 -0
- package/skills/ce-brainstorm/references/html-rendering.md +638 -0
- package/skills/ce-brainstorm/references/interaction-rules.md +33 -0
- package/skills/ce-brainstorm/references/markdown-rendering.md +242 -0
- package/skills/ce-brainstorm/references/model-tiers.md +13 -0
- package/skills/ce-brainstorm/references/output-mode.md +26 -0
- package/skills/ce-brainstorm/references/phase-0.md +104 -0
- package/skills/ce-brainstorm/references/plan-write.md +32 -0
- package/skills/ce-brainstorm/references/product-pressure-test.md +42 -0
- package/skills/ce-brainstorm/references/reasoning-elevation.md +124 -0
- package/skills/ce-brainstorm/references/settled-decisions.md +45 -0
- package/skills/ce-brainstorm/references/synthesis-summary.md +149 -0
- package/skills/ce-brainstorm/references/universal-brainstorming.md +72 -0
- package/skills/ce-brainstorm/references/verdict-routing.md +26 -0
- package/skills/ce-brainstorm/references/visual-probes.md +164 -0
- package/skills/ce-brainstorm/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-brainstorm/scripts/light-webserver.js +1275 -0
- package/skills/ce-brainstorm/scripts/packs-resolve.py +737 -0
- package/skills/ce-brainstorm/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/SKILL.md +40 -0
- package/skills/ce-code-review/references/action-class-rubric.md +67 -0
- package/skills/ce-code-review/references/cross-model-recovery.md +38 -0
- package/skills/ce-code-review/references/cross-model-review.md +191 -0
- package/skills/ce-code-review/references/depth-paths.md +32 -0
- package/skills/ce-code-review/references/diff-scope.md +59 -0
- package/skills/ce-code-review/references/dispatch-reviewers.md +135 -0
- package/skills/ce-code-review/references/findings-schema.json +125 -0
- package/skills/ce-code-review/references/finish-input.md +118 -0
- package/skills/ce-code-review/references/finish-review.md +202 -0
- package/skills/ce-code-review/references/intent-and-plan.md +73 -0
- package/skills/ce-code-review/references/modes-and-output.md +156 -0
- package/skills/ce-code-review/references/persona-catalog.md +66 -0
- package/skills/ce-code-review/references/personas/adversarial-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/agent-native-reviewer.md +173 -0
- package/skills/ce-code-review/references/personas/api-contract-reviewer.md +44 -0
- package/skills/ce-code-review/references/personas/correctness-reviewer.md +48 -0
- package/skills/ce-code-review/references/personas/data-migration-reviewer.md +111 -0
- package/skills/ce-code-review/references/personas/deployment-verification-agent.md +157 -0
- package/skills/ce-code-review/references/personas/julik-frontend-races-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/learnings-researcher.md +260 -0
- package/skills/ce-code-review/references/personas/maintainability-reviewer.md +85 -0
- package/skills/ce-code-review/references/personas/performance-reviewer.md +45 -0
- package/skills/ce-code-review/references/personas/previous-comments-reviewer.md +59 -0
- package/skills/ce-code-review/references/personas/project-standards-reviewer.md +77 -0
- package/skills/ce-code-review/references/personas/reliability-reviewer.md +49 -0
- package/skills/ce-code-review/references/personas/security-reviewer.md +50 -0
- package/skills/ce-code-review/references/personas/swift-ios-reviewer.md +99 -0
- package/skills/ce-code-review/references/personas/testing-reviewer.md +51 -0
- package/skills/ce-code-review/references/review-output-template.md +172 -0
- package/skills/ce-code-review/references/scope.md +176 -0
- package/skills/ce-code-review/references/select-and-route.md +93 -0
- package/skills/ce-code-review/references/subagent-template.md +206 -0
- package/skills/ce-code-review/references/validator-batch-template.md +67 -0
- package/skills/ce-code-review/scripts/cross-model-adversarial-review.sh +1275 -0
- package/skills/ce-code-review/scripts/findings-mechanics.py +274 -0
- package/skills/ce-code-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-code-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-code-review/scripts/review-scope.py +419 -0
- package/skills/ce-code-review/scripts/run-log.py +280 -0
- package/skills/ce-commit/SKILL.md +60 -0
- package/skills/ce-commit-push-pr/SKILL.md +69 -0
- package/skills/ce-commit-push-pr/references/apply-and-handoff.md +79 -0
- package/skills/ce-commit-push-pr/references/branch-creation.md +47 -0
- package/skills/ce-commit-push-pr/references/commit-and-push.md +26 -0
- package/skills/ce-commit-push-pr/references/compose.md +29 -0
- package/skills/ce-commit-push-pr/references/context.md +42 -0
- package/skills/ce-commit-push-pr/references/gh-stack-cli.md +83 -0
- package/skills/ce-commit-push-pr/references/pr-description-writing.md +208 -0
- package/skills/ce-commit-push-pr/references/stack-submit.md +81 -0
- package/skills/ce-compound/SKILL.md +79 -0
- package/skills/ce-compound/assets/resolution-template.md +96 -0
- package/skills/ce-compound/references/agents/best-practices-researcher.md +115 -0
- package/skills/ce-compound/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-compound/references/agents/framework-docs-researcher.md +93 -0
- package/skills/ce-compound/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-compound/references/agents/performance-oracle.md +108 -0
- package/skills/ce-compound/references/agents/security-sentinel.md +91 -0
- package/skills/ce-compound/references/agents/session-historian.md +82 -0
- package/skills/ce-compound/references/assembly.md +127 -0
- package/skills/ce-compound/references/concepts-vocabulary.md +98 -0
- package/skills/ce-compound/references/enhancement.md +42 -0
- package/skills/ce-compound/references/grounding-validation.md +89 -0
- package/skills/ce-compound/references/lightweight.md +56 -0
- package/skills/ce-compound/references/modes.md +13 -0
- package/skills/ce-compound/references/refresh-and-discoverability.md +96 -0
- package/skills/ce-compound/references/report.md +115 -0
- package/skills/ce-compound/references/research.md +144 -0
- package/skills/ce-compound/references/schema.yaml +236 -0
- package/skills/ce-compound/references/session-history.md +73 -0
- package/skills/ce-compound/references/yaml-schema.md +130 -0
- package/skills/ce-compound/scripts/packs-resolve.py +737 -0
- package/skills/ce-compound/scripts/session-history/__pycache__/dsh_session.cpython-314.pyc +0 -0
- package/skills/ce-compound/scripts/session-history/discover-sessions.sh +290 -0
- package/skills/ce-compound/scripts/session-history/discover_dsh.py +123 -0
- package/skills/ce-compound/scripts/session-history/dsh_session.py +225 -0
- package/skills/ce-compound/scripts/session-history/extract-errors.py +178 -0
- package/skills/ce-compound/scripts/session-history/extract-metadata.py +301 -0
- package/skills/ce-compound/scripts/session-history/extract-skeleton.py +346 -0
- package/skills/ce-compound/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-compound-refresh/SKILL.md +88 -0
- package/skills/ce-compound-refresh/assets/resolution-template.md +96 -0
- package/skills/ce-compound-refresh/references/classify.md +42 -0
- package/skills/ce-compound-refresh/references/commit.md +7 -0
- package/skills/ce-compound-refresh/references/concepts-vocabulary.md +116 -0
- package/skills/ce-compound-refresh/references/discoverability.md +15 -0
- package/skills/ce-compound-refresh/references/investigate.md +23 -0
- package/skills/ce-compound-refresh/references/modes.md +27 -0
- package/skills/ce-compound-refresh/references/per-action-flows.md +142 -0
- package/skills/ce-compound-refresh/references/report.md +23 -0
- package/skills/ce-compound-refresh/references/schema.yaml +236 -0
- package/skills/ce-compound-refresh/references/scope.md +18 -0
- package/skills/ce-compound-refresh/references/worth-audit.md +35 -0
- package/skills/ce-compound-refresh/references/yaml-schema.md +130 -0
- package/skills/ce-compound-refresh/scripts/validate-doc-claims.py +497 -0
- package/skills/ce-compound-refresh/scripts/validate-frontmatter.py +137 -0
- package/skills/ce-debug/SKILL.md +122 -0
- package/skills/ce-debug/references/anti-patterns.md +91 -0
- package/skills/ce-debug/references/defense-in-depth.md +39 -0
- package/skills/ce-debug/references/fix.md +21 -0
- package/skills/ce-debug/references/investigate.md +114 -0
- package/skills/ce-debug/references/investigation-techniques.md +384 -0
- package/skills/ce-debug/references/pipeline-mode.md +78 -0
- package/skills/ce-debug/references/post-fix-handoff.md +60 -0
- package/skills/ce-debug/references/return-to-caller.md +57 -0
- package/skills/ce-doc-review/SKILL.md +75 -0
- package/skills/ce-doc-review/references/bulk-preview.md +146 -0
- package/skills/ce-doc-review/references/cross-model-review.md +207 -0
- package/skills/ce-doc-review/references/decision-primer.md +47 -0
- package/skills/ce-doc-review/references/dispatch.md +56 -0
- package/skills/ce-doc-review/references/document-intake.md +44 -0
- package/skills/ce-doc-review/references/findings-schema.json +85 -0
- package/skills/ce-doc-review/references/modes.md +20 -0
- package/skills/ce-doc-review/references/open-questions-defer.md +158 -0
- package/skills/ce-doc-review/references/persona-selection.md +41 -0
- package/skills/ce-doc-review/references/personas/adversarial-document-reviewer.md +109 -0
- package/skills/ce-doc-review/references/personas/coherence-reviewer.md +69 -0
- package/skills/ce-doc-review/references/personas/design-lens-reviewer.md +48 -0
- package/skills/ce-doc-review/references/personas/feasibility-reviewer.md +50 -0
- package/skills/ce-doc-review/references/personas/product-lens-reviewer.md +85 -0
- package/skills/ce-doc-review/references/personas/scope-guardian-reviewer.md +83 -0
- package/skills/ce-doc-review/references/personas/security-lens-reviewer.md +41 -0
- package/skills/ce-doc-review/references/personas/whole-doc-reviewer.md +25 -0
- package/skills/ce-doc-review/references/rendering-floor.md +175 -0
- package/skills/ce-doc-review/references/review-output-template.md +123 -0
- package/skills/ce-doc-review/references/subagent-template.md +188 -0
- package/skills/ce-doc-review/references/synthesis-and-presentation.md +350 -0
- package/skills/ce-doc-review/references/walkthrough.md +347 -0
- package/skills/ce-doc-review/scripts/cross-model-doc-review.sh +1270 -0
- package/skills/ce-doc-review/scripts/packs-resolve.py +737 -0
- package/skills/ce-doc-review/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-dogfood/SKILL.md +66 -0
- package/skills/ce-dogfood/agents/openai.yaml +2 -0
- package/skills/ce-dogfood/references/dogfood-report-template.md +97 -0
- package/skills/ce-dogfood/references/phases.md +167 -0
- package/skills/ce-dogfood/references/test-matrix-taxonomy.md +62 -0
- package/skills/ce-dogfood/scripts/packs-resolve.py +737 -0
- package/skills/ce-explain/SKILL.md +81 -0
- package/skills/ce-explain/references/agents/behavior-trace-scout.md +19 -0
- package/skills/ce-explain/references/agents/work-recap-scout.md +23 -0
- package/skills/ce-explain/references/check-in.md +18 -0
- package/skills/ce-explain/references/destinations.md +35 -0
- package/skills/ce-explain/references/explainer-html.md +29 -0
- package/skills/ce-explain/references/explainer-markdown.md +24 -0
- package/skills/ce-explain/references/intake.md +50 -0
- package/skills/ce-explain/references/orchestration.md +46 -0
- package/skills/ce-handoff/SKILL.md +54 -0
- package/skills/ce-handoff/references/create.md +88 -0
- package/skills/ce-handoff/references/resume.md +46 -0
- package/skills/ce-ideate/SKILL.md +75 -0
- package/skills/ce-ideate/references/agents/issue-intelligence-analyst.md +166 -0
- package/skills/ce-ideate/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-ideate/references/agents/slack-researcher.md +127 -0
- package/skills/ce-ideate/references/agents/web-researcher.md +121 -0
- package/skills/ce-ideate/references/decomposition.md +39 -0
- package/skills/ce-ideate/references/divergent-ideation.md +112 -0
- package/skills/ce-ideate/references/grounding.md +138 -0
- package/skills/ce-ideate/references/html-rendering.md +638 -0
- package/skills/ce-ideate/references/ideation-sections.md +191 -0
- package/skills/ce-ideate/references/issue-intelligence.md +44 -0
- package/skills/ce-ideate/references/markdown-rendering.md +242 -0
- package/skills/ce-ideate/references/output-mode.md +34 -0
- package/skills/ce-ideate/references/post-ideation-workflow.md +169 -0
- package/skills/ce-ideate/references/scope-gates.md +148 -0
- package/skills/ce-ideate/references/universal-ideation.md +119 -0
- package/skills/ce-ideate/references/user-research-artifacts.md +28 -0
- package/skills/ce-ideate/references/web-research-cache.md +56 -0
- package/skills/ce-noslop/SKILL.md +45 -0
- package/skills/ce-noslop/references/patterns.md +61 -0
- package/skills/ce-noslop/references/terminology.md +3 -0
- package/skills/ce-optimize/SKILL.md +64 -0
- package/skills/ce-optimize/references/agents/learnings-researcher.md +247 -0
- package/skills/ce-optimize/references/agents/repo-research-analyst.md +258 -0
- package/skills/ce-optimize/references/example-expensive-benchmark-spec.yaml +88 -0
- package/skills/ce-optimize/references/example-hard-spec.yaml +64 -0
- package/skills/ce-optimize/references/example-judge-spec.yaml +83 -0
- package/skills/ce-optimize/references/example-text-target-spec.yaml +80 -0
- package/skills/ce-optimize/references/experiment-log-schema.yaml +563 -0
- package/skills/ce-optimize/references/experiment-prompt-template.md +143 -0
- package/skills/ce-optimize/references/judge-prompt-template.md +117 -0
- package/skills/ce-optimize/references/loop.md +286 -0
- package/skills/ce-optimize/references/measurement.md +168 -0
- package/skills/ce-optimize/references/optimize-spec-schema.yaml +570 -0
- package/skills/ce-optimize/references/persistence.md +93 -0
- package/skills/ce-optimize/references/spec.md +107 -0
- package/skills/ce-optimize/references/text-targets.md +51 -0
- package/skills/ce-optimize/references/usage-guide.md +147 -0
- package/skills/ce-optimize/references/wrap-up.md +54 -0
- package/skills/ce-optimize/scripts/decide.mjs +676 -0
- package/skills/ce-optimize/scripts/experiment-worktree.sh +330 -0
- package/skills/ce-optimize/scripts/measure.sh +140 -0
- package/skills/ce-optimize/scripts/parallel-probe.sh +134 -0
- package/skills/ce-plan/SKILL.md +60 -0
- package/skills/ce-plan/references/agents/agent-native-planning-strategist.md +66 -0
- package/skills/ce-plan/references/agents/architecture-strategist.md +46 -0
- package/skills/ce-plan/references/agents/best-practices-researcher.md +118 -0
- package/skills/ce-plan/references/agents/data-integrity-guardian.md +68 -0
- package/skills/ce-plan/references/agents/framework-docs-researcher.md +97 -0
- package/skills/ce-plan/references/agents/git-history-analyzer.md +40 -0
- package/skills/ce-plan/references/agents/learnings-researcher.md +268 -0
- package/skills/ce-plan/references/agents/pattern-recognition-specialist.md +55 -0
- package/skills/ce-plan/references/agents/performance-oracle.md +108 -0
- package/skills/ce-plan/references/agents/repo-research-analyst.md +262 -0
- package/skills/ce-plan/references/agents/security-sentinel.md +91 -0
- package/skills/ce-plan/references/agents/slack-researcher.md +131 -0
- package/skills/ce-plan/references/agents/spec-flow-analyzer.md +84 -0
- package/skills/ce-plan/references/agents/web-researcher.md +125 -0
- package/skills/ce-plan/references/approach-altitude.md +55 -0
- package/skills/ce-plan/references/bakeoff.md +9 -0
- package/skills/ce-plan/references/deepening-workflow.md +263 -0
- package/skills/ce-plan/references/final-review.md +134 -0
- package/skills/ce-plan/references/html-rendering.md +638 -0
- package/skills/ce-plan/references/intake.md +158 -0
- package/skills/ce-plan/references/markdown-rendering.md +242 -0
- package/skills/ce-plan/references/output-contracts.md +29 -0
- package/skills/ce-plan/references/output-mode.md +45 -0
- package/skills/ce-plan/references/plan-handoff.md +116 -0
- package/skills/ce-plan/references/plan-sections.md +489 -0
- package/skills/ce-plan/references/reasoning-elevation.md +124 -0
- package/skills/ce-plan/references/research.md +211 -0
- package/skills/ce-plan/references/resume.md +61 -0
- package/skills/ce-plan/references/settled-decisions.md +45 -0
- package/skills/ce-plan/references/structure.md +207 -0
- package/skills/ce-plan/references/synthesis-summary.md +260 -0
- package/skills/ce-plan/references/universal-planning.md +170 -0
- package/skills/ce-plan/scripts/elevation-dispatch.sh +288 -0
- package/skills/ce-plan/scripts/packs-resolve.py +737 -0
- package/skills/ce-plan/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-polish/SKILL.md +22 -0
- package/skills/ce-polish/agents/openai.yaml +2 -0
- package/skills/ce-polish/references/dev-server-astro.md +58 -0
- package/skills/ce-polish/references/dev-server-detection.md +40 -0
- package/skills/ce-polish/references/dev-server-next.md +61 -0
- package/skills/ce-polish/references/dev-server-nuxt.md +58 -0
- package/skills/ce-polish/references/dev-server-procfile.md +59 -0
- package/skills/ce-polish/references/dev-server-rails.md +50 -0
- package/skills/ce-polish/references/dev-server-remix.md +58 -0
- package/skills/ce-polish/references/dev-server-sveltekit.md +58 -0
- package/skills/ce-polish/references/dev-server-vite.md +48 -0
- package/skills/ce-polish/references/install-riffrec.md +35 -0
- package/skills/ce-polish/references/launch-json-schema.md +177 -0
- package/skills/ce-polish/references/live-loop.md +98 -0
- package/skills/ce-polish/references/live-remote.md +35 -0
- package/skills/ce-polish/references/live-start.md +93 -0
- package/skills/ce-polish/references/live-stream-contract.md +253 -0
- package/skills/ce-polish/references/run.md +67 -0
- package/skills/ce-polish/scripts/detect-project-type.sh +277 -0
- package/skills/ce-polish/scripts/detect-riffrec.sh +225 -0
- package/skills/ce-polish/scripts/live-endpoint.js +2841 -0
- package/skills/ce-polish/scripts/read-launch-json.sh +117 -0
- package/skills/ce-polish/scripts/resolve-package-manager.sh +95 -0
- package/skills/ce-polish/scripts/resolve-port.sh +307 -0
- package/skills/ce-pov/SKILL.md +64 -0
- package/skills/ce-pov/references/agents/external-evidence-researcher.md +26 -0
- package/skills/ce-pov/references/agents/pov-peer.md +43 -0
- package/skills/ce-pov/references/agents/precedent-activity-scout.md +21 -0
- package/skills/ce-pov/references/agents/project-grounding-scout.md +32 -0
- package/skills/ce-pov/references/boundaries.md +36 -0
- package/skills/ce-pov/references/cross-model-panel.md +596 -0
- package/skills/ce-pov/references/followup.md +9 -0
- package/skills/ce-pov/references/grounding.md +47 -0
- package/skills/ce-pov/references/intake.md +61 -0
- package/skills/ce-pov/references/invocation.md +47 -0
- package/skills/ce-pov/references/method.md +68 -0
- package/skills/ce-pov/references/pov-schema.json +56 -0
- package/skills/ce-pov/references/report.md +7 -0
- package/skills/ce-pov/scripts/cross-model-pov.sh +1110 -0
- package/skills/ce-pov/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-product-pulse/SKILL.md +81 -0
- package/skills/ce-product-pulse/agents/openai.yaml +2 -0
- package/skills/ce-product-pulse/references/config.md +20 -0
- package/skills/ce-product-pulse/references/interview.md +260 -0
- package/skills/ce-product-pulse/references/report-template.md +89 -0
- package/skills/ce-product-pulse/references/run.md +46 -0
- package/skills/ce-product-pulse/references/setup.md +43 -0
- package/skills/ce-promote/SKILL.md +90 -0
- package/skills/ce-promote/agents/openai.yaml +2 -0
- package/skills/ce-promote/references/spiral-cli.md +149 -0
- package/skills/ce-proof/SKILL.md +65 -0
- package/skills/ce-proof/references/api.md +176 -0
- package/skills/ce-proof/references/workflows.md +124 -0
- package/skills/ce-prototype/SKILL.md +59 -0
- package/skills/ce-prototype/assets/annotate.css +206 -0
- package/skills/ce-prototype/assets/annotate.js +837 -0
- package/skills/ce-prototype/references/annotation-loop.md +32 -0
- package/skills/ce-prototype/references/build.md +39 -0
- package/skills/ce-prototype/references/craft-floor.md +43 -0
- package/skills/ce-prototype/references/preview.md +118 -0
- package/skills/ce-prototype/references/scoping.md +38 -0
- package/skills/ce-prototype/references/write-back.md +37 -0
- package/skills/ce-prototype/scripts/light-webserver.js +1275 -0
- package/skills/ce-resolve-pr-feedback/SKILL.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/agents/pr-comment-resolver.md +59 -0
- package/skills/ce-resolve-pr-feedback/references/evaluation-rubric.md +141 -0
- package/skills/ce-resolve-pr-feedback/references/full-mode.md +336 -0
- package/skills/ce-resolve-pr-feedback/references/pipeline-mode.md +23 -0
- package/skills/ce-resolve-pr-feedback/references/resume.md +60 -0
- package/skills/ce-resolve-pr-feedback/references/return-to-caller.md +111 -0
- package/skills/ce-resolve-pr-feedback/references/targeted-mode.md +48 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-pr-comments +176 -0
- package/skills/ce-resolve-pr-feedback/scripts/get-thread-for-comment +84 -0
- package/skills/ce-resolve-pr-feedback/scripts/pending-feedback.py +287 -0
- package/skills/ce-resolve-pr-feedback/scripts/reply-to-pr-thread +57 -0
- package/skills/ce-resolve-pr-feedback/scripts/resolve-pr-thread +23 -0
- package/skills/ce-retune/SKILL.md +42 -0
- package/skills/ce-retune/agents/openai.yaml +2 -0
- package/skills/ce-retune/references/baseline-mining.md +122 -0
- package/skills/ce-retune/references/corpus-audit.md +128 -0
- package/skills/ce-retune/references/cut-passes.md +129 -0
- package/skills/ce-retune/references/halt-taxonomy.md +123 -0
- package/skills/ce-retune/references/noise-floor.md +131 -0
- package/skills/ce-retune/references/workflow-shapes.md +73 -0
- package/skills/ce-riffrec-feedback-analysis/SKILL.md +33 -0
- package/skills/ce-riffrec-feedback-analysis/references/analyzer.md +18 -0
- package/skills/ce-riffrec-feedback-analysis/references/compound-engineering-feedback-format.md +116 -0
- package/skills/ce-riffrec-feedback-analysis/references/extensive-analysis.md +98 -0
- package/skills/ce-riffrec-feedback-analysis/references/install-riffrec.md +27 -0
- package/skills/ce-riffrec-feedback-analysis/references/quick-bug-report.md +38 -0
- package/skills/ce-riffrec-feedback-analysis/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-setup/SKILL.md +103 -0
- package/skills/ce-setup/agents/openai.yaml +2 -0
- package/skills/ce-setup/assets/compounding-directive.md +11 -0
- package/skills/ce-setup/assets/noslop-directive.md +7 -0
- package/skills/ce-setup/assets/pack-rule-template.md +11 -0
- package/skills/ce-setup/references/acpx-cache-warm.md +14 -0
- package/skills/ce-setup/references/config-template.yaml +210 -0
- package/skills/ce-setup/references/legacy-codex-tool-map.md +15 -0
- package/skills/ce-setup/references/pack-scaffold.md +45 -0
- package/skills/ce-setup/references/repo-fixes.md +121 -0
- package/skills/ce-setup/scripts/check-health +929 -0
- package/skills/ce-setup/scripts/packs-resolve.py +737 -0
- package/skills/ce-simplify-code/SKILL.md +76 -0
- package/skills/ce-simplify-code/references/personas/code-quality-reviewer.md +17 -0
- package/skills/ce-simplify-code/references/personas/code-reuse-reviewer.md +7 -0
- package/skills/ce-simplify-code/references/personas/comment-reviewer.md +24 -0
- package/skills/ce-simplify-code/references/personas/efficiency-reviewer.md +11 -0
- package/skills/ce-simplify-code/references/personas/test-value-reviewer.md +23 -0
- package/skills/ce-simplify-code/references/reviewer-dispatch.md +11 -0
- package/skills/ce-simplify-code/references/standalone-report.md +25 -0
- package/skills/ce-strategy/SKILL.md +61 -0
- package/skills/ce-strategy/references/grounding.md +22 -0
- package/skills/ce-strategy/references/interview.md +179 -0
- package/skills/ce-strategy/references/strategy-template.md +94 -0
- package/skills/ce-strategy/references/update-run.md +33 -0
- package/skills/ce-sweep/SKILL.md +93 -0
- package/skills/ce-sweep/agents/openai.yaml +2 -0
- package/skills/ce-sweep/references/agents/media-analyzer.md +54 -0
- package/skills/ce-sweep/references/interview.md +208 -0
- package/skills/ce-sweep/references/model-tiers.md +13 -0
- package/skills/ce-sweep/references/plan-template.md +62 -0
- package/skills/ce-sweep/references/run.md +126 -0
- package/skills/ce-sweep/references/sources/email.md +57 -0
- package/skills/ce-sweep/references/sources/github-issues.md +59 -0
- package/skills/ce-sweep/references/sources/slack.md +61 -0
- package/skills/ce-sweep/references/state-schema.md +157 -0
- package/skills/ce-sweep/references/subagent-template.md +60 -0
- package/skills/ce-sweep/scripts/analyze_riffrec_zip.py +1306 -0
- package/skills/ce-sweep/scripts/sweep-state.py +793 -0
- package/skills/ce-test-browser/SKILL.md +50 -0
- package/skills/ce-test-browser/references/agent-browser-driver.md +47 -0
- package/skills/ce-test-browser/references/pipeline-orchestration.md +45 -0
- package/skills/ce-test-browser/references/route-and-report.md +129 -0
- package/skills/ce-test-browser/scripts/resolve-port.sh +41 -0
- package/skills/ce-test-xcode/SKILL.md +26 -0
- package/skills/ce-test-xcode/agents/openai.yaml +2 -0
- package/skills/ce-test-xcode/references/setup-and-build.md +32 -0
- package/skills/ce-test-xcode/references/test-and-report.md +64 -0
- package/skills/ce-work/SKILL.md +62 -0
- package/skills/ce-work/references/agents/figma-design-sync.md +27 -0
- package/skills/ce-work/references/agents/implementation-worker.md +17 -0
- package/skills/ce-work/references/cross-model-execution.md +191 -0
- package/skills/ce-work/references/execution-engines.md +64 -0
- package/skills/ce-work/references/execution-strategy.md +73 -0
- package/skills/ce-work/references/implementation-loop.md +107 -0
- package/skills/ce-work/references/implementation-result-schema.json +52 -0
- package/skills/ce-work/references/input-triage.md +50 -0
- package/skills/ce-work/references/non-code-execution.md +23 -0
- package/skills/ce-work/references/return-to-caller.md +33 -0
- package/skills/ce-work/references/review-findings-followup.md +82 -0
- package/skills/ce-work/references/shipping-workflow.md +89 -0
- package/skills/ce-work/references/tracker-defer.md +150 -0
- package/skills/ce-work/references/work-intake.md +51 -0
- package/skills/ce-work/references/workspace-setup.md +27 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_jobs.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/__pycache__/unit_workspace_state.cpython-314.pyc +0 -0
- package/skills/ce-work/scripts/cross-model-work.sh +1385 -0
- package/skills/ce-work/scripts/peer-job-runner.py +2594 -0
- package/skills/ce-work/scripts/unit-workspace.py +215 -0
- package/skills/ce-work/scripts/unit_workspace_ignored.py +93 -0
- package/skills/ce-work/scripts/unit_workspace_integration.py +677 -0
- package/skills/ce-work/scripts/unit_workspace_jobs.py +1216 -0
- package/skills/ce-work/scripts/unit_workspace_lifecycle.py +981 -0
- package/skills/ce-work/scripts/unit_workspace_state.py +1379 -0
- package/skills/ce-work/scripts/unit_workspace_transaction.py +612 -0
- package/skills/ce-worktree/SKILL.md +54 -0
- package/skills/lfg/SKILL.md +59 -0
- package/skills/lfg/references/debug-return.md +25 -0
- package/skills/lfg/references/intake.md +30 -0
- package/skills/lfg/references/next-work-handoff.md +96 -0
- package/skills/lfg/references/plan-brief.md +36 -0
- package/skills/lfg/references/review-followup.md +78 -0
- package/skills/lfg/references/shipping.md +65 -0
- package/skills/lfg/references/stage-routing.md +49 -0
- package/skills/lfg/references/task-visibility.md +11 -0
- package/skills/lfg/references/tracker-defer.md +150 -0
- package/skills/lfg/references/work-return.md +27 -0
- package/skills/wtf/SKILL.md +33 -0
- package/skills/wtf/agents/openai.yaml +2 -0
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
"""Canonical integration, locking, wave sequencing, and exact restoration."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import os
|
|
7
|
+
import re
|
|
8
|
+
import secrets
|
|
9
|
+
import shutil
|
|
10
|
+
from pathlib import Path
|
|
11
|
+
|
|
12
|
+
from unit_workspace_state import *
|
|
13
|
+
from unit_workspace_jobs import find_attempt, parse_diff_paths, scope_expansion_pending
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def integration_lock_path(doc: dict) -> str:
|
|
17
|
+
ident = doc["repository"]["identity_digest"] + "\0" + doc["branch"]["ref"]
|
|
18
|
+
# Anchor to the root this run actually lives under (run_dir searches both
|
|
19
|
+
# candidate roots), never the current invocation's preferred creation root:
|
|
20
|
+
# a fallback-root run must validate and release the lock it recorded.
|
|
21
|
+
run_root = os.path.dirname(run_dir(doc["run_id"]))
|
|
22
|
+
return os.path.join(run_root, ".locks", f"integration-{digest_bytes(ident.encode())}.json")
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def read_integration_lock(path: str) -> dict:
|
|
26
|
+
return read_private_json(path)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def validated_lock_nonce(doc: dict, unit_id: str, lock: dict) -> str:
|
|
30
|
+
expected = {
|
|
31
|
+
"run_id": doc["run_id"],
|
|
32
|
+
"unit_id": unit_id,
|
|
33
|
+
"repository": doc["repository"]["identity_digest"],
|
|
34
|
+
"branch_ref": doc["branch"]["ref"],
|
|
35
|
+
}
|
|
36
|
+
if any(lock.get(k) != v for k, v in expected.items()):
|
|
37
|
+
raise Operational("BLOCKED", "integration lock identity mismatch")
|
|
38
|
+
nonce = lock.get("nonce")
|
|
39
|
+
if not isinstance(nonce, str) or not re.fullmatch(r"[0-9a-f]{48}", nonce):
|
|
40
|
+
raise TrustFailure("integration lock nonce is malformed")
|
|
41
|
+
return nonce
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def validate_lock(doc: dict, unit_id: str, token: str) -> tuple[str, dict]:
|
|
45
|
+
path = integration_lock_path(doc)
|
|
46
|
+
try:
|
|
47
|
+
lock = read_integration_lock(path)
|
|
48
|
+
except TrustFailure as exc:
|
|
49
|
+
if not os.path.lexists(path):
|
|
50
|
+
raise Operational("BLOCKED", "integration lock is missing") from exc
|
|
51
|
+
raise
|
|
52
|
+
nonce = validated_lock_nonce(doc, unit_id, lock)
|
|
53
|
+
if nonce != token:
|
|
54
|
+
raise Operational("BLOCKED", "integration lock token or identity mismatch")
|
|
55
|
+
return path, lock
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def cmd_integration_acquire(args) -> tuple[str, dict]:
|
|
59
|
+
with locked_manifest(args.run_id) as doc:
|
|
60
|
+
validate_repo(doc)
|
|
61
|
+
unit = doc["units"].get(args.unit_id)
|
|
62
|
+
plan_verification = bool(getattr(args, "plan_verification", False))
|
|
63
|
+
recover_only = bool(getattr(args, "recover_only", False))
|
|
64
|
+
allowed_states = INTEGRATABLE_STATES | {"preserved", "committed", "cleaned"}
|
|
65
|
+
if plan_verification or (recover_only and unit and unit.get("state") == "native-completed"):
|
|
66
|
+
allowed_states.add("native-completed")
|
|
67
|
+
if not unit or unit["state"] not in allowed_states:
|
|
68
|
+
raise Operational("REFUSED", "unit is not ready for integration")
|
|
69
|
+
if not plan_verification and unit["state"] != "native-completed":
|
|
70
|
+
validate_wave_order(doc, unit)
|
|
71
|
+
path = integration_lock_path(doc)
|
|
72
|
+
existing = doc.get("integration_lock")
|
|
73
|
+
if existing:
|
|
74
|
+
if not args.resume:
|
|
75
|
+
raise Operational("REFUSED", "integration claim already exists; pass --resume to recover the same claim")
|
|
76
|
+
if existing.get("phase", "held") != "held":
|
|
77
|
+
raise Operational("REFUSED", "integration claim is releasing; resume or retry release before acquisition")
|
|
78
|
+
validate_lock(doc, args.unit_id, existing["nonce"])
|
|
79
|
+
return "ACQUIRED", {"lock_token": existing["nonce"], "resumed": True, "path": path}
|
|
80
|
+
nonce = secrets.token_hex(24)
|
|
81
|
+
resumed = False
|
|
82
|
+
if recover_only:
|
|
83
|
+
lock = read_integration_lock(path)
|
|
84
|
+
nonce = validated_lock_nonce(doc, args.unit_id, lock)
|
|
85
|
+
resumed = True
|
|
86
|
+
else:
|
|
87
|
+
payload = {"run_id": args.run_id, "unit_id": args.unit_id, "nonce": nonce, "repository": doc["repository"]["identity_digest"], "branch_ref": doc["branch"]["ref"], "created_at": now_iso()}
|
|
88
|
+
try:
|
|
89
|
+
create_private(path, (json.dumps(payload, sort_keys=True) + "\n").encode())
|
|
90
|
+
test_fault("integration-lock-after-create")
|
|
91
|
+
except Operational as exc:
|
|
92
|
+
if exc.word == "INTERRUPTED":
|
|
93
|
+
raise
|
|
94
|
+
lock = read_integration_lock(path)
|
|
95
|
+
if lock.get("run_id") == args.run_id and lock.get("unit_id") == args.unit_id:
|
|
96
|
+
if not args.resume:
|
|
97
|
+
raise Operational("REFUSED", "integration lock file already exists; pass --resume to recover its claim")
|
|
98
|
+
nonce = validated_lock_nonce(doc, args.unit_id, lock)
|
|
99
|
+
resumed = True
|
|
100
|
+
else:
|
|
101
|
+
raise Operational("BLOCKED", "another run/unit owns canonical integration", {"owner_run": lock.get("run_id"), "owner_unit": lock.get("unit_id")})
|
|
102
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
103
|
+
doc["integration_lock"] = {"unit_id": args.unit_id, "nonce": nonce, "path": path, "phase": "held"}
|
|
104
|
+
event(doc, "integration-lock-acquired", args.unit_id, {"resumed": resumed})
|
|
105
|
+
return "ACQUIRED", {"lock_token": nonce, "resumed": resumed, "path": path}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def semantic_snapshot(repo: str) -> dict:
|
|
109
|
+
head = git_text(repo, "rev-parse", "HEAD")
|
|
110
|
+
head_tree = git_text(repo, "rev-parse", "HEAD^{tree}")
|
|
111
|
+
index_tree = git_text(repo, "write-tree")
|
|
112
|
+
raw = git(repo, "status", "--porcelain=v2", "-z", "--untracked-files=all")
|
|
113
|
+
worktree_index = git(repo, "diff", "--name-only", "-z")
|
|
114
|
+
return {
|
|
115
|
+
"head": head,
|
|
116
|
+
"branch_ref": git_text(repo, "symbolic-ref", "-q", "HEAD", check=False),
|
|
117
|
+
"head_tree": head_tree,
|
|
118
|
+
"index_tree": index_tree,
|
|
119
|
+
"status_sha256": digest_bytes(raw),
|
|
120
|
+
"status_empty": not bool(raw),
|
|
121
|
+
"worktree_index_empty": not bool(worktree_index),
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
def expected_apply_snapshot(repo: str, pre_head: str, unit: dict) -> dict:
|
|
126
|
+
transport = unit["transport"]
|
|
127
|
+
if pre_head == transport["base"]:
|
|
128
|
+
tree = transport["tree"]
|
|
129
|
+
else:
|
|
130
|
+
# Compute the same semantic three-way result as applying the
|
|
131
|
+
# base-parented transport commit, without touching the canonical index.
|
|
132
|
+
merged = git_text(repo, "merge-tree", "--write-tree", pre_head, transport["commit"])
|
|
133
|
+
tree = merged.splitlines()[0] if merged else ""
|
|
134
|
+
if not tree:
|
|
135
|
+
raise Operational("BLOCKED", "could not derive expected canonical apply tree")
|
|
136
|
+
raw = git(repo, "diff-tree", "-r", "-M", "--name-status", "-z", pre_head, tree)
|
|
137
|
+
return {"index_tree": tree, "changed_paths": parse_diff_paths(raw)}
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
def matches_expected_apply(repo: str, unit: dict, snap: dict | None = None) -> bool:
|
|
141
|
+
snap = snap or semantic_snapshot(repo)
|
|
142
|
+
pre = unit.get("integration", {}).get("pre_fold")
|
|
143
|
+
expected = unit.get("integration", {}).get("expected_apply")
|
|
144
|
+
if not pre or not expected:
|
|
145
|
+
return False
|
|
146
|
+
return (
|
|
147
|
+
snap["head"] == pre["head"]
|
|
148
|
+
and snap["index_tree"] == expected["index_tree"]
|
|
149
|
+
and snap["worktree_index_empty"]
|
|
150
|
+
and status_paths(repo) == set(expected["changed_paths"])
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
def wave_members(doc: dict, unit: dict) -> list[dict]:
|
|
155
|
+
wave = unit.get("wave", {})
|
|
156
|
+
wave_id = wave.get("id")
|
|
157
|
+
if not wave_id:
|
|
158
|
+
return []
|
|
159
|
+
base = wave.get("base")
|
|
160
|
+
members = [
|
|
161
|
+
candidate for candidate in doc.get("units", {}).values()
|
|
162
|
+
if candidate.get("wave", {}).get("id") == wave_id
|
|
163
|
+
]
|
|
164
|
+
positions = [candidate.get("wave", {}).get("position") for candidate in members]
|
|
165
|
+
if any(candidate.get("wave", {}).get("base") != base for candidate in members):
|
|
166
|
+
raise Operational("BLOCKED", "wave members do not share one recorded base")
|
|
167
|
+
if len(set(positions)) != len(positions):
|
|
168
|
+
raise Operational("BLOCKED", "wave positions are not unique")
|
|
169
|
+
return sorted(members, key=lambda candidate: candidate["wave"]["position"])
|
|
170
|
+
|
|
171
|
+
|
|
172
|
+
def validate_wave_order(doc: dict, unit: dict) -> None:
|
|
173
|
+
members = wave_members(doc, unit)
|
|
174
|
+
earlier_unresolved = [
|
|
175
|
+
candidate["unit_id"] for candidate in members
|
|
176
|
+
if candidate["wave"]["position"] < unit["wave"]["position"]
|
|
177
|
+
and not (
|
|
178
|
+
candidate.get("state") in {"committed", "preserved", "cleaned"}
|
|
179
|
+
or (
|
|
180
|
+
candidate.get("state") == "native-completed"
|
|
181
|
+
and unit_accepted_commit(candidate) is not None
|
|
182
|
+
)
|
|
183
|
+
)
|
|
184
|
+
]
|
|
185
|
+
if earlier_unresolved:
|
|
186
|
+
raise Operational(
|
|
187
|
+
"BLOCKED",
|
|
188
|
+
"earlier wave units must be committed or preserved before this fold-in",
|
|
189
|
+
{"reason": "earlier wave unit not resolved", "units": earlier_unresolved},
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def wave_member_changed_paths(candidate: dict) -> set[str] | None:
|
|
194
|
+
transport = candidate.get("transport", {})
|
|
195
|
+
if transport.get("commit"):
|
|
196
|
+
paths = transport.get("changed_paths")
|
|
197
|
+
elif candidate.get("state") == "native-completed":
|
|
198
|
+
if unit_accepted_commit(candidate) is None:
|
|
199
|
+
raise TrustFailure("native wave completion evidence is malformed")
|
|
200
|
+
completion = find_attempt(candidate).get("fallback", {}).get("completed")
|
|
201
|
+
paths = completion.get("changed_paths") if isinstance(completion, dict) else None
|
|
202
|
+
else:
|
|
203
|
+
return None
|
|
204
|
+
if not isinstance(paths, list) or any(not isinstance(path, str) for path in paths):
|
|
205
|
+
raise TrustFailure("wave member changed paths are malformed")
|
|
206
|
+
return set(paths)
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def validate_wave_collisions(
|
|
210
|
+
doc: dict,
|
|
211
|
+
unit: dict,
|
|
212
|
+
overrides: dict[str, set[str]] | None = None,
|
|
213
|
+
require_complete: bool = True,
|
|
214
|
+
) -> None:
|
|
215
|
+
members = wave_members(doc, unit)
|
|
216
|
+
if not members:
|
|
217
|
+
return
|
|
218
|
+
overrides = overrides or {}
|
|
219
|
+
changed_by_unit: dict[str, set[str]] = {}
|
|
220
|
+
unterminated: list[str] = []
|
|
221
|
+
for candidate in members:
|
|
222
|
+
unit_id = candidate["unit_id"]
|
|
223
|
+
paths = overrides.get(unit_id)
|
|
224
|
+
if paths is None:
|
|
225
|
+
paths = wave_member_changed_paths(candidate)
|
|
226
|
+
if paths is None:
|
|
227
|
+
unterminated.append(unit_id)
|
|
228
|
+
else:
|
|
229
|
+
changed_by_unit[unit_id] = paths
|
|
230
|
+
if unterminated:
|
|
231
|
+
if require_complete:
|
|
232
|
+
raise Operational(
|
|
233
|
+
"BLOCKED",
|
|
234
|
+
"every wave worker must terminalize before the first fold-in",
|
|
235
|
+
{"reason": "wave not fully terminalized", "units": unterminated},
|
|
236
|
+
)
|
|
237
|
+
collisions: dict[str, list[str]] = {}
|
|
238
|
+
for index, left in enumerate(members):
|
|
239
|
+
for right in members[index + 1:]:
|
|
240
|
+
left_paths = changed_by_unit.get(left["unit_id"])
|
|
241
|
+
right_paths = changed_by_unit.get(right["unit_id"])
|
|
242
|
+
if left_paths is None or right_paths is None:
|
|
243
|
+
continue
|
|
244
|
+
overlap = sorted(left_paths & right_paths)
|
|
245
|
+
if overlap:
|
|
246
|
+
collisions[f'{left["unit_id"]}:{right["unit_id"]}'] = overlap
|
|
247
|
+
if collisions:
|
|
248
|
+
raise Operational(
|
|
249
|
+
"BLOCKED",
|
|
250
|
+
"wave transports have a changed-path collision",
|
|
251
|
+
{"reason": "changed-path collision", "collisions": collisions},
|
|
252
|
+
)
|
|
253
|
+
|
|
254
|
+
|
|
255
|
+
def validate_wave_ready(doc: dict, unit: dict) -> None:
|
|
256
|
+
members = wave_members(doc, unit)
|
|
257
|
+
if not members:
|
|
258
|
+
return
|
|
259
|
+
validate_wave_order(doc, unit)
|
|
260
|
+
validate_wave_collisions(doc, unit)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def validate_wave_advancement(members: list[dict], unit: dict, parent: str, canonical: str) -> list[str]:
|
|
264
|
+
position = unit["wave"]["position"]
|
|
265
|
+
targets = [candidate for candidate in members if candidate["wave"]["position"] > position]
|
|
266
|
+
for candidate in targets:
|
|
267
|
+
allowed = candidate["wave"].get("allowed_heads", [])
|
|
268
|
+
if canonical in allowed:
|
|
269
|
+
continue
|
|
270
|
+
if not allowed or allowed[-1] != parent:
|
|
271
|
+
raise Operational("BLOCKED", "wave advancement is not the exact recorded canonical chain")
|
|
272
|
+
return [candidate["unit_id"] for candidate in targets]
|
|
273
|
+
|
|
274
|
+
|
|
275
|
+
def advance_wave_allowed_heads(members: list[dict], position: int, canonical: str) -> list[str]:
|
|
276
|
+
advanced: list[str] = []
|
|
277
|
+
for candidate in members:
|
|
278
|
+
if candidate["wave"]["position"] <= position:
|
|
279
|
+
continue
|
|
280
|
+
allowed = candidate["wave"].setdefault("allowed_heads", [])
|
|
281
|
+
if canonical not in allowed:
|
|
282
|
+
allowed.append(canonical)
|
|
283
|
+
advanced.append(candidate["unit_id"])
|
|
284
|
+
return advanced
|
|
285
|
+
|
|
286
|
+
|
|
287
|
+
def validate_dependencies_ready(doc: dict, unit: dict) -> None:
|
|
288
|
+
missing: list[str] = []
|
|
289
|
+
unaccepted: list[str] = []
|
|
290
|
+
for dependency_id in unit.get("dependencies", []):
|
|
291
|
+
dependency = doc.get("units", {}).get(dependency_id)
|
|
292
|
+
if dependency is None:
|
|
293
|
+
missing.append(dependency_id)
|
|
294
|
+
continue
|
|
295
|
+
if unit_accepted_commit(dependency) is None:
|
|
296
|
+
unaccepted.append(dependency_id)
|
|
297
|
+
if missing or unaccepted:
|
|
298
|
+
raise Operational(
|
|
299
|
+
"BLOCKED",
|
|
300
|
+
"unit dependencies must have controller-accepted canonical commits before preflight",
|
|
301
|
+
{
|
|
302
|
+
"unit_id": unit["unit_id"],
|
|
303
|
+
"missing_dependencies": missing,
|
|
304
|
+
"unaccepted_dependencies": unaccepted,
|
|
305
|
+
},
|
|
306
|
+
)
|
|
307
|
+
|
|
308
|
+
|
|
309
|
+
def dependency_advanced_head(doc: dict, unit: dict, head: str) -> bool:
|
|
310
|
+
dependency_commits = [
|
|
311
|
+
unit_accepted_commit(doc["units"][dependency_id])
|
|
312
|
+
for dependency_id in unit.get("dependencies", [])
|
|
313
|
+
]
|
|
314
|
+
if any(commit is None for commit in dependency_commits):
|
|
315
|
+
return False
|
|
316
|
+
accepted_heads = {
|
|
317
|
+
commit
|
|
318
|
+
for unit_id, candidate in doc.get("units", {}).items()
|
|
319
|
+
if unit_id != unit.get("unit_id")
|
|
320
|
+
if (commit := unit_accepted_commit(candidate)) is not None
|
|
321
|
+
}
|
|
322
|
+
if head not in accepted_heads:
|
|
323
|
+
return False
|
|
324
|
+
allowed_heads = unit.get("wave", {}).get("allowed_heads", [])
|
|
325
|
+
required_ancestors = {
|
|
326
|
+
*accepted_heads,
|
|
327
|
+
*dependency_commits,
|
|
328
|
+
*allowed_heads,
|
|
329
|
+
unit.get("workspace", {}).get("base"),
|
|
330
|
+
}
|
|
331
|
+
required_ancestors.discard(None)
|
|
332
|
+
repo = doc["repository"]["toplevel"]
|
|
333
|
+
return all(
|
|
334
|
+
git_text(repo, "merge-base", commit, head, check=False) == commit
|
|
335
|
+
for commit in required_ancestors
|
|
336
|
+
)
|
|
337
|
+
|
|
338
|
+
|
|
339
|
+
def validate_preflight_ancestry(doc: dict, unit: dict, heads: set[str]) -> None:
|
|
340
|
+
required = {
|
|
341
|
+
commit
|
|
342
|
+
for unit_id, candidate in doc.get("units", {}).items()
|
|
343
|
+
if unit_id != unit["unit_id"]
|
|
344
|
+
and (commit := unit_accepted_commit(candidate)) is not None
|
|
345
|
+
}
|
|
346
|
+
wave = unit.get("wave", {})
|
|
347
|
+
required.update(head for head in wave.get("allowed_heads", []) if head != wave.get("base"))
|
|
348
|
+
|
|
349
|
+
repo = doc["repository"]["toplevel"]
|
|
350
|
+
missing = {
|
|
351
|
+
head: sorted(
|
|
352
|
+
commit for commit in required
|
|
353
|
+
if git_text(repo, "merge-base", commit, head, check=False) != commit
|
|
354
|
+
)
|
|
355
|
+
for head in sorted(heads)
|
|
356
|
+
}
|
|
357
|
+
missing = {head: commits for head, commits in missing.items() if commits}
|
|
358
|
+
if missing:
|
|
359
|
+
raise Operational(
|
|
360
|
+
"BLOCKED",
|
|
361
|
+
"preflight HEAD omits controller-accepted prerequisite commits",
|
|
362
|
+
{"unit_id": unit["unit_id"], "missing_ancestry": missing},
|
|
363
|
+
)
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def cmd_preflight(args) -> tuple[str, dict]:
|
|
367
|
+
with locked_manifest(args.run_id) as doc:
|
|
368
|
+
info = validate_repo(doc)
|
|
369
|
+
unit = doc["units"].get(args.unit_id)
|
|
370
|
+
if not unit or unit["state"] not in {"integration-pending", "preserved"}:
|
|
371
|
+
raise Operational("REFUSED", "unit is not integration-pending")
|
|
372
|
+
validate_lock(doc, args.unit_id, args.lock_token)
|
|
373
|
+
validate_dependencies_ready(doc, unit)
|
|
374
|
+
if scope_expansion_pending(unit):
|
|
375
|
+
raise Operational(
|
|
376
|
+
"BLOCKED",
|
|
377
|
+
"worker requested scope expansion; inspect the retained result and transport, then resolve or re-dispatch explicitly",
|
|
378
|
+
{
|
|
379
|
+
"unit_id": args.unit_id,
|
|
380
|
+
"terminal_status": "scope_expansion",
|
|
381
|
+
"transport": unit["transport"],
|
|
382
|
+
"recovery_path": unit["recovery_path"],
|
|
383
|
+
},
|
|
384
|
+
)
|
|
385
|
+
validate_wave_ready(doc, unit)
|
|
386
|
+
allowed = set(unit["wave"].get("allowed_heads", []))
|
|
387
|
+
requested: set[str] = set()
|
|
388
|
+
if args.allowed_head:
|
|
389
|
+
requested = {git_text(info["toplevel"], "rev-parse", f"{h}^{{commit}}") for h in args.allowed_head}
|
|
390
|
+
if any(head not in allowed and not dependency_advanced_head(doc, unit, head) for head in requested):
|
|
391
|
+
raise Operational("BLOCKED", "unrecorded same-wave HEAD allowance")
|
|
392
|
+
if info["head"] not in allowed and not dependency_advanced_head(doc, unit, info["head"]):
|
|
393
|
+
raise Operational("BLOCKED", "canonical HEAD advanced outside the recorded wave")
|
|
394
|
+
validate_preflight_ancestry(doc, unit, requested | {info["head"]})
|
|
395
|
+
snap = semantic_snapshot(info["toplevel"])
|
|
396
|
+
if not snap["status_empty"] or snap["index_tree"] != snap["head_tree"]:
|
|
397
|
+
raise Operational("BLOCKED", "canonical checkout is not clean at preflight")
|
|
398
|
+
expected = expected_apply_snapshot(info["toplevel"], snap["head"], unit)
|
|
399
|
+
intent_revision = doc["revision"] + 1
|
|
400
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
401
|
+
unit = doc["units"][args.unit_id]
|
|
402
|
+
unit["state"] = "integration-pending"
|
|
403
|
+
unit["integration"]["intent_revision"] = intent_revision
|
|
404
|
+
unit["integration"]["pre_fold"] = snap
|
|
405
|
+
unit["integration"]["expected_apply"] = expected
|
|
406
|
+
event(doc, "canonical-apply-intent", args.unit_id, {"transport": unit["transport"]["commit"], "pre_head": snap["head"]})
|
|
407
|
+
return "PREFLIGHT_OK", {"unit_id": args.unit_id, "pre_fold": snap, "transport": unit["transport"]}
|
|
408
|
+
|
|
409
|
+
|
|
410
|
+
def cmd_mark_applied(args) -> tuple[str, dict]:
|
|
411
|
+
with locked_manifest(args.run_id) as doc:
|
|
412
|
+
validate_lock(doc, args.unit_id, args.lock_token)
|
|
413
|
+
unit = doc["units"].get(args.unit_id)
|
|
414
|
+
if not unit or unit["state"] not in {"integration-pending", "integrated"} or not unit["integration"].get("pre_fold"):
|
|
415
|
+
raise Operational("REFUSED", "no recorded preflight intent")
|
|
416
|
+
repo = validate_repo(doc)["toplevel"]
|
|
417
|
+
snap = semantic_snapshot(repo)
|
|
418
|
+
if snap["head"] != unit["integration"]["pre_fold"]["head"]:
|
|
419
|
+
raise Operational("BLOCKED", "canonical HEAD moved before apply was recorded")
|
|
420
|
+
if not matches_expected_apply(repo, unit, snap):
|
|
421
|
+
raise Operational("BLOCKED", "canonical state does not match the expected transport application")
|
|
422
|
+
test_fault("after-apply-observed")
|
|
423
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
424
|
+
unit = doc["units"][args.unit_id]
|
|
425
|
+
unit["state"] = "integrated"
|
|
426
|
+
unit["integration"]["applied"] = {"at": now_iso(), "post_index_tree": snap["index_tree"], "status_sha256": snap["status_sha256"]}
|
|
427
|
+
event(doc, "transport-applied", args.unit_id, {"post_index_tree": snap["index_tree"]})
|
|
428
|
+
return "APPLIED", {"unit_id": args.unit_id, "post_index_tree": snap["index_tree"]}
|
|
429
|
+
|
|
430
|
+
|
|
431
|
+
def cmd_mark_verified(args) -> tuple[str, dict]:
|
|
432
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
433
|
+
validate_lock(doc, args.unit_id, args.lock_token)
|
|
434
|
+
unit = doc["units"].get(args.unit_id)
|
|
435
|
+
if not unit or unit["state"] not in {"integrated", "verified"}:
|
|
436
|
+
raise Operational("REFUSED", "unit is not applied")
|
|
437
|
+
repo = validate_repo(doc)["toplevel"]
|
|
438
|
+
if not matches_expected_apply(repo, unit):
|
|
439
|
+
raise Operational(
|
|
440
|
+
"BLOCKED",
|
|
441
|
+
"canonical state changed after the recorded transport application",
|
|
442
|
+
{
|
|
443
|
+
"unit_id": args.unit_id,
|
|
444
|
+
"reason": "canonical state no longer matches the expected transport application",
|
|
445
|
+
},
|
|
446
|
+
)
|
|
447
|
+
evidence = {"at": now_iso(), "digest": args.evidence_digest, "summary": args.summary}
|
|
448
|
+
ignored_state = getattr(args, "ignored_state", None)
|
|
449
|
+
if isinstance(ignored_state, str):
|
|
450
|
+
ignored_state = parse_json_arg(ignored_state, "ignored-state")
|
|
451
|
+
if ignored_state is not None:
|
|
452
|
+
evidence["ignored_state"] = ignored_state
|
|
453
|
+
unit["integration"]["verification"] = evidence
|
|
454
|
+
unit["state"] = "verified"
|
|
455
|
+
event(doc, "canonical-verification-passed", args.unit_id, {"digest": args.evidence_digest})
|
|
456
|
+
return "VERIFIED", {"unit_id": args.unit_id, "verification": evidence}
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def reconcile_commit(doc: dict, unit: dict) -> dict | None:
|
|
460
|
+
repo = doc["repository"]["toplevel"]
|
|
461
|
+
head = git_text(repo, "rev-parse", "HEAD")
|
|
462
|
+
parents = git_text(repo, "rev-list", "--parents", "-n", "1", head).split()
|
|
463
|
+
expected_parent = unit["integration"]["pre_fold"]["head"]
|
|
464
|
+
expected_tree = unit["integration"]["applied"]["post_index_tree"]
|
|
465
|
+
actual_tree = git_text(repo, "rev-parse", "HEAD^{tree}")
|
|
466
|
+
if parents == [head, expected_parent] and actual_tree == expected_tree and not status_paths(repo):
|
|
467
|
+
return {"commit": head, "parent": expected_parent, "tree": actual_tree, "at": now_iso()}
|
|
468
|
+
return None
|
|
469
|
+
|
|
470
|
+
|
|
471
|
+
def cmd_mark_committed(args) -> tuple[str, dict]:
|
|
472
|
+
with locked_manifest(args.run_id) as doc:
|
|
473
|
+
validate_lock(doc, args.unit_id, args.lock_token)
|
|
474
|
+
unit = doc["units"].get(args.unit_id)
|
|
475
|
+
if not unit or unit["state"] not in {"verified", "committed"}:
|
|
476
|
+
raise Operational("REFUSED", "unit has not passed canonical verification")
|
|
477
|
+
commit = reconcile_commit(doc, unit)
|
|
478
|
+
if not commit:
|
|
479
|
+
raise Operational("BLOCKED", "canonical commit parent/tree/cleanliness do not match recorded integration")
|
|
480
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
481
|
+
unit = doc["units"][args.unit_id]
|
|
482
|
+
unit["integration"]["canonical_commit"] = commit
|
|
483
|
+
unit["state"] = "committed"
|
|
484
|
+
event(doc, "canonical-commit-confirmed", args.unit_id, {"commit": commit["commit"]})
|
|
485
|
+
return "COMMITTED", {"unit_id": args.unit_id, "canonical_commit": commit}
|
|
486
|
+
|
|
487
|
+
|
|
488
|
+
def cmd_wave_advance(args) -> tuple[str, dict]:
|
|
489
|
+
with locked_manifest(args.run_id) as doc:
|
|
490
|
+
info = validate_repo(doc)
|
|
491
|
+
unit = doc["units"].get(args.unit_id)
|
|
492
|
+
if not unit or unit.get("state") != "committed":
|
|
493
|
+
raise Operational("REFUSED", "only a committed wave unit can advance its siblings")
|
|
494
|
+
validate_lock(doc, args.unit_id, args.lock_token)
|
|
495
|
+
members = wave_members(doc, unit)
|
|
496
|
+
if not members:
|
|
497
|
+
raise Operational("REFUSED", "unit does not belong to a parallel wave")
|
|
498
|
+
validate_wave_ready(doc, unit)
|
|
499
|
+
canonical = git_text(info["toplevel"], "rev-parse", f"{args.canonical_commit}^{{commit}}")
|
|
500
|
+
recorded = unit.get("integration", {}).get("canonical_commit", {})
|
|
501
|
+
if recorded.get("commit") != canonical or info["head"] != canonical:
|
|
502
|
+
raise Operational("BLOCKED", "canonical wave commit does not match manifest and HEAD")
|
|
503
|
+
parent = unit.get("integration", {}).get("pre_fold", {}).get("head")
|
|
504
|
+
if recorded.get("parent") != parent:
|
|
505
|
+
raise Operational("BLOCKED", "canonical wave commit parent is not the recorded pre-fold HEAD")
|
|
506
|
+
validate_wave_advancement(members, unit, parent, canonical)
|
|
507
|
+
with locked_manifest(args.run_id, write=True) as doc:
|
|
508
|
+
unit = doc["units"][args.unit_id]
|
|
509
|
+
position = unit["wave"]["position"]
|
|
510
|
+
advanced = advance_wave_allowed_heads(wave_members(doc, unit), position, canonical)
|
|
511
|
+
event(doc, "wave-advanced", args.unit_id, {"canonical_commit": canonical, "eligible_siblings": advanced})
|
|
512
|
+
return "WAVE_ADVANCED", {"unit_id": args.unit_id, "canonical_commit": canonical, "eligible_siblings": advanced}
|
|
513
|
+
|
|
514
|
+
|
|
515
|
+
def path_in_tree(repo: str, treeish: str, rel: str) -> bool:
|
|
516
|
+
out = git(repo, "ls-tree", "-z", "--full-tree", treeish, "--", rel)
|
|
517
|
+
return bool(out)
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def remove_introduced_paths(repo: str, unit: dict) -> None:
|
|
521
|
+
pre = unit["integration"]["pre_fold"]["head"]
|
|
522
|
+
base = unit["transport"]["base"]
|
|
523
|
+
commit = unit["transport"]["commit"]
|
|
524
|
+
raw = git(repo, "diff-tree", "-r", "-M", "--name-status", "-z", base, commit)
|
|
525
|
+
for rel in parse_diff_paths(raw):
|
|
526
|
+
if path_in_tree(repo, pre, rel):
|
|
527
|
+
continue
|
|
528
|
+
target = os.path.abspath(os.path.join(repo, rel))
|
|
529
|
+
if os.path.commonpath([repo, target]) != repo:
|
|
530
|
+
raise Operational("BLOCKED", "transport path escaped canonical repository")
|
|
531
|
+
if os.path.islink(target) or os.path.isfile(target):
|
|
532
|
+
os.unlink(target)
|
|
533
|
+
elif os.path.isdir(target):
|
|
534
|
+
shutil.rmtree(target)
|
|
535
|
+
parent = os.path.dirname(target)
|
|
536
|
+
while parent != repo and os.path.commonpath([repo, parent]) == repo:
|
|
537
|
+
try:
|
|
538
|
+
os.rmdir(parent)
|
|
539
|
+
except OSError:
|
|
540
|
+
break
|
|
541
|
+
parent = os.path.dirname(parent)
|
|
542
|
+
|
|
543
|
+
|
|
544
|
+
def restore(run_id: str, unit_id: str, lock_token: str) -> bool:
|
|
545
|
+
with locked_manifest(run_id) as doc:
|
|
546
|
+
validate_lock(doc, unit_id, lock_token)
|
|
547
|
+
unit = doc["units"].get(unit_id)
|
|
548
|
+
if not unit or not unit["integration"].get("pre_fold"):
|
|
549
|
+
raise Operational("REFUSED", "unit has no pre-fold snapshot")
|
|
550
|
+
repo = doc["repository"]["toplevel"]
|
|
551
|
+
pre = dict(unit["integration"]["pre_fold"])
|
|
552
|
+
git_dir = git_text(repo, "rev-parse", "--path-format=absolute", "--absolute-git-dir")
|
|
553
|
+
cherry_pick_head = os.path.join(git_dir, "CHERRY_PICK_HEAD")
|
|
554
|
+
expected_conflict = False
|
|
555
|
+
if os.path.isfile(cherry_pick_head) and git_text(repo, "rev-parse", "HEAD") == pre["head"]:
|
|
556
|
+
expected_conflict = Path(cherry_pick_head).read_text().strip() == unit["transport"]["commit"]
|
|
557
|
+
current = None if expected_conflict else semantic_snapshot(repo)
|
|
558
|
+
already_exact = current == pre if current else False
|
|
559
|
+
expected_apply = matches_expected_apply(repo, unit, current) if current else False
|
|
560
|
+
partial_reset = bool(current) and (
|
|
561
|
+
unit.get("state") == "restoring"
|
|
562
|
+
and current["head"] == pre["head"]
|
|
563
|
+
and current["index_tree"] == pre["index_tree"]
|
|
564
|
+
and current["worktree_index_empty"]
|
|
565
|
+
and status_paths(repo).issubset(set(unit["integration"]["expected_apply"]["changed_paths"]))
|
|
566
|
+
)
|
|
567
|
+
if not (already_exact or expected_apply or partial_reset or expected_conflict):
|
|
568
|
+
raise Operational("BLOCKED", "canonical state is not a proven in-flight transport state; refusing destructive restoration")
|
|
569
|
+
with locked_manifest(run_id, write=True) as doc:
|
|
570
|
+
unit = doc["units"][unit_id]
|
|
571
|
+
unit["state"] = "restoring"
|
|
572
|
+
event(doc, "restore-intent", unit_id)
|
|
573
|
+
if not already_exact:
|
|
574
|
+
git(repo, "cherry-pick", "--abort", check=False)
|
|
575
|
+
git(repo, "reset", "--hard", pre["head"])
|
|
576
|
+
test_fault("restore-after-reset")
|
|
577
|
+
with locked_manifest(run_id) as doc:
|
|
578
|
+
unit = doc["units"][unit_id]
|
|
579
|
+
remove_introduced_paths(repo, unit)
|
|
580
|
+
test_fault("restore-after-path-removal")
|
|
581
|
+
actual = semantic_snapshot(repo)
|
|
582
|
+
exact = actual == pre
|
|
583
|
+
with locked_manifest(run_id, write=True) as doc:
|
|
584
|
+
unit = doc["units"][unit_id]
|
|
585
|
+
unit["integration"]["restore"] = {
|
|
586
|
+
"at": now_iso(),
|
|
587
|
+
"exact": exact,
|
|
588
|
+
"already_exact": already_exact,
|
|
589
|
+
"snapshot": actual,
|
|
590
|
+
}
|
|
591
|
+
if exact:
|
|
592
|
+
unit["state"] = "preserved"
|
|
593
|
+
event(doc, "canonical-restored", unit_id)
|
|
594
|
+
else:
|
|
595
|
+
blocker = {"at": now_iso(), "unit_id": unit_id, "reason": "exact pre-fold restoration could not be proven"}
|
|
596
|
+
doc["blockers"].append(blocker)
|
|
597
|
+
event(doc, "restore-blocked", unit_id)
|
|
598
|
+
return exact
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
def cmd_restore(args) -> tuple[str, dict]:
|
|
602
|
+
exact = restore(args.run_id, args.unit_id, args.lock_token)
|
|
603
|
+
if not exact:
|
|
604
|
+
raise Operational("BLOCKED", "exact pre-fold restoration could not be proven")
|
|
605
|
+
return "PRESERVED", {"unit_id": args.unit_id, "recovery_path": os.path.join(run_dir(args.run_id), "units", args.unit_id)}
|
|
606
|
+
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def release_lock_is_owned(doc: dict, unit_id: str, lock_token: str, lock: dict) -> bool:
|
|
611
|
+
expected = {
|
|
612
|
+
"run_id": doc["run_id"],
|
|
613
|
+
"unit_id": unit_id,
|
|
614
|
+
"repository": doc["repository"]["identity_digest"],
|
|
615
|
+
"branch_ref": doc["branch"]["ref"],
|
|
616
|
+
}
|
|
617
|
+
if any(lock.get(key) != value for key, value in expected.items()):
|
|
618
|
+
return False
|
|
619
|
+
nonce = lock.get("nonce")
|
|
620
|
+
if not isinstance(nonce, str) or not re.fullmatch(r"[0-9a-f]{48}", nonce):
|
|
621
|
+
return False
|
|
622
|
+
return nonce == lock_token
|
|
623
|
+
|
|
624
|
+
|
|
625
|
+
def integration_release(run_id: str, unit_id: str, lock_token: str) -> None:
|
|
626
|
+
with locked_manifest(run_id, write=True) as doc:
|
|
627
|
+
held = doc.get("integration_lock")
|
|
628
|
+
if not held or held.get("unit_id") != unit_id or held.get("nonce") != lock_token:
|
|
629
|
+
raise Operational("REFUSED", "integration lock token or identity mismatch")
|
|
630
|
+
unit = doc["units"].get(unit_id)
|
|
631
|
+
pre_apply = bool(
|
|
632
|
+
unit
|
|
633
|
+
and unit.get("state") == "integration-pending"
|
|
634
|
+
and not unit.get("integration", {}).get("pre_fold")
|
|
635
|
+
)
|
|
636
|
+
if not unit or (unit["state"] not in {"committed", "preserved", "cleaned", "native-completed"} and not pre_apply):
|
|
637
|
+
raise Operational("REFUSED", "integration lock releases only before preflight or after accepted completion")
|
|
638
|
+
path = held.get("path")
|
|
639
|
+
if path != integration_lock_path(doc):
|
|
640
|
+
raise Operational("BLOCKED", "manifest integration lock path changed")
|
|
641
|
+
phase = held.get("phase", "held")
|
|
642
|
+
if phase == "held":
|
|
643
|
+
validate_lock(doc, unit_id, lock_token)
|
|
644
|
+
held["phase"] = "releasing"
|
|
645
|
+
held["release_started_at"] = now_iso()
|
|
646
|
+
event(doc, "integration-lock-release-intent", unit_id)
|
|
647
|
+
elif phase != "releasing":
|
|
648
|
+
raise Operational("BLOCKED", "manifest integration claim has an unknown phase")
|
|
649
|
+
with locked_manifest(run_id, write=True) as doc:
|
|
650
|
+
held = doc.get("integration_lock")
|
|
651
|
+
if not held or held.get("unit_id") != unit_id or held.get("nonce") != lock_token or held.get("phase") != "releasing":
|
|
652
|
+
raise Operational("BLOCKED", "manifest integration claim changed")
|
|
653
|
+
if held.get("path") != path or path != integration_lock_path(doc):
|
|
654
|
+
raise Operational("BLOCKED", "manifest integration lock path changed")
|
|
655
|
+
current = None
|
|
656
|
+
try:
|
|
657
|
+
current = read_integration_lock(path)
|
|
658
|
+
except TrustFailure:
|
|
659
|
+
if os.path.lexists(path):
|
|
660
|
+
raise
|
|
661
|
+
if current is not None and release_lock_is_owned(doc, unit_id, lock_token, current):
|
|
662
|
+
try:
|
|
663
|
+
os.unlink(path)
|
|
664
|
+
except FileNotFoundError:
|
|
665
|
+
pass
|
|
666
|
+
test_fault("integration-release-after-unlink")
|
|
667
|
+
if os.path.lexists(path):
|
|
668
|
+
current = read_integration_lock(path)
|
|
669
|
+
if release_lock_is_owned(doc, unit_id, lock_token, current):
|
|
670
|
+
raise Operational("BLOCKED", "integration lock file remained after release")
|
|
671
|
+
doc["integration_lock"] = None
|
|
672
|
+
event(doc, "integration-lock-released", unit_id)
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def cmd_integration_release(args) -> tuple[str, dict]:
|
|
676
|
+
integration_release(args.run_id, args.unit_id, args.lock_token)
|
|
677
|
+
return "RELEASED", {"unit_id": args.unit_id}
|