dsh-aris-panel 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 +98 -0
- package/README_CN.md +87 -0
- package/dsh/checkout.patch.yml +38 -0
- package/dsh/client.js +634 -0
- package/dsh/cordis.patch.yml +44 -0
- package/dsh/index.mjs +76 -0
- package/dsh/run-status.mjs +182 -0
- package/dsh/scope-limits.mjs +50 -0
- package/dsh/workbench.mjs +291 -0
- package/mcp-servers/claude-review/README.md +93 -0
- package/mcp-servers/claude-review/run_with_claude_aws.sh +49 -0
- package/mcp-servers/claude-review/server.py +718 -0
- package/mcp-servers/codex-image2/README.md +65 -0
- package/mcp-servers/codex-image2/server.py +893 -0
- package/mcp-servers/feishu-bridge/requirements.txt +1 -0
- package/mcp-servers/feishu-bridge/server.py +240 -0
- package/mcp-servers/gemini-review/README.md +171 -0
- package/mcp-servers/gemini-review/server.py +1856 -0
- package/mcp-servers/llm-chat/requirements.txt +1 -0
- package/mcp-servers/llm-chat/server.py +664 -0
- package/mcp-servers/manual-review/README.md +133 -0
- package/mcp-servers/manual-review/server.py +910 -0
- package/mcp-servers/manual-review/ui.html +279 -0
- package/mcp-servers/minimax-chat/requirements.txt +1 -0
- package/mcp-servers/minimax-chat/server.py +381 -0
- package/package.json +51 -0
- package/skills/ablation-planner/SKILL.md +123 -0
- package/skills/alphaxiv/SKILL.md +196 -0
- package/skills/analyze-results/SKILL.md +46 -0
- package/skills/arxiv/SKILL.md +248 -0
- package/skills/auto-paper-improvement-loop/SKILL.md +651 -0
- package/skills/auto-review-loop/SKILL.md +1137 -0
- package/skills/auto-review-loop-llm/SKILL.md +259 -0
- package/skills/auto-review-loop-minimax/SKILL.md +302 -0
- package/skills/citation-audit/SKILL.md +502 -0
- package/skills/claims-drafting/SKILL.md +227 -0
- package/skills/comm-lit-review/SKILL.md +297 -0
- package/skills/deepxiv/SKILL.md +263 -0
- package/skills/dse-loop/SKILL.md +296 -0
- package/skills/embodiment-description/SKILL.md +129 -0
- package/skills/exa-search/SKILL.md +205 -0
- package/skills/experiment-audit/SKILL.md +311 -0
- package/skills/experiment-bridge/SKILL.md +376 -0
- package/skills/experiment-plan/SKILL.md +249 -0
- package/skills/experiment-queue/SKILL.md +431 -0
- package/skills/experiment-queue/scripts/build_manifest.py +142 -0
- package/skills/experiment-queue/scripts/queue_manager.py +433 -0
- package/skills/feishu-notify/SKILL.md +156 -0
- package/skills/figure-description/SKILL.md +138 -0
- package/skills/figure-spec/SKILL.md +262 -0
- package/skills/figure-spec/scripts/figure_renderer.py +799 -0
- package/skills/formula-derivation/SKILL.md +280 -0
- package/skills/gemini-search/SKILL.md +231 -0
- package/skills/grant-proposal/SKILL.md +698 -0
- package/skills/idea-creator/SKILL.md +542 -0
- package/skills/idea-discovery/SKILL.md +521 -0
- package/skills/idea-discovery-robot/SKILL.md +363 -0
- package/skills/integrity-forensics/SKILL.md +284 -0
- package/skills/interview-cheatsheet/SKILL.md +245 -0
- package/skills/invention-structuring/SKILL.md +188 -0
- package/skills/jurisdiction-format/SKILL.md +192 -0
- package/skills/kill-argument/SKILL.md +437 -0
- package/skills/mermaid-diagram/SKILL.md +419 -0
- package/skills/meta-apply/SKILL.md +141 -0
- package/skills/meta-optimize/SKILL.md +437 -0
- package/skills/monitor-experiment/SKILL.md +140 -0
- package/skills/novelty-check/SKILL.md +101 -0
- package/skills/openalex/SKILL.md +237 -0
- package/skills/overleaf-sync/SKILL.md +220 -0
- package/skills/paper-claim-audit/SKILL.md +348 -0
- package/skills/paper-compile/SKILL.md +266 -0
- package/skills/paper-figure/SKILL.md +312 -0
- package/skills/paper-illustration/SKILL.md +736 -0
- package/skills/paper-illustration-image2/SKILL.md +391 -0
- package/skills/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/paper-plan/SKILL.md +386 -0
- package/skills/paper-poster/SKILL.md +19 -0
- package/skills/paper-poster-html/DESIGN_FINAL.md +176 -0
- package/skills/paper-poster-html/IMPLEMENTATION_CONVENTIONS.md +161 -0
- package/skills/paper-poster-html/LICENSES/posterly-MIT.txt +21 -0
- package/skills/paper-poster-html/NOTICE.md +57 -0
- package/skills/paper-poster-html/SKILL.md +323 -0
- package/skills/paper-poster-html/scripts/_posterly/__init__.py +0 -0
- package/skills/paper-poster-html/scripts/_posterly/canvas.py +200 -0
- package/skills/paper-poster-html/scripts/_posterly/measure.py +588 -0
- package/skills/paper-poster-html/scripts/_posterly/polish.py +498 -0
- package/skills/paper-poster-html/scripts/_posterly/preflight.py +489 -0
- package/skills/paper-poster-html/scripts/_posterly/render.py +215 -0
- package/skills/paper-poster-html/scripts/_posterly/textutil.py +16 -0
- package/skills/paper-poster-html/scripts/_posterly/verify_final.py +171 -0
- package/skills/paper-poster-html/scripts/asset_check.py +897 -0
- package/skills/paper-poster-html/scripts/extract_pdf_figures.py +666 -0
- package/skills/paper-poster-html/scripts/poster_check.py +251 -0
- package/skills/paper-poster-html/scripts/preprocess_figures.py +238 -0
- package/skills/paper-poster-html/scripts/render_preview.py +217 -0
- package/skills/paper-poster-html/scripts/run_gates.py +556 -0
- package/skills/paper-poster-html/scripts/style_check.py +1324 -0
- package/skills/paper-poster-html/templates/COMPONENTS.md +462 -0
- package/skills/paper-poster-html/templates/README.md +170 -0
- package/skills/paper-poster-html/templates/landscape_4col.html +1032 -0
- package/skills/paper-poster-html/templates/landscape_hero.html +1046 -0
- package/skills/paper-poster-html/templates/portrait_2col.html +947 -0
- package/skills/paper-poster-html/templates/tokens/acl.json +9 -0
- package/skills/paper-poster-html/templates/tokens/cvpr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/generic.json +9 -0
- package/skills/paper-poster-html/templates/tokens/iclr.json +9 -0
- package/skills/paper-poster-html/templates/tokens/icml.json +9 -0
- package/skills/paper-poster-html/templates/tokens/neurips.json +9 -0
- package/skills/paper-slides/SKILL.md +635 -0
- package/skills/paper-talk/SKILL.md +381 -0
- package/skills/paper-write/SKILL.md +604 -0
- package/skills/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/paper-write/templates/iclr2026.tex +84 -0
- package/skills/paper-write/templates/icml2025.tex +87 -0
- package/skills/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/paper-write/templates/math_commands.tex +48 -0
- package/skills/paper-write/templates/neurips2025.tex +80 -0
- package/skills/paper-writing/SKILL.md +916 -0
- package/skills/patent-novelty-check/SKILL.md +153 -0
- package/skills/patent-pipeline/SKILL.md +344 -0
- package/skills/patent-review/SKILL.md +203 -0
- package/skills/pixel-art/SKILL.md +137 -0
- package/skills/prior-art-search/SKILL.md +146 -0
- package/skills/proof-checker/SKILL.md +866 -0
- package/skills/proof-orchestrator/NOTICE.md +24 -0
- package/skills/proof-orchestrator/SKILL.md +254 -0
- package/skills/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/proof-orchestrator/references/deepseek-routing.md +74 -0
- package/skills/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/proof-writer/SKILL.md +223 -0
- package/skills/qzcli/SKILL.md +324 -0
- package/skills/rebuttal/SKILL.md +376 -0
- package/skills/render-html/SKILL.md +316 -0
- package/skills/render-html/scripts/render_html.py +1006 -0
- package/skills/render-html/scripts/templates/academic.html +703 -0
- package/skills/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/research-lit/SKILL.md +756 -0
- package/skills/research-pipeline/SKILL.md +384 -0
- package/skills/research-refine/SKILL.md +770 -0
- package/skills/research-refine-pipeline/SKILL.md +186 -0
- package/skills/research-review/SKILL.md +198 -0
- package/skills/research-wiki/SKILL.md +461 -0
- package/skills/resubmit-pipeline/SKILL.md +447 -0
- package/skills/result-to-claim/SKILL.md +311 -0
- package/skills/run-experiment/SKILL.md +313 -0
- package/skills/semantic-scholar/SKILL.md +236 -0
- package/skills/serverless-modal/SKILL.md +335 -0
- package/skills/shared-references/acceptance-gate.md +324 -0
- package/skills/shared-references/assurance-contract.md +248 -0
- package/skills/shared-references/capture-antipatterns.md +78 -0
- package/skills/shared-references/citation-discipline.md +583 -0
- package/skills/shared-references/compute-env-contract.md +163 -0
- package/skills/shared-references/effort-contract.md +183 -0
- package/skills/shared-references/evidence-precheck.md +65 -0
- package/skills/shared-references/experiment-integrity.md +49 -0
- package/skills/shared-references/external-cadence.md +326 -0
- package/skills/shared-references/fan-out-pattern.md +366 -0
- package/skills/shared-references/injection-hygiene.md +127 -0
- package/skills/shared-references/integration-contract.md +461 -0
- package/skills/shared-references/output-composition.md +93 -0
- package/skills/shared-references/output-language.md +45 -0
- package/skills/shared-references/output-manifest.md +49 -0
- package/skills/shared-references/output-versioning.md +111 -0
- package/skills/shared-references/patent-format-cn.md +199 -0
- package/skills/shared-references/patent-format-ep.md +173 -0
- package/skills/shared-references/patent-format-us.md +161 -0
- package/skills/shared-references/patent-writing-principles.md +197 -0
- package/skills/shared-references/prior-art-databases.md +141 -0
- package/skills/shared-references/resumable-runs.md +109 -0
- package/skills/shared-references/review-scope-limits.md +81 -0
- package/skills/shared-references/review-tracing.md +391 -0
- package/skills/shared-references/reviewer-independence.md +79 -0
- package/skills/shared-references/reviewer-routing.md +852 -0
- package/skills/shared-references/skill-governance.md +104 -0
- package/skills/shared-references/taste-calibration.md +85 -0
- package/skills/shared-references/venue-checklists.md +114 -0
- package/skills/shared-references/wiki-helper-resolution.md +134 -0
- package/skills/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/README.md +102 -0
- package/skills/skills-codex/README_CN.md +100 -0
- package/skills/skills-codex/ablation-planner/SKILL.md +126 -0
- package/skills/skills-codex/alphaxiv/SKILL.md +186 -0
- package/skills/skills-codex/analyze-results/SKILL.md +45 -0
- package/skills/skills-codex/arxiv/SKILL.md +210 -0
- package/skills/skills-codex/auto-paper-improvement-loop/SKILL.md +574 -0
- package/skills/skills-codex/auto-review-loop/SKILL.md +500 -0
- package/skills/skills-codex/auto-review-loop-llm/SKILL.md +247 -0
- package/skills/skills-codex/auto-review-loop-minimax/SKILL.md +290 -0
- package/skills/skills-codex/citation-audit/SKILL.md +504 -0
- package/skills/skills-codex/claims-drafting/SKILL.md +239 -0
- package/skills/skills-codex/comm-lit-review/SKILL.md +299 -0
- package/skills/skills-codex/comm-lit-review/references/domain-taxonomy.md +57 -0
- package/skills/skills-codex/comm-lit-review/references/output-template.md +37 -0
- package/skills/skills-codex/comm-lit-review/references/source-policy.md +99 -0
- package/skills/skills-codex/comm-lit-review/references/venue-tiering.md +112 -0
- package/skills/skills-codex/deepxiv/SKILL.md +142 -0
- package/skills/skills-codex/dse-loop/SKILL.md +285 -0
- package/skills/skills-codex/embodiment-description/SKILL.md +129 -0
- package/skills/skills-codex/exa-search/SKILL.md +192 -0
- package/skills/skills-codex/experiment-audit/SKILL.md +286 -0
- package/skills/skills-codex/experiment-bridge/SKILL.md +356 -0
- package/skills/skills-codex/experiment-plan/SKILL.md +249 -0
- package/skills/skills-codex/experiment-queue/SKILL.md +401 -0
- package/skills/skills-codex/feishu-notify/SKILL.md +155 -0
- package/skills/skills-codex/figure-description/SKILL.md +138 -0
- package/skills/skills-codex/figure-spec/SKILL.md +252 -0
- package/skills/skills-codex/formula-derivation/SKILL.md +280 -0
- package/skills/skills-codex/gemini-search/SKILL.md +205 -0
- package/skills/skills-codex/grant-proposal/SKILL.md +626 -0
- package/skills/skills-codex/idea-creator/SKILL.md +405 -0
- package/skills/skills-codex/idea-discovery/SKILL.md +475 -0
- package/skills/skills-codex/idea-discovery-robot/SKILL.md +362 -0
- package/skills/skills-codex/integrity-forensics/SKILL.md +106 -0
- package/skills/skills-codex/interview-cheatsheet/SKILL.md +245 -0
- package/skills/skills-codex/invention-structuring/SKILL.md +188 -0
- package/skills/skills-codex/jurisdiction-format/SKILL.md +192 -0
- package/skills/skills-codex/kill-argument/SKILL.md +403 -0
- package/skills/skills-codex/mermaid-diagram/SKILL.md +379 -0
- package/skills/skills-codex/meta-apply/SKILL.md +154 -0
- package/skills/skills-codex/meta-optimize/SKILL.md +348 -0
- package/skills/skills-codex/monitor-experiment/SKILL.md +98 -0
- package/skills/skills-codex/novelty-check/SKILL.md +89 -0
- package/skills/skills-codex/openalex/SKILL.md +228 -0
- package/skills/skills-codex/overleaf-sync/SKILL.md +220 -0
- package/skills/skills-codex/paper-claim-audit/SKILL.md +350 -0
- package/skills/skills-codex/paper-compile/SKILL.md +253 -0
- package/skills/skills-codex/paper-figure/SKILL.md +311 -0
- package/skills/skills-codex/paper-illustration/SKILL.md +690 -0
- package/skills/skills-codex/paper-illustration-image2/SKILL.md +383 -0
- package/skills/skills-codex/paper-illustration-image2/scripts/paper_illustration_image2.py +255 -0
- package/skills/skills-codex/paper-plan/SKILL.md +278 -0
- package/skills/skills-codex/paper-poster/SKILL.md +19 -0
- package/skills/skills-codex/paper-poster-html/SKILL.md +377 -0
- package/skills/skills-codex/paper-slides/SKILL.md +571 -0
- package/skills/skills-codex/paper-talk/SKILL.md +381 -0
- package/skills/skills-codex/paper-write/SKILL.md +411 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.bst +2409 -0
- package/skills/skills-codex/paper-write/templates/IEEEtran.cls +6347 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.bst +1493 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.sty +315 -0
- package/skills/skills-codex/paper-write/templates/aaai2026.tex +952 -0
- package/skills/skills-codex/paper-write/templates/acl.sty +312 -0
- package/skills/skills-codex/paper-write/templates/acl2026.tex +377 -0
- package/skills/skills-codex/paper-write/templates/acl_natbib.bst +1940 -0
- package/skills/skills-codex/paper-write/templates/acm.bst +3081 -0
- package/skills/skills-codex/paper-write/templates/acm_mm2026.tex +204 -0
- package/skills/skills-codex/paper-write/templates/acmart.cls +3520 -0
- package/skills/skills-codex/paper-write/templates/cvpr.bst +1448 -0
- package/skills/skills-codex/paper-write/templates/cvpr.sty +508 -0
- package/skills/skills-codex/paper-write/templates/cvpr2026.tex +63 -0
- package/skills/skills-codex/paper-write/templates/iclr2026.tex +84 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.bst +1440 -0
- package/skills/skills-codex/paper-write/templates/iclr2026_conference.sty +246 -0
- package/skills/skills-codex/paper-write/templates/icml2026.sty +767 -0
- package/skills/skills-codex/paper-write/templates/icml2026.tex +662 -0
- package/skills/skills-codex/paper-write/templates/ieee_conference.tex +89 -0
- package/skills/skills-codex/paper-write/templates/ieee_journal.tex +93 -0
- package/skills/skills-codex/paper-write/templates/math_commands.tex +48 -0
- package/skills/skills-codex/paper-write/templates/neurips2026.tex +493 -0
- package/skills/skills-codex/paper-write/templates/neurips_2026.sty +437 -0
- package/skills/skills-codex/paper-writing/SKILL.md +731 -0
- package/skills/skills-codex/patent-novelty-check/SKILL.md +153 -0
- package/skills/skills-codex/patent-pipeline/SKILL.md +344 -0
- package/skills/skills-codex/patent-review/SKILL.md +202 -0
- package/skills/skills-codex/pixel-art/SKILL.md +139 -0
- package/skills/skills-codex/prior-art-search/SKILL.md +146 -0
- package/skills/skills-codex/proof-checker/SKILL.md +554 -0
- package/skills/skills-codex/proof-orchestrator/SKILL.md +260 -0
- package/skills/skills-codex/proof-orchestrator/references/audit-output-contract.md +126 -0
- package/skills/skills-codex/proof-orchestrator/references/deepseek-routing.md +76 -0
- package/skills/skills-codex/proof-orchestrator/references/dispatch-prompts.md +227 -0
- package/skills/skills-codex/proof-orchestrator/references/notation-audit.md +135 -0
- package/skills/skills-codex/proof-orchestrator/references/proof-audit-rubric.md +70 -0
- package/skills/skills-codex/proof-orchestrator/references/stress-tests.md +38 -0
- package/skills/skills-codex/proof-writer/SKILL.md +222 -0
- package/skills/skills-codex/qzcli/SKILL.md +324 -0
- package/skills/skills-codex/rebuttal/SKILL.md +305 -0
- package/skills/skills-codex/render-html/SKILL.md +305 -0
- package/skills/skills-codex/render-html/scripts/__pycache__/render_html.cpython-314.pyc +0 -0
- package/skills/skills-codex/render-html/scripts/render_html.py +909 -0
- package/skills/skills-codex/render-html/scripts/templates/academic.html +342 -0
- package/skills/skills-codex/render-html/scripts/templates/dashboard.html +333 -0
- package/skills/skills-codex/research-lit/SKILL.md +464 -0
- package/skills/skills-codex/research-pipeline/SKILL.md +340 -0
- package/skills/skills-codex/research-refine/SKILL.md +721 -0
- package/skills/skills-codex/research-refine-pipeline/SKILL.md +186 -0
- package/skills/skills-codex/research-review/SKILL.md +135 -0
- package/skills/skills-codex/research-wiki/SKILL.md +421 -0
- package/skills/skills-codex/resubmit-pipeline/SKILL.md +444 -0
- package/skills/skills-codex/result-to-claim/SKILL.md +246 -0
- package/skills/skills-codex/run-experiment/SKILL.md +236 -0
- package/skills/skills-codex/semantic-scholar/SKILL.md +219 -0
- package/skills/skills-codex/serverless-modal/SKILL.md +335 -0
- package/skills/skills-codex/shared-references/acceptance-gate.md +336 -0
- package/skills/skills-codex/shared-references/assurance-contract.md +139 -0
- package/skills/skills-codex/shared-references/capture-antipatterns.md +84 -0
- package/skills/skills-codex/shared-references/citation-discipline.md +452 -0
- package/skills/skills-codex/shared-references/compute-env-contract.md +163 -0
- package/skills/skills-codex/shared-references/effort-contract.md +143 -0
- package/skills/skills-codex/shared-references/evidence-precheck.md +73 -0
- package/skills/skills-codex/shared-references/experiment-integrity.md +49 -0
- package/skills/skills-codex/shared-references/external-cadence.md +334 -0
- package/skills/skills-codex/shared-references/fan-out-pattern.md +375 -0
- package/skills/skills-codex/shared-references/injection-hygiene.md +132 -0
- package/skills/skills-codex/shared-references/integration-contract.md +372 -0
- package/skills/skills-codex/shared-references/output-composition.md +98 -0
- package/skills/skills-codex/shared-references/output-language.md +45 -0
- package/skills/skills-codex/shared-references/output-manifest.md +40 -0
- package/skills/skills-codex/shared-references/output-versioning.md +111 -0
- package/skills/skills-codex/shared-references/patent-format-cn.md +199 -0
- package/skills/skills-codex/shared-references/patent-format-ep.md +173 -0
- package/skills/skills-codex/shared-references/patent-format-us.md +161 -0
- package/skills/skills-codex/shared-references/patent-writing-principles.md +197 -0
- package/skills/skills-codex/shared-references/prior-art-databases.md +141 -0
- package/skills/skills-codex/shared-references/resumable-runs.md +125 -0
- package/skills/skills-codex/shared-references/review-scope-limits.md +81 -0
- package/skills/skills-codex/shared-references/review-tracing.md +144 -0
- package/skills/skills-codex/shared-references/reviewer-independence.md +66 -0
- package/skills/skills-codex/shared-references/reviewer-routing.md +128 -0
- package/skills/skills-codex/shared-references/skill-governance.md +119 -0
- package/skills/skills-codex/shared-references/taste-calibration.md +90 -0
- package/skills/skills-codex/shared-references/venue-checklists.md +73 -0
- package/skills/skills-codex/shared-references/wiki-helper-resolution.md +69 -0
- package/skills/skills-codex/shared-references/writing-principles.md +525 -0
- package/skills/skills-codex/slides-polish/SKILL.md +563 -0
- package/skills/skills-codex/specification-writing/SKILL.md +211 -0
- package/skills/skills-codex/system-profile/SKILL.md +103 -0
- package/skills/skills-codex/training-check/SKILL.md +83 -0
- package/skills/skills-codex/vast-gpu/SKILL.md +394 -0
- package/skills/skills-codex/web-debug-search/SKILL.md +334 -0
- package/skills/skills-codex/wiki-enrich/SKILL.md +255 -0
- package/skills/skills-codex/writing-systems-papers/SKILL.md +184 -0
- package/skills/skills-codex-claude-review/README.md +79 -0
- package/skills/skills-codex-claude-review/README_CN.md +78 -0
- package/skills/skills-codex-claude-review/auto-paper-improvement-loop/SKILL.md +581 -0
- package/skills/skills-codex-claude-review/auto-review-loop/SKILL.md +510 -0
- package/skills/skills-codex-claude-review/novelty-check/SKILL.md +102 -0
- package/skills/skills-codex-claude-review/paper-figure/SKILL.md +319 -0
- package/skills/skills-codex-claude-review/paper-plan/SKILL.md +287 -0
- package/skills/skills-codex-claude-review/paper-write/SKILL.md +420 -0
- package/skills/skills-codex-claude-review/research-refine/SKILL.md +732 -0
- package/skills/skills-codex-claude-review/research-review/SKILL.md +149 -0
- package/skills/skills-codex-gemini-review/README.md +176 -0
- package/skills/skills-codex-gemini-review/README_CN.md +175 -0
- package/skills/skills-codex-gemini-review/auto-paper-improvement-loop/SKILL.md +331 -0
- package/skills/skills-codex-gemini-review/auto-review-loop/SKILL.md +304 -0
- package/skills/skills-codex-gemini-review/grant-proposal/SKILL.md +630 -0
- package/skills/skills-codex-gemini-review/idea-creator/SKILL.md +263 -0
- package/skills/skills-codex-gemini-review/idea-discovery/SKILL.md +275 -0
- package/skills/skills-codex-gemini-review/idea-discovery-robot/SKILL.md +365 -0
- package/skills/skills-codex-gemini-review/novelty-check/SKILL.md +92 -0
- package/skills/skills-codex-gemini-review/paper-figure/SKILL.md +289 -0
- package/skills/skills-codex-gemini-review/paper-plan/SKILL.md +265 -0
- package/skills/skills-codex-gemini-review/paper-poster-html/SKILL.md +102 -0
- package/skills/skills-codex-gemini-review/paper-slides/SKILL.md +582 -0
- package/skills/skills-codex-gemini-review/paper-write/SKILL.md +346 -0
- package/skills/skills-codex-gemini-review/paper-writing/SKILL.md +312 -0
- package/skills/skills-codex-gemini-review/research-refine/SKILL.md +674 -0
- package/skills/skills-codex-gemini-review/research-review/SKILL.md +112 -0
- package/skills/slides-polish/SKILL.md +565 -0
- package/skills/specification-writing/SKILL.md +211 -0
- package/skills/system-profile/SKILL.md +103 -0
- package/skills/training-check/SKILL.md +132 -0
- package/skills/vast-gpu/SKILL.md +394 -0
- package/skills/web-debug-search/SKILL.md +334 -0
- package/skills/wiki-enrich/SKILL.md +257 -0
- package/skills/writing-systems-papers/SKILL.md +184 -0
- package/templates/CLAUDE_MD_TEMPLATE.md +29 -0
- package/templates/EXPERIMENT_LOG_TEMPLATE.md +47 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE.md +51 -0
- package/templates/EXPERIMENT_PLAN_TEMPLATE_CN.md +53 -0
- package/templates/FINDINGS_TEMPLATE.md +52 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE.md +47 -0
- package/templates/IDEA_CANDIDATES_TEMPLATE_CN.md +47 -0
- package/templates/INVENTION_BRIEF_TEMPLATE.md +87 -0
- package/templates/MANIFEST_TEMPLATE.md +7 -0
- package/templates/NARRATIVE_REPORT_TEMPLATE.md +49 -0
- package/templates/PAPER_PLAN_TEMPLATE.md +47 -0
- package/templates/PATENT_CLAIMS_TEMPLATE.md +78 -0
- package/templates/PATENT_SPECIFICATION_TEMPLATE.md +68 -0
- package/templates/README.md +57 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE.md +35 -0
- package/templates/RESEARCH_BRIEF_TEMPLATE_CN.md +41 -0
- package/templates/RESEARCH_CONTRACT_TEMPLATE.md +60 -0
- package/templates/claude-hooks/corpus_write_guard.json +16 -0
- package/templates/claude-hooks/corpus_write_guard.py +85 -0
- package/templates/claude-hooks/meta_logging.json +74 -0
- package/templates/gitignore-trace.txt +3 -0
- package/tools/__pycache__/check_skills_inventory.cpython-314.pyc +0 -0
- package/tools/arxiv_fetch.py +311 -0
- package/tools/capture_filter.py +126 -0
- package/tools/check_skills_inventory.py +273 -0
- package/tools/convert_skills_to_llm_chat.py +282 -0
- package/tools/copilot_native_evidence.py +818 -0
- package/tools/deepxiv_fetch.py +213 -0
- package/tools/evidence_check.py +212 -0
- package/tools/exa_search.py +425 -0
- package/tools/experiment_queue/README.md +118 -0
- package/tools/experiment_queue/build_manifest.py +44 -0
- package/tools/experiment_queue/queue_manager.py +44 -0
- package/tools/extract_paper_style.py +560 -0
- package/tools/figure_renderer.py +69 -0
- package/tools/forensics_gate.py +669 -0
- package/tools/generate_codex_claude_review_overrides.py +299 -0
- package/tools/idea_discovery_gate.py +256 -0
- package/tools/install_aris.ps1 +1372 -0
- package/tools/install_aris.sh +1370 -0
- package/tools/install_aris_codex.sh +1023 -0
- package/tools/install_aris_copilot.sh +1052 -0
- package/tools/iteration_log.py +143 -0
- package/tools/lint_skills_helpers.sh +84 -0
- package/tools/meta_opt/check_ready.sh +80 -0
- package/tools/meta_opt/log_event.sh +91 -0
- package/tools/meta_opt/trigger_eval.py +280 -0
- package/tools/meta_opt/trigger_evals.sample.json +28 -0
- package/tools/openalex_fetch.py +326 -0
- package/tools/overleaf_audit.sh +104 -0
- package/tools/overleaf_setup.sh +150 -0
- package/tools/paper_illustration_image2.py +62 -0
- package/tools/provenance.py +294 -0
- package/tools/research_wiki.py +1720 -0
- package/tools/review_gate.py +502 -0
- package/tools/run_state.py +399 -0
- package/tools/save_trace.sh +477 -0
- package/tools/semantic_scholar_fetch.py +438 -0
- package/tools/skill-groups.tsv +116 -0
- package/tools/skill_picker.py +238 -0
- package/tools/smart_update.ps1 +521 -0
- package/tools/smart_update.sh +591 -0
- package/tools/smart_update_codex.sh +419 -0
- package/tools/smart_update_copilot.sh +605 -0
- package/tools/threat_scan.py +222 -0
- package/tools/verify_paper_audits.sh +487 -0
- package/tools/verify_papers.py +613 -0
- package/tools/verify_wiki_coverage.sh +176 -0
- package/tools/watchdog.py +485 -0
|
@@ -0,0 +1,669 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
forensics_gate.py — typed policy gate + append-only obligations ledger for the
|
|
4
|
+
/integrity-forensics launcher (Anti-Autoresearch integration).
|
|
5
|
+
|
|
6
|
+
Doctrine (cross-model design review, 2026-07-12):
|
|
7
|
+
|
|
8
|
+
- Anti-Autoresearch's verdict is preserved VERBATIM and never re-labeled. In
|
|
9
|
+
particular `CLEAN_GIVEN_EVIDENCE` maps to `NO_NEW_BLOCKER` — "no flag found in
|
|
10
|
+
the evidence at hand" — NEVER to PASS/accepted. A forensics sweep can raise
|
|
11
|
+
flags; it cannot acquit anything (flags are computable, acquittals are not).
|
|
12
|
+
- Findings become APPEND-ONLY obligations. A re-run may add obligations; a
|
|
13
|
+
finding that disappears from a later report NEVER auto-closes its obligation
|
|
14
|
+
(an LLM asked to "make the flag go away" learns to reword the span faster
|
|
15
|
+
than to fix the number). A vanished, unresolved obligation is marked
|
|
16
|
+
`UNRESOLVED_DISAPPEARANCE` — deterministically recorded, not an accusation.
|
|
17
|
+
- Closure is an explicit, evidence-bearing act: `resolve` (typed fix + evidence
|
|
18
|
+
file hashed at closure time + who verified) or `waive` (human sign-off; a
|
|
19
|
+
waiver is never a resolution and never rewrites the original finding).
|
|
20
|
+
|
|
21
|
+
Gate policy (fixed):
|
|
22
|
+
upstream HARD_FLAGS → BLOCK
|
|
23
|
+
upstream REVIEW_UNAVAILABLE → BLOCK (an incomplete sweep cannot wave a paper through)
|
|
24
|
+
any OPEN critical obligation → BLOCK
|
|
25
|
+
upstream SOFT_FLAGS → WARN (human disposition)
|
|
26
|
+
any OPEN obligation → WARN
|
|
27
|
+
otherwise → NO_NEW_BLOCKER
|
|
28
|
+
|
|
29
|
+
Pure stdlib. Artifacts:
|
|
30
|
+
<paper>/.aris/forensics/gate.json (one per run; overwritten)
|
|
31
|
+
<paper>/.aris/forensics/obligations.json (append-only ledger; never pruned)
|
|
32
|
+
"""
|
|
33
|
+
import argparse
|
|
34
|
+
import contextlib
|
|
35
|
+
import hashlib
|
|
36
|
+
import json
|
|
37
|
+
import os
|
|
38
|
+
import re
|
|
39
|
+
import sys
|
|
40
|
+
import tempfile
|
|
41
|
+
from datetime import datetime, timezone
|
|
42
|
+
|
|
43
|
+
try:
|
|
44
|
+
import fcntl # POSIX
|
|
45
|
+
except ImportError: # pragma: no cover
|
|
46
|
+
fcntl = None
|
|
47
|
+
|
|
48
|
+
GATE_VERSION = "1"
|
|
49
|
+
AUDITOR_FAMILY = "openai" # Anti-AR's reviewer pins are GPT-family by upstream contract
|
|
50
|
+
|
|
51
|
+
BLOCK = "BLOCK"
|
|
52
|
+
WARN = "WARN"
|
|
53
|
+
NO_NEW_BLOCKER = "NO_NEW_BLOCKER"
|
|
54
|
+
|
|
55
|
+
FIX_TYPES = ("corrected-from-results", "claim-narrowed", "claim-withdrawn",
|
|
56
|
+
"citation-replaced")
|
|
57
|
+
|
|
58
|
+
_FAMILY_NEEDLES = [
|
|
59
|
+
("anthropic", ("claude", "opus", "sonnet", "haiku")),
|
|
60
|
+
("openai", ("gpt", "codex", "oracle", "chatgpt", "o1", "o3", "o4")),
|
|
61
|
+
("google", ("gemini", "palm", "bard")),
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _now():
|
|
66
|
+
return datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _sha256_file(path):
|
|
70
|
+
h = hashlib.sha256()
|
|
71
|
+
with open(path, "rb") as fh:
|
|
72
|
+
for chunk in iter(lambda: fh.read(65536), b""):
|
|
73
|
+
h.update(chunk)
|
|
74
|
+
return h.hexdigest()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _norm_ws(s):
|
|
78
|
+
return re.sub(r"\s+", " ", (s or "")).strip()
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _executor_family(name):
|
|
82
|
+
n = (name or "").strip().lower()
|
|
83
|
+
hits = {fam for fam, needles in _FAMILY_NEEDLES if any(x in n for x in needles)}
|
|
84
|
+
return next(iter(hits)) if len(hits) == 1 else "unknown"
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def _severity(f):
|
|
88
|
+
return f.get("_severity_final") or f.get("severity") or "info"
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _is_obligation_bearing(f):
|
|
92
|
+
"""Weight-1, above-info findings become obligations. Zero-weight tracks
|
|
93
|
+
(AIS/advisory) inform; they never gate."""
|
|
94
|
+
if f.get("_verdict_weight", 1) != 1:
|
|
95
|
+
return False
|
|
96
|
+
return _severity(f) in ("critical", "major", "minor")
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def fingerprint(f):
|
|
100
|
+
"""Stable identity of a finding ACROSS re-runs. Deliberately excludes:
|
|
101
|
+
finding_id (F001... is positional), claim_id (positional in the ledger),
|
|
102
|
+
and artifact_hash (upstream hashes the WHOLE source file, so any unrelated
|
|
103
|
+
edit to the same .tex would re-identify every finding in it — duplicating
|
|
104
|
+
obligations on honest revisions). Identity = which auditor, which pattern,
|
|
105
|
+
the verbatim evidence spans (whitespace-normalized), and their file paths;
|
|
106
|
+
artifact hashes remain recorded on each observation as provenance."""
|
|
107
|
+
ev = sorted(
|
|
108
|
+
(_norm_ws(e.get("span")),
|
|
109
|
+
os.path.normpath((e.get("location") or {}).get("file") or ""))
|
|
110
|
+
for e in (f.get("evidence") or []) if isinstance(e, dict)
|
|
111
|
+
)
|
|
112
|
+
basis = json.dumps({"skill": f.get("skill"), "pattern_id": f.get("pattern_id"),
|
|
113
|
+
"evidence": ev}, sort_keys=True, ensure_ascii=False)
|
|
114
|
+
return hashlib.sha256(basis.encode("utf-8")).hexdigest()[:24]
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def _forensics_dir(paper_dir):
|
|
118
|
+
d = os.path.join(paper_dir, ".aris", "forensics")
|
|
119
|
+
os.makedirs(d, exist_ok=True)
|
|
120
|
+
return d
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
# The compile-input closure PLUS the compiled deliverable PLUS tabular data
|
|
124
|
+
# sources (pgfplots/tables read .csv/.dat at compile time). Numbers moved
|
|
125
|
+
# into a .sty macro, a regenerated figure, an edited data file, or a rebuilt
|
|
126
|
+
# PDF after the sweep must all read as STALE — the sweep audited none of
|
|
127
|
+
# them. .json is deliberately NOT fingerprinted: the sweep's own report.json
|
|
128
|
+
# (and re-run siblings) live in the paper dir and would self-trip the
|
|
129
|
+
# staleness guard; JSON results feeding the paper are covered by resolution
|
|
130
|
+
# evidence re-hashing instead.
|
|
131
|
+
_FINGERPRINT_EXTS = (".tex", ".bib", ".sty", ".cls", ".bst", ".inc", ".def",
|
|
132
|
+
".tikz", ".pgf", ".pdf", ".png", ".jpg", ".jpeg", ".eps",
|
|
133
|
+
".svg", ".csv", ".tsv", ".dat")
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def _paper_fingerprint(paper_dir):
|
|
137
|
+
"""sha256 over the paper's compile inputs + deliverables (sorted relpaths,
|
|
138
|
+
dot-dirs like .aris/.git excluded, symlinked dirs followed) — binds a gate
|
|
139
|
+
artifact to the content it audited, so a paper edited (or recompiled)
|
|
140
|
+
AFTER a clean sweep reads as STALE (via the `fresh` subcommand) instead of
|
|
141
|
+
inheriting the old gate."""
|
|
142
|
+
entries = []
|
|
143
|
+
for root, dirs, files in os.walk(paper_dir, followlinks=True):
|
|
144
|
+
dirs[:] = sorted(x for x in dirs if not x.startswith("."))
|
|
145
|
+
for fn in files:
|
|
146
|
+
if fn.lower().endswith(_FINGERPRINT_EXTS):
|
|
147
|
+
fp = os.path.join(root, fn)
|
|
148
|
+
entries.append((os.path.relpath(fp, paper_dir), _sha256_file(fp)))
|
|
149
|
+
h = hashlib.sha256()
|
|
150
|
+
for rel, sha in sorted(entries):
|
|
151
|
+
h.update("{}\0{}\n".format(rel, sha).encode("utf-8"))
|
|
152
|
+
return h.hexdigest()
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
def _assert_report_not_stale(report_path, paper_dir):
|
|
156
|
+
"""A report older than any audited paper file was generated BEFORE those
|
|
157
|
+
edits — folding or gating it would bind new text to a sweep that never saw
|
|
158
|
+
it. Refuse (fail closed); re-run the sweep instead. NOTE this is an mtime
|
|
159
|
+
net for the honest resumed-run case, not proof of provenance. That is where
|
|
160
|
+
it stops on purpose: this is a research-workflow gate, not a provenance
|
|
161
|
+
system, and upstream deliberately removed its report fingerprints in 2026-08
|
|
162
|
+
because nothing consumed them."""
|
|
163
|
+
rep_mtime = os.path.getmtime(report_path)
|
|
164
|
+
for root, dirs, files in os.walk(paper_dir, followlinks=True):
|
|
165
|
+
dirs[:] = [x for x in dirs if not x.startswith(".")]
|
|
166
|
+
for fn in files:
|
|
167
|
+
fp = os.path.join(root, fn)
|
|
168
|
+
if not fn.lower().endswith(_FINGERPRINT_EXTS):
|
|
169
|
+
continue
|
|
170
|
+
if os.path.samefile(fp, report_path):
|
|
171
|
+
continue # the report itself may be fingerprinted (.json)
|
|
172
|
+
if os.path.getmtime(fp) > rep_mtime:
|
|
173
|
+
raise SystemExit(f"FATAL: {fp} was modified AFTER the report — "
|
|
174
|
+
"stale report; re-run the sweep")
|
|
175
|
+
|
|
176
|
+
|
|
177
|
+
SEV_RANK = {"minor": 1, "major": 2, "critical": 3}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
@contextlib.contextmanager
|
|
181
|
+
def _ledger_lock(paper_dir):
|
|
182
|
+
"""One lock around every load→mutate→save transaction — concurrent
|
|
183
|
+
update/resolve/waive must never lose records (append-only is a promise)."""
|
|
184
|
+
lock_path = os.path.join(_forensics_dir(paper_dir), ".ledger.lock")
|
|
185
|
+
fh = open(lock_path, "w")
|
|
186
|
+
try:
|
|
187
|
+
if fcntl is not None:
|
|
188
|
+
fcntl.flock(fh, fcntl.LOCK_EX)
|
|
189
|
+
yield
|
|
190
|
+
finally:
|
|
191
|
+
if fcntl is not None:
|
|
192
|
+
fcntl.flock(fh, fcntl.LOCK_UN)
|
|
193
|
+
fh.close()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _load_ledger(path):
|
|
197
|
+
if os.path.isfile(path):
|
|
198
|
+
with open(path, encoding="utf-8") as fh:
|
|
199
|
+
data = json.load(fh)
|
|
200
|
+
if not isinstance(data, dict) or not isinstance(data.get("obligations"), list):
|
|
201
|
+
raise SystemExit(f"FATAL: {path} is not an obligations ledger")
|
|
202
|
+
for i, o in enumerate(data["obligations"]):
|
|
203
|
+
if not isinstance(o, dict) or not isinstance(o.get("obligation_id"), str):
|
|
204
|
+
raise SystemExit(f"FATAL: {path} obligations[{i}] is malformed")
|
|
205
|
+
return data
|
|
206
|
+
return {"ledger_version": "1", "obligations": []}
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
def _save_ledger(path, data):
|
|
210
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(path), suffix=".tmp")
|
|
211
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
212
|
+
json.dump(data, fh, indent=2, ensure_ascii=False)
|
|
213
|
+
os.replace(tmp, path)
|
|
214
|
+
# EVERY ledger mutation invalidates the standing gate: a gate computed
|
|
215
|
+
# against the previous ledger state must never survive it (`evaluate`
|
|
216
|
+
# rewrites gate.json right after; a crash in between leaves NO gate —
|
|
217
|
+
# which fails closed — rather than a stale pass).
|
|
218
|
+
with contextlib.suppress(FileNotFoundError):
|
|
219
|
+
os.remove(os.path.join(os.path.dirname(path), "gate.json"))
|
|
220
|
+
|
|
221
|
+
|
|
222
|
+
def _load_report_strict(path):
|
|
223
|
+
"""A report that cannot be parsed into the expected shape must fail CLOSED —
|
|
224
|
+
never fall through to a permissive gate."""
|
|
225
|
+
try:
|
|
226
|
+
with open(path, encoding="utf-8") as fh:
|
|
227
|
+
report = json.load(fh)
|
|
228
|
+
except Exception as e:
|
|
229
|
+
raise SystemExit(f"FATAL: cannot parse report {path}: {type(e).__name__}")
|
|
230
|
+
if not isinstance(report, dict):
|
|
231
|
+
raise SystemExit(f"FATAL: report {path} is not a JSON object")
|
|
232
|
+
findings = report.get("findings")
|
|
233
|
+
if findings is None:
|
|
234
|
+
findings = []
|
|
235
|
+
if not isinstance(findings, list):
|
|
236
|
+
raise SystemExit(f"FATAL: report {path} has a non-list findings field")
|
|
237
|
+
if not isinstance(report.get("overall_verdict", ""), str):
|
|
238
|
+
raise SystemExit(f"FATAL: report {path} has a non-string overall_verdict")
|
|
239
|
+
# Structural provenance floor: every real Anti-AR report names its
|
|
240
|
+
# adjudicator and carries a coverage map. A bare {verdict, findings} stub
|
|
241
|
+
# is not a report this gate will speak for. (This raises the bar for a
|
|
242
|
+
# skipped-sweep shortcut; it is NOT cryptographic provenance — see the
|
|
243
|
+
# launcher's Trust boundary note.)
|
|
244
|
+
if not (isinstance(report.get("adjudicator"), str) and report["adjudicator"].strip()):
|
|
245
|
+
raise SystemExit(f"FATAL: report {path} names no adjudicator — not an "
|
|
246
|
+
"Anti-AR report; run the pinned sweep")
|
|
247
|
+
if not isinstance(report.get("coverage"), dict):
|
|
248
|
+
raise SystemExit(f"FATAL: report {path} carries no coverage map — not an "
|
|
249
|
+
"Anti-AR report; run the pinned sweep")
|
|
250
|
+
for i, f in enumerate(findings):
|
|
251
|
+
# Schema drift fails CLOSED: a finding this tool cannot classify must
|
|
252
|
+
# stop the gate, not silently lose its obligation (e.g. a critical
|
|
253
|
+
# finding whose _verdict_weight arrives as the STRING "1" would
|
|
254
|
+
# otherwise read as non-weight-1 and never gate).
|
|
255
|
+
if not isinstance(f, dict):
|
|
256
|
+
raise SystemExit(f"FATAL: report {path} findings[{i}] is not an object")
|
|
257
|
+
w = f.get("_verdict_weight", 1)
|
|
258
|
+
if isinstance(w, bool) or not isinstance(w, int) or w not in (0, 1):
|
|
259
|
+
raise SystemExit(f"FATAL: report {path} findings[{i}] has _verdict_weight "
|
|
260
|
+
f"{w!r} (must be 0 or 1) — schema drift; see Pin-bump checklist")
|
|
261
|
+
for key in ("severity", "_severity_final"):
|
|
262
|
+
v = f.get(key)
|
|
263
|
+
if v is not None and v not in ("critical", "major", "minor", "info"):
|
|
264
|
+
raise SystemExit(f"FATAL: report {path} findings[{i}] has unknown "
|
|
265
|
+
f"{key} {v!r} — schema drift; see Pin-bump checklist")
|
|
266
|
+
if f.get("severity") is None and f.get("_severity_final") is None:
|
|
267
|
+
raise SystemExit(f"FATAL: report {path} findings[{i}] carries no severity "
|
|
268
|
+
"at all — schema drift; see Pin-bump checklist")
|
|
269
|
+
report["findings"] = findings
|
|
270
|
+
return report
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def cmd_update(args):
|
|
274
|
+
"""Fold a fresh report into the ledger. APPEND-ONLY: new findings open new
|
|
275
|
+
obligations; existing ones are untouched except for two escalations that can
|
|
276
|
+
only move TOWARD caution — severity ratchets up to the historical max, and a
|
|
277
|
+
RESOLVED obligation whose finding RECURS re-opens (the fix evidently didn't
|
|
278
|
+
hold; the old resolution is archived, never erased). OPEN obligations whose
|
|
279
|
+
fingerprint is absent from this report gain an UNRESOLVED_DISAPPEARANCE note
|
|
280
|
+
(they stay OPEN — disappearance is not resolution). WAIVED stays closed
|
|
281
|
+
(a human already dispositioned it)."""
|
|
282
|
+
report = _load_report_strict(args.report)
|
|
283
|
+
report_sha = _sha256_file(args.report)
|
|
284
|
+
_assert_report_not_stale(args.report, args.paper_dir)
|
|
285
|
+
path = os.path.join(_forensics_dir(args.paper_dir), "obligations.json")
|
|
286
|
+
ledger = _load_ledger(path)
|
|
287
|
+
by_id = {o["obligation_id"]: o for o in ledger["obligations"]}
|
|
288
|
+
|
|
289
|
+
current = {}
|
|
290
|
+
for f in report["findings"]:
|
|
291
|
+
if _is_obligation_bearing(f):
|
|
292
|
+
current[fingerprint(f)] = f
|
|
293
|
+
|
|
294
|
+
opened = reopened = 0
|
|
295
|
+
for fid, f in current.items():
|
|
296
|
+
if fid in by_id:
|
|
297
|
+
o = by_id[fid]
|
|
298
|
+
o["last_seen_report"] = report_sha
|
|
299
|
+
# a closed disappearance episode is archived, never erased — the
|
|
300
|
+
# appeared→vanished→reappeared trail is itself audit evidence
|
|
301
|
+
gone = o.pop("unresolved_disappearance", None)
|
|
302
|
+
if gone is not None:
|
|
303
|
+
o.setdefault("disappearance_history", []).append(
|
|
304
|
+
{**gone, "reappeared_at": _now(), "reappeared_in_report": report_sha})
|
|
305
|
+
# severity ratchet: minor->critical escalates, never de-escalates
|
|
306
|
+
new_sev, old_sev = _severity(f), o.get("severity", "minor")
|
|
307
|
+
if SEV_RANK.get(new_sev, 0) > SEV_RANK.get(old_sev, 0):
|
|
308
|
+
o["severity"] = new_sev
|
|
309
|
+
o.setdefault("_escalations", []).append(
|
|
310
|
+
{"from": old_sev, "to": new_sev, "at": _now(), "report": report_sha})
|
|
311
|
+
# recurrence after resolution: the fix did not hold — re-open
|
|
312
|
+
if o["status"] == "RESOLVED":
|
|
313
|
+
o.setdefault("previous_resolutions", []).append(o.pop("resolution"))
|
|
314
|
+
o["status"] = "OPEN"
|
|
315
|
+
o["recurred_after_resolution"] = {"at": _now(), "report": report_sha}
|
|
316
|
+
reopened += 1
|
|
317
|
+
continue
|
|
318
|
+
ledger["obligations"].append({
|
|
319
|
+
"obligation_id": fid,
|
|
320
|
+
"status": "OPEN",
|
|
321
|
+
"severity": _severity(f),
|
|
322
|
+
"skill": f.get("skill"),
|
|
323
|
+
"pattern_id": f.get("pattern_id"),
|
|
324
|
+
"title": f.get("title", ""),
|
|
325
|
+
"finding_snapshot": f, # immutable record of the accusation
|
|
326
|
+
"first_seen_report": report_sha,
|
|
327
|
+
"last_seen_report": report_sha,
|
|
328
|
+
"opened_at": _now(),
|
|
329
|
+
})
|
|
330
|
+
opened += 1
|
|
331
|
+
ledger["last_report_sha256"] = report_sha # binds gate to the folded report
|
|
332
|
+
|
|
333
|
+
vanished = 0
|
|
334
|
+
for o in ledger["obligations"]:
|
|
335
|
+
if o["status"] == "OPEN" and o["obligation_id"] not in current \
|
|
336
|
+
and o.get("last_seen_report") != report_sha:
|
|
337
|
+
d = o.get("unresolved_disappearance")
|
|
338
|
+
if d is None:
|
|
339
|
+
o["unresolved_disappearance"] = {
|
|
340
|
+
"noted_at": _now(), "absent_from_report": report_sha,
|
|
341
|
+
"note": "finding no longer detected but obligation was never "
|
|
342
|
+
"resolved with evidence — disappearance is not resolution",
|
|
343
|
+
}
|
|
344
|
+
vanished += 1
|
|
345
|
+
else: # still absent: extend the episode, never overwrite its start
|
|
346
|
+
d["last_absent_report"] = report_sha
|
|
347
|
+
d["last_noted_at"] = _now()
|
|
348
|
+
|
|
349
|
+
_save_ledger(path, ledger)
|
|
350
|
+
# Archive the folded report verbatim: `fresh` re-reads the VERDICT from
|
|
351
|
+
# this sha-verified copy and recomputes the decision, instead of trusting
|
|
352
|
+
# a stored (editable) policy token.
|
|
353
|
+
with open(args.report, "rb") as src:
|
|
354
|
+
blob = src.read()
|
|
355
|
+
fd, tmp = tempfile.mkstemp(dir=_forensics_dir(args.paper_dir), suffix=".tmp")
|
|
356
|
+
with os.fdopen(fd, "wb") as fh:
|
|
357
|
+
fh.write(blob)
|
|
358
|
+
os.replace(tmp, os.path.join(_forensics_dir(args.paper_dir), "last_report.json"))
|
|
359
|
+
print(f"obligations: +{opened} opened, {reopened} re-opened (recurrence), "
|
|
360
|
+
f"{vanished} unresolved-disappearance, "
|
|
361
|
+
f"{sum(1 for o in ledger['obligations'] if o['status'] == 'OPEN')} open total -> {path}")
|
|
362
|
+
return 0
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
VERIFIED_BY_RE = re.compile(r"(human|checker|cross-family-review):\S.*")
|
|
366
|
+
|
|
367
|
+
|
|
368
|
+
def cmd_resolve(args):
|
|
369
|
+
if args.fix_type not in FIX_TYPES:
|
|
370
|
+
raise SystemExit(f"FATAL: fix_type must be one of {FIX_TYPES}")
|
|
371
|
+
if not os.path.isfile(args.evidence):
|
|
372
|
+
raise SystemExit(f"FATAL: evidence file does not exist: {args.evidence} "
|
|
373
|
+
"(a resolution without evidence is a reworded flag)")
|
|
374
|
+
if not VERIFIED_BY_RE.fullmatch((args.verified_by or "").strip()):
|
|
375
|
+
raise SystemExit("FATAL: --verified-by must be typed provenance — "
|
|
376
|
+
"'human:<name>', 'checker:<tool>', or "
|
|
377
|
+
"'cross-family-review:<thread-id>' (a freehand token is "
|
|
378
|
+
"not a receipt)")
|
|
379
|
+
path = os.path.join(_forensics_dir(args.paper_dir), "obligations.json")
|
|
380
|
+
ledger = _load_ledger(path)
|
|
381
|
+
for o in ledger["obligations"]:
|
|
382
|
+
if o["obligation_id"] == args.obligation_id:
|
|
383
|
+
if o["status"] != "OPEN":
|
|
384
|
+
raise SystemExit(f"FATAL: obligation is {o['status']}, not OPEN")
|
|
385
|
+
o["status"] = "RESOLVED"
|
|
386
|
+
o["resolution"] = {
|
|
387
|
+
"fix_type": args.fix_type,
|
|
388
|
+
# abspath: the receipt is re-verified against this file on
|
|
389
|
+
# every later gate — evidence must be durable and unchanged
|
|
390
|
+
"evidence_path": os.path.abspath(args.evidence),
|
|
391
|
+
"evidence_sha256": _sha256_file(args.evidence),
|
|
392
|
+
"verified_by": args.verified_by.strip(),
|
|
393
|
+
"resolved_at": _now(),
|
|
394
|
+
}
|
|
395
|
+
_save_ledger(path, ledger)
|
|
396
|
+
print(f"resolved {args.obligation_id} ({args.fix_type})")
|
|
397
|
+
return 0
|
|
398
|
+
raise SystemExit(f"FATAL: no obligation {args.obligation_id}")
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def cmd_waive(args):
|
|
402
|
+
if not re.fullmatch(r"human:\S.*", (args.approver or "").strip()) \
|
|
403
|
+
or not (args.reason or "").strip():
|
|
404
|
+
raise SystemExit("FATAL: waive requires --approver 'human:<name>' and "
|
|
405
|
+
"--reason. The tool cannot authenticate humanity — the "
|
|
406
|
+
"typed prefix makes a non-human waiver an explicit false "
|
|
407
|
+
"record with a permanent paper trail, not a lazy default.")
|
|
408
|
+
path = os.path.join(_forensics_dir(args.paper_dir), "obligations.json")
|
|
409
|
+
ledger = _load_ledger(path)
|
|
410
|
+
for o in ledger["obligations"]:
|
|
411
|
+
if o["obligation_id"] == args.obligation_id:
|
|
412
|
+
if o["status"] != "OPEN":
|
|
413
|
+
raise SystemExit(f"FATAL: obligation is {o['status']}, not OPEN")
|
|
414
|
+
o["status"] = "WAIVED" # a waiver is NOT a resolution
|
|
415
|
+
o["waiver"] = {"approver": args.approver, "reason": args.reason,
|
|
416
|
+
"waived_at": _now()}
|
|
417
|
+
_save_ledger(path, ledger)
|
|
418
|
+
print(f"waived {args.obligation_id} (approver: {args.approver})")
|
|
419
|
+
return 0
|
|
420
|
+
raise SystemExit(f"FATAL: no obligation {args.obligation_id}")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def _closure_invalid(o):
|
|
424
|
+
"""A ledger entry counts as validly closed ONLY with its receipt: RESOLVED
|
|
425
|
+
needs a typed resolution (legal fix_type, hashed evidence, verifier);
|
|
426
|
+
WAIVED needs a human waiver record. A closed status WITHOUT the receipt —
|
|
427
|
+
or any unknown status — is not 'closed', it is malformed: fail toward
|
|
428
|
+
caution (a hand-edited `"status": "RESOLVED"` must not open the gate)."""
|
|
429
|
+
st = o.get("status")
|
|
430
|
+
if st == "OPEN":
|
|
431
|
+
return False
|
|
432
|
+
if st == "RESOLVED":
|
|
433
|
+
r = o.get("resolution")
|
|
434
|
+
if not (isinstance(r, dict) and r.get("fix_type") in FIX_TYPES
|
|
435
|
+
and isinstance(r.get("evidence_sha256"), str)
|
|
436
|
+
and re.fullmatch(r"[0-9a-f]{64}", r["evidence_sha256"])
|
|
437
|
+
and VERIFIED_BY_RE.fullmatch((r.get("verified_by") or "").strip())):
|
|
438
|
+
return True
|
|
439
|
+
# the receipt is re-verified, not remembered: the evidence file must
|
|
440
|
+
# still exist and still hash to what was recorded at closure time
|
|
441
|
+
p = r.get("evidence_path")
|
|
442
|
+
return not (isinstance(p, str) and os.path.isfile(p)
|
|
443
|
+
and _sha256_file(p) == r["evidence_sha256"])
|
|
444
|
+
if st == "WAIVED":
|
|
445
|
+
w = o.get("waiver")
|
|
446
|
+
return not (isinstance(w, dict)
|
|
447
|
+
and re.fullmatch(r"human:\S.*", (w.get("approver") or "").strip())
|
|
448
|
+
and (w.get("reason") or "").strip())
|
|
449
|
+
return True
|
|
450
|
+
|
|
451
|
+
|
|
452
|
+
def _decide(verdict, ledger, report_sha):
|
|
453
|
+
"""The ONE deterministic decision function — cmd_gate computes with it and
|
|
454
|
+
cmd_fresh RE-computes with it (a stored policy token is display, never
|
|
455
|
+
authority: editing gate.json's decision field must change nothing)."""
|
|
456
|
+
open_obl = [o for o in ledger["obligations"] if o.get("status") == "OPEN"]
|
|
457
|
+
open_critical = [o for o in open_obl if o.get("severity") == "critical"]
|
|
458
|
+
weird = [o for o in ledger["obligations"] if _closure_invalid(o)]
|
|
459
|
+
# The gate only speaks for a report the ledger has actually folded in.
|
|
460
|
+
# A MISSING ledger (or one that never folded anything) is unbound too —
|
|
461
|
+
# deleting obligations.json must not turn a CLEAN report into exit 0.
|
|
462
|
+
unbound = ledger.get("last_report_sha256") != report_sha
|
|
463
|
+
|
|
464
|
+
if verdict in ("HARD_FLAGS", "REVIEW_UNAVAILABLE") or open_critical or weird or unbound:
|
|
465
|
+
decision = BLOCK
|
|
466
|
+
elif verdict == "SOFT_FLAGS" or open_obl:
|
|
467
|
+
decision = WARN
|
|
468
|
+
elif verdict == "CLEAN_GIVEN_EVIDENCE":
|
|
469
|
+
decision = NO_NEW_BLOCKER
|
|
470
|
+
else:
|
|
471
|
+
decision = BLOCK # unknown verdict token: fail closed
|
|
472
|
+
return decision, open_obl, open_critical, weird, unbound
|
|
473
|
+
|
|
474
|
+
|
|
475
|
+
def cmd_gate(args):
|
|
476
|
+
report = _load_report_strict(args.report)
|
|
477
|
+
verdict = report.get("overall_verdict", "")
|
|
478
|
+
report_sha = _sha256_file(args.report)
|
|
479
|
+
_assert_report_not_stale(args.report, args.paper_dir)
|
|
480
|
+
path = os.path.join(_forensics_dir(args.paper_dir), "obligations.json")
|
|
481
|
+
ledger = _load_ledger(path)
|
|
482
|
+
decision, open_obl, open_critical, weird, unbound = _decide(verdict, ledger, report_sha)
|
|
483
|
+
|
|
484
|
+
exec_family = _executor_family(args.executor_model)
|
|
485
|
+
claims = os.path.join(args.paper_dir, "claims.json")
|
|
486
|
+
# Which dimensions never ran. The upstream verdict folds incompleteness in only
|
|
487
|
+
# when it would otherwise be CLEAN, so a SOFT_FLAGS sweep can silently be a
|
|
488
|
+
# partial one. This gate does not re-decide on it — it says so out loud.
|
|
489
|
+
unavailable_dims = sorted(k for k, v in (report.get("coverage") or {}).items()
|
|
490
|
+
if v == "review_unavailable")
|
|
491
|
+
gate = {
|
|
492
|
+
"gate_version": GATE_VERSION,
|
|
493
|
+
"generated_at": _now(),
|
|
494
|
+
# the upstream verdict, VERBATIM — this gate never re-labels it
|
|
495
|
+
"upstream_verdict": verdict,
|
|
496
|
+
"upstream_adjudicator": report.get("adjudicator", ""),
|
|
497
|
+
"policy_decision": decision,
|
|
498
|
+
"anti_ar_commit": args.anti_ar_commit,
|
|
499
|
+
"report_sha256": report_sha,
|
|
500
|
+
"ledger_bound": not unbound,
|
|
501
|
+
"malformed_ledger_entries": len(weird),
|
|
502
|
+
# binds this gate to the paper text it audited (checked by `fresh`)
|
|
503
|
+
"paper_fingerprint": _paper_fingerprint(args.paper_dir),
|
|
504
|
+
# binds this gate to the exact obligations ledger it judged — an
|
|
505
|
+
# out-of-band edit (e.g. hand-deleting one obligation) reads as
|
|
506
|
+
# LEDGER_DRIFT in `fresh` even when the recomputed decision agrees
|
|
507
|
+
"obligations_sha256": _sha256_file(path) if os.path.isfile(path) else None,
|
|
508
|
+
# upstream's span-anchored claims ledger (informational provenance —
|
|
509
|
+
# honestly labeled: this is NOT the obligations ledger)
|
|
510
|
+
"claims_ledger_sha256": _sha256_file(claims) if os.path.isfile(claims) else None,
|
|
511
|
+
"observability_level": report.get("observability_level"),
|
|
512
|
+
"coverage": report.get("coverage", {}),
|
|
513
|
+
"unavailable_dimensions": unavailable_dims,
|
|
514
|
+
"open_obligations": len(open_obl),
|
|
515
|
+
"open_critical_obligations": len(open_critical),
|
|
516
|
+
# honest provenance: the sweep's auditors are GPT-family. For a Claude
|
|
517
|
+
# executor that is cross-family PROPOSAL provenance; for a Codex executor
|
|
518
|
+
# it is same-family. Either way this gate only raises flags — it records
|
|
519
|
+
# provenance, it does not (cannot) grant acceptance.
|
|
520
|
+
"executor_model": args.executor_model,
|
|
521
|
+
"proposal_provenance": ("cross-family" if exec_family not in (AUDITOR_FAMILY, "unknown")
|
|
522
|
+
else ("same-family" if exec_family == AUDITOR_FAMILY
|
|
523
|
+
else "unknown")),
|
|
524
|
+
}
|
|
525
|
+
out = os.path.join(_forensics_dir(args.paper_dir), "gate.json")
|
|
526
|
+
fd, tmp = tempfile.mkstemp(dir=os.path.dirname(out), suffix=".tmp")
|
|
527
|
+
with os.fdopen(fd, "w", encoding="utf-8") as fh:
|
|
528
|
+
json.dump(gate, fh, indent=2, ensure_ascii=False)
|
|
529
|
+
os.replace(tmp, out)
|
|
530
|
+
never_ran = (f"; never ran: {', '.join(unavailable_dims)}" if unavailable_dims else "")
|
|
531
|
+
print(f"forensics gate: {decision} (upstream: {verdict or '(missing)'}; "
|
|
532
|
+
f"open obligations: {len(open_obl)}, critical: {len(open_critical)}"
|
|
533
|
+
f"{never_ran}) -> {out}")
|
|
534
|
+
return 0 if decision != BLOCK else 1
|
|
535
|
+
|
|
536
|
+
|
|
537
|
+
def cmd_fresh(args):
|
|
538
|
+
"""The downstream preflight, in one exit code. Exit 0 ⟺ a gate exists AND
|
|
539
|
+
it speaks for the CURRENT paper content AND for the CURRENT obligations
|
|
540
|
+
ledger AND its decision is pass-capable (WARN / NO_NEW_BLOCKER). Missing
|
|
541
|
+
gate, post-gate edits, an unbound ledger, BLOCK, or any unknown policy
|
|
542
|
+
token → exit 1: a resumed or post-edit run must re-sweep + `evaluate`,
|
|
543
|
+
never inherit an old (or hand-crafted) pass."""
|
|
544
|
+
out = os.path.join(_forensics_dir(args.paper_dir), "gate.json")
|
|
545
|
+
if not os.path.isfile(out):
|
|
546
|
+
print("forensics fresh: NO_GATE — gate.json missing; run `evaluate` first")
|
|
547
|
+
return 1
|
|
548
|
+
with open(out, encoding="utf-8") as fh:
|
|
549
|
+
gate = json.load(fh)
|
|
550
|
+
if gate.get("gate_version") != GATE_VERSION:
|
|
551
|
+
print(f"forensics fresh: VERSION_MISMATCH — gate is v{gate.get('gate_version')!r}, "
|
|
552
|
+
f"tool is v{GATE_VERSION}; re-run `evaluate`")
|
|
553
|
+
return 1
|
|
554
|
+
if args.anti_ar_commit and gate.get("anti_ar_commit") != args.anti_ar_commit:
|
|
555
|
+
print(f"forensics fresh: PIN_MISMATCH — gate was produced at pin "
|
|
556
|
+
f"{gate.get('anti_ar_commit')!r}, launcher pins {args.anti_ar_commit!r}; "
|
|
557
|
+
"findings from an older adjudicator must be re-audited (pin-bump rule)")
|
|
558
|
+
return 1
|
|
559
|
+
if gate.get("paper_fingerprint") != _paper_fingerprint(args.paper_dir):
|
|
560
|
+
print("forensics fresh: STALE — paper sources/deliverables changed after "
|
|
561
|
+
"the gate; re-run the sweep + `evaluate`")
|
|
562
|
+
return 1
|
|
563
|
+
ledger_path = os.path.join(_forensics_dir(args.paper_dir), "obligations.json")
|
|
564
|
+
current_ledger_sha = _sha256_file(ledger_path) if os.path.isfile(ledger_path) else None
|
|
565
|
+
if gate.get("obligations_sha256") is None \
|
|
566
|
+
or gate.get("obligations_sha256") != current_ledger_sha:
|
|
567
|
+
print("forensics fresh: LEDGER_DRIFT — obligations.json changed after the "
|
|
568
|
+
"gate (even an edit that keeps the same decision); re-run `evaluate`")
|
|
569
|
+
return 1
|
|
570
|
+
ledger = _load_ledger(ledger_path)
|
|
571
|
+
if gate.get("report_sha256") is None \
|
|
572
|
+
or gate.get("report_sha256") != ledger.get("last_report_sha256"):
|
|
573
|
+
print("forensics fresh: UNBOUND — gate does not match the current "
|
|
574
|
+
"obligations ledger; re-run `evaluate`")
|
|
575
|
+
return 1
|
|
576
|
+
# Re-derive the decision from the sha-verified ARCHIVED report + the live
|
|
577
|
+
# ledger — never from the gate's stored token (a one-field edit of
|
|
578
|
+
# gate.json from BLOCK to WARN must change nothing).
|
|
579
|
+
archived = os.path.join(_forensics_dir(args.paper_dir), "last_report.json")
|
|
580
|
+
if not os.path.isfile(archived) \
|
|
581
|
+
or _sha256_file(archived) != ledger.get("last_report_sha256"):
|
|
582
|
+
print("forensics fresh: NO_ARCHIVE — the folded report is missing or does "
|
|
583
|
+
"not hash to the ledger's binding; re-run `evaluate`")
|
|
584
|
+
return 1
|
|
585
|
+
verdict = _load_report_strict(archived).get("overall_verdict", "")
|
|
586
|
+
decision, _open_obl, _crit, _weird, _unbound = _decide(
|
|
587
|
+
verdict, ledger, gate["report_sha256"])
|
|
588
|
+
if decision != gate.get("policy_decision"):
|
|
589
|
+
print(f"forensics fresh: MISMATCH — gate.json says "
|
|
590
|
+
f"{gate.get('policy_decision')!r} but the recomputed decision is "
|
|
591
|
+
f"{decision!r}; the gate artifact does not reproduce — re-run `evaluate`")
|
|
592
|
+
return 1
|
|
593
|
+
if decision not in (WARN, NO_NEW_BLOCKER):
|
|
594
|
+
print(f"forensics fresh: {decision} — not pass-capable; the gate is closed")
|
|
595
|
+
return 1
|
|
596
|
+
stale_dims = gate.get("unavailable_dimensions", [])
|
|
597
|
+
never_ran = (f"; never ran: {', '.join(stale_dims)}" if stale_dims else "")
|
|
598
|
+
print(f"forensics fresh: OK ({decision}; recomputed from the archived report "
|
|
599
|
+
f"and current ledger{never_ran})")
|
|
600
|
+
return 0
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def main(argv=None):
|
|
604
|
+
ap = argparse.ArgumentParser(description="Typed gate + obligations for /integrity-forensics.")
|
|
605
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
606
|
+
|
|
607
|
+
g = sub.add_parser("gate", help="compute the policy decision from an Anti-AR report")
|
|
608
|
+
g.add_argument("--report", required=True)
|
|
609
|
+
g.add_argument("--paper-dir", required=True)
|
|
610
|
+
g.add_argument("--anti-ar-commit", required=True,
|
|
611
|
+
help="the SHA-pin the launcher ran (provenance)")
|
|
612
|
+
g.add_argument("--executor-model", default="claude",
|
|
613
|
+
help="the pipeline's executor, for honest provenance labeling")
|
|
614
|
+
|
|
615
|
+
u = sub.add_parser("update", help="fold a report's findings into the append-only ledger")
|
|
616
|
+
u.add_argument("--report", required=True)
|
|
617
|
+
u.add_argument("--paper-dir", required=True)
|
|
618
|
+
|
|
619
|
+
e = sub.add_parser("evaluate", help="atomic update + gate in one transaction (preferred)")
|
|
620
|
+
e.add_argument("--report", required=True)
|
|
621
|
+
e.add_argument("--paper-dir", required=True)
|
|
622
|
+
e.add_argument("--anti-ar-commit", required=True)
|
|
623
|
+
e.add_argument("--executor-model", default="claude")
|
|
624
|
+
|
|
625
|
+
r = sub.add_parser("resolve", help="close ONE obligation with typed, hashed evidence")
|
|
626
|
+
r.add_argument("--paper-dir", required=True)
|
|
627
|
+
r.add_argument("--obligation-id", required=True)
|
|
628
|
+
r.add_argument("--fix-type", required=True)
|
|
629
|
+
r.add_argument("--evidence", required=True)
|
|
630
|
+
r.add_argument("--verified-by", required=True)
|
|
631
|
+
|
|
632
|
+
w = sub.add_parser("waive", help="human-approved waiver (never a resolution)")
|
|
633
|
+
w.add_argument("--paper-dir", required=True)
|
|
634
|
+
w.add_argument("--obligation-id", required=True)
|
|
635
|
+
w.add_argument("--approver", required=True)
|
|
636
|
+
w.add_argument("--reason", required=True)
|
|
637
|
+
|
|
638
|
+
f = sub.add_parser("fresh", help="verify gate.json still matches the current paper text")
|
|
639
|
+
f.add_argument("--paper-dir", required=True)
|
|
640
|
+
f.add_argument("--anti-ar-commit", default=None,
|
|
641
|
+
help="when given, the gate must have been produced at this pin")
|
|
642
|
+
|
|
643
|
+
a = ap.parse_args(argv)
|
|
644
|
+
if a.cmd == "gate":
|
|
645
|
+
# gate does not mutate the ledger, but it must read it and write
|
|
646
|
+
# gate.json under the same lock — otherwise a concurrent `evaluate`
|
|
647
|
+
# could land a BLOCK gate that this older read then overwrites
|
|
648
|
+
with _ledger_lock(a.paper_dir):
|
|
649
|
+
return cmd_gate(a)
|
|
650
|
+
if a.cmd == "fresh":
|
|
651
|
+
# same lock as the mutators: a concurrent resolve/waive/update must
|
|
652
|
+
# not interleave between fresh's reads (gate, ledger, archive)
|
|
653
|
+
with _ledger_lock(a.paper_dir):
|
|
654
|
+
return cmd_fresh(a)
|
|
655
|
+
if a.cmd == "evaluate":
|
|
656
|
+
# one lock across update AND gate — the emitted gate.json provably
|
|
657
|
+
# describes the ledger state this same transaction produced
|
|
658
|
+
with _ledger_lock(a.paper_dir):
|
|
659
|
+
rc = cmd_update(a)
|
|
660
|
+
if rc != 0:
|
|
661
|
+
return rc
|
|
662
|
+
return cmd_gate(a)
|
|
663
|
+
with _ledger_lock(a.paper_dir):
|
|
664
|
+
return {"update": cmd_update, "resolve": cmd_resolve,
|
|
665
|
+
"waive": cmd_waive}[a.cmd](a)
|
|
666
|
+
|
|
667
|
+
|
|
668
|
+
if __name__ == "__main__":
|
|
669
|
+
sys.exit(main())
|