@trac3r/oh-my-god 2.2.11
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +188 -0
- package/INSTALL-VERIFICATION-INDEX.md +51 -0
- package/LICENSE +21 -0
- package/OMG-setup.sh +2549 -0
- package/QUICK-REFERENCE.md +58 -0
- package/README.md +207 -0
- package/agents/__init__.py +1 -0
- package/agents/__pycache__/model_roles.cpython-313.pyc +0 -0
- package/agents/_model_roles.yaml +26 -0
- package/agents/designer.md +67 -0
- package/agents/explore.md +60 -0
- package/agents/model_roles.py +196 -0
- package/agents/omg-api-builder.md +23 -0
- package/agents/omg-architect-mode.md +41 -0
- package/agents/omg-architect.md +13 -0
- package/agents/omg-backend-engineer.md +41 -0
- package/agents/omg-critic.md +16 -0
- package/agents/omg-database-engineer.md +41 -0
- package/agents/omg-escalation-router.md +17 -0
- package/agents/omg-executor.md +12 -0
- package/agents/omg-frontend-designer.md +41 -0
- package/agents/omg-implement-mode.md +49 -0
- package/agents/omg-infra-engineer.md +41 -0
- package/agents/omg-qa-tester.md +16 -0
- package/agents/omg-research-mode.md +41 -0
- package/agents/omg-security-auditor.md +41 -0
- package/agents/omg-testing-engineer.md +41 -0
- package/agents/plan.md +80 -0
- package/agents/quick_task.md +64 -0
- package/agents/reviewer.md +83 -0
- package/agents/task.md +71 -0
- package/bin/omg +41 -0
- package/commands/OMG:ai-commit.md +113 -0
- package/commands/OMG:api-twin.md +22 -0
- package/commands/OMG:arch.md +313 -0
- package/commands/OMG:browser.md +29 -0
- package/commands/OMG:ccg.md +22 -0
- package/commands/OMG:compat.md +57 -0
- package/commands/OMG:cost.md +181 -0
- package/commands/OMG:crazy.md +125 -0
- package/commands/OMG:create-agent.md +183 -0
- package/commands/OMG:deep-plan.md +18 -0
- package/commands/OMG:deps.md +248 -0
- package/commands/OMG:diagnose-plugins.md +33 -0
- package/commands/OMG:doctor.md +37 -0
- package/commands/OMG:domain-init.md +11 -0
- package/commands/OMG:escalate.md +52 -0
- package/commands/OMG:forge.md +103 -0
- package/commands/OMG:health-check.md +48 -0
- package/commands/OMG:init.md +134 -0
- package/commands/OMG:issue.md +56 -0
- package/commands/OMG:mode.md +44 -0
- package/commands/OMG:playwright.md +17 -0
- package/commands/OMG:preflight.md +26 -0
- package/commands/OMG:preset.md +49 -0
- package/commands/OMG:profile-review.md +58 -0
- package/commands/OMG:project-init.md +11 -0
- package/commands/OMG:ralph-start.md +43 -0
- package/commands/OMG:ralph-stop.md +23 -0
- package/commands/OMG:security-check.md +28 -0
- package/commands/OMG:session-branch.md +101 -0
- package/commands/OMG:session-fork.md +57 -0
- package/commands/OMG:session-merge.md +138 -0
- package/commands/OMG:setup.md +82 -0
- package/commands/OMG:ship.md +18 -0
- package/commands/OMG:stats.md +225 -0
- package/commands/OMG:teams.md +54 -0
- package/commands/OMG:theme.md +44 -0
- package/commands/OMG:validate.md +59 -0
- package/commands/__init__.py +1 -0
- package/docs/command-surface.md +55 -0
- package/docs/install/claude-code.md +53 -0
- package/docs/install/codex.md +45 -0
- package/docs/install/gemini.md +43 -0
- package/docs/install/github-action.md +81 -0
- package/docs/install/github-app-required-checks.md +107 -0
- package/docs/install/github-app.md +161 -0
- package/docs/install/kimi.md +43 -0
- package/docs/install/opencode.md +38 -0
- package/docs/proof.md +182 -0
- package/hooks/__init__.py +0 -0
- package/hooks/__pycache__/__init__.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_agent_registry.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_analytics.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_budget.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_common.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_compression_optimizer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_cost_ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_learnings.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_memory.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_protected_context.cpython-313.pyc +0 -0
- package/hooks/__pycache__/_token_counter.cpython-313.pyc +0 -0
- package/hooks/__pycache__/branch_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/budget_governor.cpython-313.pyc +0 -0
- package/hooks/__pycache__/circuit-breaker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/compression_feedback.cpython-313.pyc +0 -0
- package/hooks/__pycache__/config-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/context_pressure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/credential_store.cpython-313.pyc +0 -0
- package/hooks/__pycache__/fetch-rate-limits.cpython-313.pyc +0 -0
- package/hooks/__pycache__/firewall.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-formatter-bridge.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-injector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/hashline-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/idle-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/instructions-loaded.cpython-313.pyc +0 -0
- package/hooks/__pycache__/intentgate-keyword-detector.cpython-313.pyc +0 -0
- package/hooks/__pycache__/magic-keyword-router.cpython-313.pyc +0 -0
- package/hooks/__pycache__/policy_engine.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-tool-failure.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post-write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/post_write.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-compact.cpython-313.pyc +0 -0
- package/hooks/__pycache__/pre-tool-inject.cpython-313.pyc +0 -0
- package/hooks/__pycache__/prompt-enhancer.cpython-313.pyc +0 -0
- package/hooks/__pycache__/quality-runner.cpython-313.pyc +0 -0
- package/hooks/__pycache__/query.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/secret_audit.cpython-313.pyc +0 -0
- package/hooks/__pycache__/security_validators.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-end-capture.cpython-313.pyc +0 -0
- package/hooks/__pycache__/session-start.cpython-313.pyc +0 -0
- package/hooks/__pycache__/setup_wizard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/shadow_manager.cpython-313.pyc +0 -0
- package/hooks/__pycache__/state_migration.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/stop_dispatcher.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tdd-gate.cpython-313.pyc +0 -0
- package/hooks/__pycache__/terms-guard.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test-validator.cpython-313.pyc +0 -0
- package/hooks/__pycache__/test_generator_hook.cpython-313.pyc +0 -0
- package/hooks/__pycache__/todo-state-tracker.cpython-313.pyc +0 -0
- package/hooks/__pycache__/tool-ledger.cpython-313.pyc +0 -0
- package/hooks/__pycache__/trust_review.cpython-313.pyc +0 -0
- package/hooks/__pycache__/user-prompt-submit.cpython-313.pyc +0 -0
- package/hooks/_agent_registry.py +481 -0
- package/hooks/_analytics.py +291 -0
- package/hooks/_budget.py +31 -0
- package/hooks/_common.py +761 -0
- package/hooks/_compression_optimizer.py +119 -0
- package/hooks/_cost_ledger.py +176 -0
- package/hooks/_learnings.py +126 -0
- package/hooks/_memory.py +103 -0
- package/hooks/_post_write.py +46 -0
- package/hooks/_protected_context.py +150 -0
- package/hooks/_token_counter.py +221 -0
- package/hooks/branch_manager.py +255 -0
- package/hooks/budget_governor.py +326 -0
- package/hooks/circuit-breaker.py +270 -0
- package/hooks/compression_feedback.py +254 -0
- package/hooks/config-guard.py +193 -0
- package/hooks/context_pressure.py +119 -0
- package/hooks/credential_store.py +970 -0
- package/hooks/fetch-rate-limits.py +212 -0
- package/hooks/firewall.py +323 -0
- package/hooks/hashline-formatter-bridge.py +224 -0
- package/hooks/hashline-injector.py +273 -0
- package/hooks/hashline-validator.py +216 -0
- package/hooks/idle-detector.py +97 -0
- package/hooks/instructions-loaded.py +26 -0
- package/hooks/intentgate-keyword-detector.py +200 -0
- package/hooks/magic-keyword-router.py +195 -0
- package/hooks/policy_engine.py +767 -0
- package/hooks/post-tool-failure.py +19 -0
- package/hooks/post-write.py +233 -0
- package/hooks/pre-compact.py +470 -0
- package/hooks/pre-tool-inject.py +98 -0
- package/hooks/prompt-enhancer.py +879 -0
- package/hooks/quality-runner.py +191 -0
- package/hooks/query.py +512 -0
- package/hooks/secret-guard.py +120 -0
- package/hooks/secret_audit.py +144 -0
- package/hooks/security_validators.py +93 -0
- package/hooks/session-end-capture.py +505 -0
- package/hooks/session-start.py +261 -0
- package/hooks/setup_wizard.py +1101 -0
- package/hooks/shadow_manager.py +476 -0
- package/hooks/state_migration.py +228 -0
- package/hooks/stop-gate.py +7 -0
- package/hooks/stop_dispatcher.py +1259 -0
- package/hooks/tdd-gate.py +10 -0
- package/hooks/terms-guard.py +98 -0
- package/hooks/test-validator.py +462 -0
- package/hooks/test_generator_hook.py +123 -0
- package/hooks/todo-state-tracker.py +114 -0
- package/hooks/tool-ledger.py +165 -0
- package/hooks/trust_review.py +662 -0
- package/hooks/user-prompt-submit.py +12 -0
- package/hud/omg-hud.mjs +1571 -0
- package/lab/__init__.py +1 -0
- package/lab/__pycache__/__init__.cpython-313.pyc +0 -0
- package/lab/__pycache__/axolotl_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/forge_runner.cpython-313.pyc +0 -0
- package/lab/__pycache__/gazebo_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/isaac_gym_adapter.cpython-313.pyc +0 -0
- package/lab/__pycache__/mock_isaac_env.cpython-313.pyc +0 -0
- package/lab/__pycache__/pipeline.cpython-313.pyc +0 -0
- package/lab/__pycache__/policies.cpython-313.pyc +0 -0
- package/lab/__pycache__/pybullet_adapter.cpython-313.pyc +0 -0
- package/lab/axolotl_adapter.py +531 -0
- package/lab/forge_runner.py +103 -0
- package/lab/gazebo_adapter.py +168 -0
- package/lab/isaac_gym_adapter.py +190 -0
- package/lab/mock_isaac_env.py +47 -0
- package/lab/pipeline.py +712 -0
- package/lab/policies.py +52 -0
- package/lab/pybullet_adapter.py +192 -0
- package/package.json +61 -0
- package/plugins/README.md +78 -0
- package/plugins/__init__.py +1 -0
- package/plugins/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/advanced/commands/OMG-code-review.md +114 -0
- package/plugins/advanced/commands/OMG-deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG-handoff.md +115 -0
- package/plugins/advanced/commands/OMG-learn.md +110 -0
- package/plugins/advanced/commands/OMG-maintainer.md +31 -0
- package/plugins/advanced/commands/OMG-ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG-ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG-security-review.md +16 -0
- package/plugins/advanced/commands/OMG-sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG-ship.md +46 -0
- package/plugins/advanced/commands/OMG:code-review.md +114 -0
- package/plugins/advanced/commands/OMG:deep-plan.md +266 -0
- package/plugins/advanced/commands/OMG:handoff.md +115 -0
- package/plugins/advanced/commands/OMG:learn.md +110 -0
- package/plugins/advanced/commands/OMG:maintainer.md +31 -0
- package/plugins/advanced/commands/OMG:ralph-start.md +43 -0
- package/plugins/advanced/commands/OMG:ralph-stop.md +23 -0
- package/plugins/advanced/commands/OMG:security-review.md +16 -0
- package/plugins/advanced/commands/OMG:sequential-thinking.md +20 -0
- package/plugins/advanced/commands/OMG:ship.md +46 -0
- package/plugins/advanced/plugin.json +104 -0
- package/plugins/core/plugin.json +204 -0
- package/plugins/dephealth/__init__.py +0 -0
- package/plugins/dephealth/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/cve_scanner.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/license_checker.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/manifest_detector.cpython-313.pyc +0 -0
- package/plugins/dephealth/__pycache__/vuln_analyzer.cpython-313.pyc +0 -0
- package/plugins/dephealth/cve_scanner.py +279 -0
- package/plugins/dephealth/license_checker.py +135 -0
- package/plugins/dephealth/manifest_detector.py +423 -0
- package/plugins/dephealth/vuln_analyzer.py +176 -0
- package/plugins/testgen/__init__.py +0 -0
- package/plugins/testgen/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/codamosa_engine.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/edge_case_synthesizer.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/framework_detector.cpython-313.pyc +0 -0
- package/plugins/testgen/__pycache__/skeleton_generator.cpython-313.pyc +0 -0
- package/plugins/testgen/codamosa_engine.py +402 -0
- package/plugins/testgen/edge_case_synthesizer.py +184 -0
- package/plugins/testgen/framework_detector.py +271 -0
- package/plugins/testgen/skeleton_generator.py +219 -0
- package/plugins/viz/__init__.py +0 -0
- package/plugins/viz/__pycache__/__init__.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/ast_parser.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/diagram_generator.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/graph_builder.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/native_parsers.cpython-313.pyc +0 -0
- package/plugins/viz/__pycache__/regex_parser.cpython-313.pyc +0 -0
- package/plugins/viz/ast_parser.py +139 -0
- package/plugins/viz/diagram_generator.py +192 -0
- package/plugins/viz/graph_builder.py +444 -0
- package/plugins/viz/native_parsers.py +259 -0
- package/plugins/viz/regex_parser.py +112 -0
- package/pyproject.toml +143 -0
- package/registry/__init__.py +1 -0
- package/registry/__pycache__/__init__.cpython-313.pyc +0 -0
- package/registry/__pycache__/approval_artifact.cpython-313.pyc +0 -0
- package/registry/__pycache__/verify_artifact.cpython-313.pyc +0 -0
- package/registry/approval_artifact.py +236 -0
- package/registry/bundles/algorithms.yaml +45 -0
- package/registry/bundles/api-twin.yaml +48 -0
- package/registry/bundles/ast-pack.yaml +80 -0
- package/registry/bundles/claim-judge.yaml +49 -0
- package/registry/bundles/control-plane.yaml +192 -0
- package/registry/bundles/data-lineage.yaml +47 -0
- package/registry/bundles/delta-classifier.yaml +47 -0
- package/registry/bundles/eval-gate.yaml +47 -0
- package/registry/bundles/hash-edit.yaml +73 -0
- package/registry/bundles/health.yaml +45 -0
- package/registry/bundles/hook-governor.yaml +101 -0
- package/registry/bundles/incident-replay.yaml +47 -0
- package/registry/bundles/lsp-pack.yaml +80 -0
- package/registry/bundles/mcp-fabric.yaml +53 -0
- package/registry/bundles/plan-council.yaml +56 -0
- package/registry/bundles/preflight.yaml +48 -0
- package/registry/bundles/proof-gate.yaml +49 -0
- package/registry/bundles/remote-supervisor.yaml +49 -0
- package/registry/bundles/robotics.yaml +45 -0
- package/registry/bundles/secure-worktree-pipeline.yaml +69 -0
- package/registry/bundles/security-check.yaml +50 -0
- package/registry/bundles/terminal-lane.yaml +61 -0
- package/registry/bundles/test-intent-lock.yaml +49 -0
- package/registry/bundles/tracebank.yaml +47 -0
- package/registry/bundles/vision.yaml +45 -0
- package/registry/omg-capability.schema.json +378 -0
- package/registry/policy-packs/airgapped.lock.json +11 -0
- package/registry/policy-packs/airgapped.signature.json +10 -0
- package/registry/policy-packs/airgapped.yaml +16 -0
- package/registry/policy-packs/fintech.lock.json +11 -0
- package/registry/policy-packs/fintech.signature.json +10 -0
- package/registry/policy-packs/fintech.yaml +15 -0
- package/registry/policy-packs/locked-prod.lock.json +11 -0
- package/registry/policy-packs/locked-prod.signature.json +10 -0
- package/registry/policy-packs/locked-prod.yaml +18 -0
- package/registry/trusted_signers.json +44 -0
- package/registry/verify_artifact.py +493 -0
- package/runtime/__init__.py +36 -0
- package/runtime/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/__pycache__/adoption.cpython-313.pyc +0 -0
- package/runtime/__pycache__/agent_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/api_twin.cpython-313.pyc +0 -0
- package/runtime/__pycache__/architecture_signal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/artifact_parsers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/asset_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/background_verification.cpython-313.pyc +0 -0
- package/runtime/__pycache__/budget_envelopes.cpython-313.pyc +0 -0
- package/runtime/__pycache__/business_workflow.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/canonical_taxonomy.cpython-313.pyc +0 -0
- package/runtime/__pycache__/claim_judge.cpython-313.pyc +0 -0
- package/runtime/__pycache__/cli_provider.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/complexity_scorer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/compliance_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/config_transaction.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_engine.cpython-313.pyc +0 -0
- package/runtime/__pycache__/context_limits.cpython-313.pyc +0 -0
- package/runtime/__pycache__/contract_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/custom_agent_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/data_lineage.cpython-313.pyc +0 -0
- package/runtime/__pycache__/defense_state.cpython-313.pyc +0 -0
- package/runtime/__pycache__/delta_classifier.cpython-313.pyc +0 -0
- package/runtime/__pycache__/dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/doc_generator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/domain_packs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/ecosystem.cpython-313.pyc +0 -0
- package/runtime/__pycache__/equalizer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/eval_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_narrator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_query.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/evidence_requirements.cpython-313.pyc +0 -0
- package/runtime/__pycache__/exec_kernel.cpython-313.pyc +0 -0
- package/runtime/__pycache__/explainer_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/feature_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_agents.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_domains.cpython-313.pyc +0 -0
- package/runtime/__pycache__/forge_run_id.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_integration.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_bot.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_contract.cpython-313.pyc +0 -0
- package/runtime/__pycache__/github_review_formatter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/guide_assert.cpython-313.pyc +0 -0
- package/runtime/__pycache__/hook_governor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/host_parity.cpython-313.pyc +0 -0
- package/runtime/__pycache__/incident_replay.cpython-313.pyc +0 -0
- package/runtime/__pycache__/install_planner.cpython-313.pyc +0 -0
- package/runtime/__pycache__/interaction_journal.cpython-313.pyc +0 -0
- package/runtime/__pycache__/issue_surface.cpython-313.pyc +0 -0
- package/runtime/__pycache__/legacy_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_config_writers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_lifecycle.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mcp_memory_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/memory_store.cpython-313.pyc +0 -0
- package/runtime/__pycache__/merge_writer.cpython-313.pyc +0 -0
- package/runtime/__pycache__/music_omr_testbed.cpython-313.pyc +0 -0
- package/runtime/__pycache__/mutation_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omc_compat.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_browser_cli.cpython-313.pyc +0 -0
- package/runtime/__pycache__/omg_mcp_server.cpython-313.pyc +0 -0
- package/runtime/__pycache__/opus_plan.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_adapter.cpython-313.pyc +0 -0
- package/runtime/__pycache__/playwright_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_diagnostics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/plugin_interop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/policy_pack_loader.cpython-313.pyc +0 -0
- package/runtime/__pycache__/preflight.cpython-313.pyc +0 -0
- package/runtime/__pycache__/profile_io.cpython-313.pyc +0 -0
- package/runtime/__pycache__/prompt_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_chain.cpython-313.pyc +0 -0
- package/runtime/__pycache__/proof_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/provider_parity_eval.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_artifact_audit.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_run_coordinator.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_compiler.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surface_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/release_surfaces.cpython-313.pyc +0 -0
- package/runtime/__pycache__/remote_supervisor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/repro_pack.cpython-313.pyc +0 -0
- package/runtime/__pycache__/rollback_manifest.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_critics.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_executor.cpython-313.pyc +0 -0
- package/runtime/__pycache__/router_selector.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_contracts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/runtime_profile.cpython-313.pyc +0 -0
- package/runtime/__pycache__/security_check.cpython-313.pyc +0 -0
- package/runtime/__pycache__/session_health.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_evolution.cpython-313.pyc +0 -0
- package/runtime/__pycache__/skill_registry.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subagent_dispatcher.cpython-313.pyc +0 -0
- package/runtime/__pycache__/subscription_tiers.cpython-313.pyc +0 -0
- package/runtime/__pycache__/team_router.cpython-313.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313-pytest-9.0.2.pyc +0 -0
- package/runtime/__pycache__/test_intent_lock.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tmux_session_manager.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_fabric.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_plan_gate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tool_relevance.cpython-313.pyc +0 -0
- package/runtime/__pycache__/tracebank.cpython-313.pyc +0 -0
- package/runtime/__pycache__/untrusted_content.cpython-313.pyc +0 -0
- package/runtime/__pycache__/validate.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verdict_schema.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_controller.cpython-313.pyc +0 -0
- package/runtime/__pycache__/verification_loop.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_artifacts.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_cache.cpython-313.pyc +0 -0
- package/runtime/__pycache__/vision_jobs.cpython-313.pyc +0 -0
- package/runtime/__pycache__/worker_watchdog.cpython-313.pyc +0 -0
- package/runtime/adapters/__init__.py +13 -0
- package/runtime/adapters/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/claude.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/gpt.cpython-313.pyc +0 -0
- package/runtime/adapters/__pycache__/local.cpython-313.pyc +0 -0
- package/runtime/adapters/claude.py +63 -0
- package/runtime/adapters/gpt.py +56 -0
- package/runtime/adapters/local.py +56 -0
- package/runtime/adoption.py +280 -0
- package/runtime/api_twin.py +450 -0
- package/runtime/architecture_signal.py +226 -0
- package/runtime/artifact_parsers.py +161 -0
- package/runtime/asset_loader.py +62 -0
- package/runtime/background_verification.py +178 -0
- package/runtime/budget_envelopes.py +398 -0
- package/runtime/business_workflow.py +234 -0
- package/runtime/canonical_surface.py +53 -0
- package/runtime/canonical_taxonomy.py +27 -0
- package/runtime/claim_judge.py +648 -0
- package/runtime/cli_provider.py +105 -0
- package/runtime/compat.py +2222 -0
- package/runtime/complexity_scorer.py +148 -0
- package/runtime/compliance_governor.py +505 -0
- package/runtime/config_transaction.py +304 -0
- package/runtime/context_compiler.py +131 -0
- package/runtime/context_engine.py +708 -0
- package/runtime/context_limits.py +363 -0
- package/runtime/contract_compiler.py +3664 -0
- package/runtime/custom_agent_loader.py +366 -0
- package/runtime/data_lineage.py +244 -0
- package/runtime/defense_state.py +261 -0
- package/runtime/delta_classifier.py +231 -0
- package/runtime/dispatcher.py +47 -0
- package/runtime/doc_generator.py +319 -0
- package/runtime/domain_packs.py +75 -0
- package/runtime/ecosystem.py +371 -0
- package/runtime/equalizer.py +268 -0
- package/runtime/eval_gate.py +96 -0
- package/runtime/evidence_narrator.py +147 -0
- package/runtime/evidence_query.py +303 -0
- package/runtime/evidence_registry.py +16 -0
- package/runtime/evidence_requirements.py +157 -0
- package/runtime/exec_kernel.py +267 -0
- package/runtime/explainer_formatter.py +82 -0
- package/runtime/feature_registry.py +109 -0
- package/runtime/forge_agents.py +915 -0
- package/runtime/forge_contracts.py +519 -0
- package/runtime/forge_domains.py +68 -0
- package/runtime/forge_run_id.py +86 -0
- package/runtime/guide_assert.py +135 -0
- package/runtime/hook_governor.py +156 -0
- package/runtime/host_parity.py +373 -0
- package/runtime/incident_replay.py +310 -0
- package/runtime/install_planner.py +617 -0
- package/runtime/interaction_journal.py +566 -0
- package/runtime/issue_surface.py +472 -0
- package/runtime/legacy_compat.py +7 -0
- package/runtime/mcp_config_writers.py +360 -0
- package/runtime/mcp_lifecycle.py +175 -0
- package/runtime/mcp_memory_server.py +220 -0
- package/runtime/memory_parsers/__init__.py +0 -0
- package/runtime/memory_parsers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/chatgpt_parser.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/claude_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/export.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/gemini_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/__pycache__/kimi_import.cpython-313.pyc +0 -0
- package/runtime/memory_parsers/chatgpt_parser.py +257 -0
- package/runtime/memory_parsers/claude_import.py +107 -0
- package/runtime/memory_parsers/export.py +97 -0
- package/runtime/memory_parsers/gemini_import.py +91 -0
- package/runtime/memory_parsers/kimi_import.py +91 -0
- package/runtime/memory_store.py +1182 -0
- package/runtime/merge_writer.py +445 -0
- package/runtime/music_omr_testbed.py +336 -0
- package/runtime/mutation_gate.py +320 -0
- package/runtime/omc_compat.py +7 -0
- package/runtime/omg_browser_cli.py +95 -0
- package/runtime/omg_compat_contract_snapshot.json +936 -0
- package/runtime/omg_contract_snapshot.json +936 -0
- package/runtime/omg_mcp_server.py +306 -0
- package/runtime/playwright_adapter.py +39 -0
- package/runtime/playwright_pack.py +253 -0
- package/runtime/plugin_diagnostics.py +308 -0
- package/runtime/plugin_interop.py +1060 -0
- package/runtime/policy_pack_loader.py +147 -0
- package/runtime/preflight.py +135 -0
- package/runtime/profile_io.py +328 -0
- package/runtime/proof_chain.py +472 -0
- package/runtime/proof_gate.py +442 -0
- package/runtime/provider_parity_eval.py +109 -0
- package/runtime/providers/__init__.py +0 -0
- package/runtime/providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/codex_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/gemini_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/kimi_provider.cpython-313.pyc +0 -0
- package/runtime/providers/__pycache__/opencode_provider.cpython-313.pyc +0 -0
- package/runtime/providers/codex_provider.py +129 -0
- package/runtime/providers/gemini_provider.py +143 -0
- package/runtime/providers/kimi_provider.py +167 -0
- package/runtime/providers/opencode_provider.py +99 -0
- package/runtime/release_artifact_audit.py +556 -0
- package/runtime/release_run_coordinator.py +574 -0
- package/runtime/release_surface_compiler.py +643 -0
- package/runtime/release_surface_registry.py +283 -0
- package/runtime/release_surfaces.py +320 -0
- package/runtime/remote_supervisor.py +79 -0
- package/runtime/repro_pack.py +398 -0
- package/runtime/rollback_manifest.py +143 -0
- package/runtime/router_critics.py +229 -0
- package/runtime/router_executor.py +142 -0
- package/runtime/router_selector.py +99 -0
- package/runtime/runtime_contracts.py +292 -0
- package/runtime/runtime_profile.py +133 -0
- package/runtime/security_check.py +1094 -0
- package/runtime/session_health.py +546 -0
- package/runtime/skill_evolution.py +221 -0
- package/runtime/skill_registry.py +53 -0
- package/runtime/subagent_dispatcher.py +604 -0
- package/runtime/subscription_tiers.py +258 -0
- package/runtime/team_router.py +1399 -0
- package/runtime/test_intent_lock.py +543 -0
- package/runtime/tmux_session_manager.py +172 -0
- package/runtime/tool_fabric.py +570 -0
- package/runtime/tool_plan_gate.py +460 -0
- package/runtime/tracebank.py +125 -0
- package/runtime/untrusted_content.py +360 -0
- package/runtime/validate.py +293 -0
- package/runtime/verdict_schema.py +198 -0
- package/runtime/verification_controller.py +235 -0
- package/runtime/verification_loop.py +73 -0
- package/runtime/vision_artifacts.py +31 -0
- package/runtime/vision_cache.py +38 -0
- package/runtime/vision_jobs.py +92 -0
- package/runtime/worker_watchdog.py +526 -0
- package/scripts/__pycache__/audit-published-artifact.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-doc-parity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/check-omg-standalone-clean.cpython-313.pyc +0 -0
- package/scripts/__pycache__/github_review_helpers.cpython-313.pyc +0 -0
- package/scripts/__pycache__/omg.cpython-313.pyc +0 -0
- package/scripts/__pycache__/prepare-release-proof-fixtures.cpython-313.pyc +0 -0
- package/scripts/__pycache__/sync-release-identity.cpython-313.pyc +0 -0
- package/scripts/__pycache__/validate-release-identity.cpython-313.pyc +0 -0
- package/scripts/audit-published-artifact.py +59 -0
- package/scripts/check-omg-compat-contract-snapshot.py +137 -0
- package/scripts/check-omg-contract-snapshot.py +12 -0
- package/scripts/check-omg-public-ready.py +273 -0
- package/scripts/check-omg-standalone-clean.py +133 -0
- package/scripts/emit_host_parity.py +72 -0
- package/scripts/legacy_to_omg_migrate.py +29 -0
- package/scripts/migrate-legacy.py +464 -0
- package/scripts/omc_to_omg_migrate.py +12 -0
- package/scripts/omg.py +2962 -0
- package/scripts/pre-release-check.sh +38 -0
- package/scripts/prepare-release-proof-fixtures.py +602 -0
- package/scripts/print-canonical-version.py +80 -0
- package/scripts/settings-merge.py +289 -0
- package/scripts/sync-release-identity.py +481 -0
- package/scripts/validate-release-identity.py +632 -0
- package/scripts/verify-no-omc.sh +5 -0
- package/scripts/verify-standalone.sh +35 -0
- package/settings.json +751 -0
- package/tools/__init__.py +2 -0
- package/tools/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_consent.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_stealth.cpython-313.pyc +0 -0
- package/tools/__pycache__/browser_tool.cpython-313.pyc +0 -0
- package/tools/__pycache__/changelog_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/commit_splitter.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_discovery.cpython-313.pyc +0 -0
- package/tools/__pycache__/config_merger.cpython-313.pyc +0 -0
- package/tools/__pycache__/dashboard_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/git_inspector.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_client.cpython-313.pyc +0 -0
- package/tools/__pycache__/lsp_operations.cpython-313.pyc +0 -0
- package/tools/__pycache__/pr_generator.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_repl.cpython-313.pyc +0 -0
- package/tools/__pycache__/python_sandbox.cpython-313.pyc +0 -0
- package/tools/__pycache__/session_snapshot.cpython-313.pyc +0 -0
- package/tools/__pycache__/ssh_manager.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_engine.cpython-313.pyc +0 -0
- package/tools/__pycache__/theme_selector.cpython-313.pyc +0 -0
- package/tools/__pycache__/web_search.cpython-313.pyc +0 -0
- package/tools/browser_consent.py +289 -0
- package/tools/browser_stealth.py +481 -0
- package/tools/browser_tool.py +448 -0
- package/tools/changelog_generator.py +347 -0
- package/tools/commit_splitter.py +749 -0
- package/tools/config_discovery.py +151 -0
- package/tools/config_merger.py +449 -0
- package/tools/dashboard_generator.py +300 -0
- package/tools/git_inspector.py +298 -0
- package/tools/lsp_client.py +275 -0
- package/tools/lsp_discovery.py +231 -0
- package/tools/lsp_operations.py +392 -0
- package/tools/pr_generator.py +404 -0
- package/tools/python_repl.py +712 -0
- package/tools/python_sandbox.py +768 -0
- package/tools/search_providers/__init__.py +77 -0
- package/tools/search_providers/__pycache__/__init__.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/brave.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/exa.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/jina.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/perplexity.cpython-313.pyc +0 -0
- package/tools/search_providers/__pycache__/synthetic.cpython-313.pyc +0 -0
- package/tools/search_providers/brave.py +115 -0
- package/tools/search_providers/exa.py +116 -0
- package/tools/search_providers/jina.py +104 -0
- package/tools/search_providers/perplexity.py +139 -0
- package/tools/search_providers/synthetic.py +74 -0
- package/tools/session_snapshot.py +851 -0
- package/tools/ssh_manager.py +912 -0
- package/tools/theme_engine.py +296 -0
- package/tools/theme_selector.py +137 -0
- package/tools/web_search.py +675 -0
|
@@ -0,0 +1,3664 @@
|
|
|
1
|
+
"""Canonical OMG contract registry, compiler, and release-readiness checks."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import hashlib
|
|
5
|
+
import asyncio
|
|
6
|
+
from concurrent.futures import ThreadPoolExecutor, TimeoutError as FuturesTimeoutError
|
|
7
|
+
import importlib
|
|
8
|
+
import importlib.util
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import time
|
|
12
|
+
from datetime import datetime, timedelta, timezone
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
import re
|
|
15
|
+
import shutil
|
|
16
|
+
import subprocess
|
|
17
|
+
import sys
|
|
18
|
+
import tempfile
|
|
19
|
+
from typing import Any, Iterable
|
|
20
|
+
from urllib.parse import urlparse
|
|
21
|
+
import zipfile
|
|
22
|
+
|
|
23
|
+
import yaml
|
|
24
|
+
|
|
25
|
+
from runtime.asset_loader import resolve_asset, resolve_assets
|
|
26
|
+
from runtime.proof_chain import _normalize_evidence_pack
|
|
27
|
+
from runtime.evidence_requirements import requirements_for_profile
|
|
28
|
+
from runtime.runtime_contracts import schema_versions
|
|
29
|
+
from runtime.compliance_governor import evaluate_release_compliance
|
|
30
|
+
from runtime.release_run_coordinator import get_active_coordinator_run_id, is_release_orchestration_active
|
|
31
|
+
from runtime.release_surfaces import get_package_parity_surfaces, get_runtime_behavior_surfaces
|
|
32
|
+
from runtime.release_surface_registry import get_public_surfaces, validate_registry
|
|
33
|
+
from runtime.release_surface_compiler import compile_release_surfaces
|
|
34
|
+
from runtime.doc_generator import check_docs
|
|
35
|
+
from runtime.worker_watchdog import get_worker_watchdog
|
|
36
|
+
from runtime.adoption import (
|
|
37
|
+
CANONICAL_MARKETPLACE_ID,
|
|
38
|
+
CANONICAL_PACKAGE_NAME,
|
|
39
|
+
CANONICAL_PLUGIN_ID,
|
|
40
|
+
CANONICAL_REPO_URL,
|
|
41
|
+
CANONICAL_VERSION,
|
|
42
|
+
)
|
|
43
|
+
from runtime.canonical_taxonomy import CANONICAL_PRESETS, POLICY_PACK_IDS, RELEASE_CHANNELS
|
|
44
|
+
from registry.approval_artifact import verify_approval_artifact
|
|
45
|
+
from runtime.canonical_surface import get_canonical_hosts, get_compat_hosts
|
|
46
|
+
from registry.verify_artifact import sign_artifact_statement, verify_artifact_statement, _load_trusted_signers
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
CONTRACT_DOC_PATH = Path("OMG_COMPAT_CONTRACT.md")
|
|
50
|
+
SCHEMA_PATH = Path("registry") / "omg-capability.schema.json"
|
|
51
|
+
BUNDLES_DIR = Path("registry") / "bundles"
|
|
52
|
+
SUPPORTED_HOSTS = tuple(get_canonical_hosts())
|
|
53
|
+
RELEASE_BLOCKING_HOSTS = tuple(get_canonical_hosts())
|
|
54
|
+
# Compatibility-only hosts (for example, OpenCode) are intentionally excluded from
|
|
55
|
+
# release-blocking parity and compile-readiness requirements.
|
|
56
|
+
COMPATIBILITY_ONLY_HOSTS = tuple(get_compat_hosts())
|
|
57
|
+
SUPPORTED_PRESETS = CANONICAL_PRESETS
|
|
58
|
+
SUPPORTED_CHANNELS = RELEASE_CHANNELS
|
|
59
|
+
DEFAULT_REQUIRED_BUNDLES = (
|
|
60
|
+
"control-plane",
|
|
61
|
+
"plan-council",
|
|
62
|
+
"claim-judge",
|
|
63
|
+
"test-intent-lock",
|
|
64
|
+
"proof-gate",
|
|
65
|
+
"hook-governor",
|
|
66
|
+
"mcp-fabric",
|
|
67
|
+
"lsp-pack",
|
|
68
|
+
"secure-worktree-pipeline",
|
|
69
|
+
"security-check",
|
|
70
|
+
"api-twin",
|
|
71
|
+
"preflight",
|
|
72
|
+
"robotics",
|
|
73
|
+
"vision",
|
|
74
|
+
"algorithms",
|
|
75
|
+
"health",
|
|
76
|
+
"tracebank",
|
|
77
|
+
"eval-gate",
|
|
78
|
+
"delta-classifier",
|
|
79
|
+
"incident-replay",
|
|
80
|
+
"data-lineage",
|
|
81
|
+
"remote-supervisor",
|
|
82
|
+
)
|
|
83
|
+
TRUTH_COUNCIL_BUNDLES = (
|
|
84
|
+
"plan-council",
|
|
85
|
+
"claim-judge",
|
|
86
|
+
"test-intent-lock",
|
|
87
|
+
"proof-gate",
|
|
88
|
+
)
|
|
89
|
+
def _get_required_advanced_plugin_artifacts(root: Path) -> tuple[str, ...]:
|
|
90
|
+
manifest_path = root / "plugins" / "advanced" / "plugin.json"
|
|
91
|
+
try:
|
|
92
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
93
|
+
except (OSError, json.JSONDecodeError):
|
|
94
|
+
return ()
|
|
95
|
+
|
|
96
|
+
required: list[str] = ["bundle/plugins/advanced/plugin.json"]
|
|
97
|
+
seen = set(required)
|
|
98
|
+
commands = manifest.get("commands", {})
|
|
99
|
+
if not isinstance(commands, dict):
|
|
100
|
+
return tuple(required)
|
|
101
|
+
|
|
102
|
+
for command in commands.values():
|
|
103
|
+
if not isinstance(command, dict):
|
|
104
|
+
continue
|
|
105
|
+
command_path = command.get("path")
|
|
106
|
+
if not isinstance(command_path, str) or not command_path:
|
|
107
|
+
continue
|
|
108
|
+
safe_command_path = command_path.replace(":", "-")
|
|
109
|
+
bundled_path = f"bundle/plugins/advanced/{safe_command_path}"
|
|
110
|
+
if bundled_path in seen:
|
|
111
|
+
continue
|
|
112
|
+
required.append(bundled_path)
|
|
113
|
+
seen.add(bundled_path)
|
|
114
|
+
return tuple(required)
|
|
115
|
+
REQUIRED_DOC_TOKENS = (
|
|
116
|
+
"execution_contract",
|
|
117
|
+
"tool_policy",
|
|
118
|
+
"invocation_policy",
|
|
119
|
+
"host_compilation_rules",
|
|
120
|
+
"local_supervisor",
|
|
121
|
+
)
|
|
122
|
+
REQUIRED_BUNDLE_FIELDS = (
|
|
123
|
+
"id",
|
|
124
|
+
"kind",
|
|
125
|
+
"version",
|
|
126
|
+
"title",
|
|
127
|
+
"description",
|
|
128
|
+
"hosts",
|
|
129
|
+
"assets",
|
|
130
|
+
"invocation_policy",
|
|
131
|
+
"tool_policy",
|
|
132
|
+
"lifecycle_hooks",
|
|
133
|
+
"mcp_contract",
|
|
134
|
+
"lsp_contract",
|
|
135
|
+
"evidence_outputs",
|
|
136
|
+
"execution_contract",
|
|
137
|
+
"channel_overrides",
|
|
138
|
+
)
|
|
139
|
+
REQUIRED_POLICY_MODEL_FIELDS = (
|
|
140
|
+
"trust_tiers",
|
|
141
|
+
"tool_policies",
|
|
142
|
+
"protected_paths",
|
|
143
|
+
"evidence_contract",
|
|
144
|
+
"host_rules",
|
|
145
|
+
)
|
|
146
|
+
REQUIRED_CLAUDE_HOOK_EVENTS = (
|
|
147
|
+
"UserPromptSubmit",
|
|
148
|
+
"PreToolUse",
|
|
149
|
+
"PostToolUse",
|
|
150
|
+
"PostToolUseFailure",
|
|
151
|
+
"InstructionsLoaded",
|
|
152
|
+
)
|
|
153
|
+
REQUIRED_CLAUDE_SUBAGENT_NAMES = (
|
|
154
|
+
"architect-planner",
|
|
155
|
+
"explorer-indexer",
|
|
156
|
+
"implementer",
|
|
157
|
+
"security-reviewer",
|
|
158
|
+
"verifier",
|
|
159
|
+
"causal-tracer",
|
|
160
|
+
)
|
|
161
|
+
REQUIRED_CODEX_AGENTS_SECTIONS = (
|
|
162
|
+
"## Build & Test",
|
|
163
|
+
"## Protected Paths",
|
|
164
|
+
"## Evidence Contract",
|
|
165
|
+
"## Release Audit",
|
|
166
|
+
"## Required Skills",
|
|
167
|
+
"## Web Search Policy",
|
|
168
|
+
"## Approval Constraints",
|
|
169
|
+
)
|
|
170
|
+
REQUIRED_CODEX_OUTPUTS = (
|
|
171
|
+
"AGENTS.fragment.md",
|
|
172
|
+
"codex-rules.md",
|
|
173
|
+
"codex-mcp.toml",
|
|
174
|
+
)
|
|
175
|
+
HOST_COMPILED_ARTIFACTS = {
|
|
176
|
+
"claude": (
|
|
177
|
+
".claude-plugin/plugin.json",
|
|
178
|
+
".claude-plugin/marketplace.json",
|
|
179
|
+
".mcp.json",
|
|
180
|
+
"settings.json",
|
|
181
|
+
),
|
|
182
|
+
"codex": (
|
|
183
|
+
".agents/skills/omg/AGENTS.fragment.md",
|
|
184
|
+
".agents/skills/omg/codex-rules.md",
|
|
185
|
+
".agents/skills/omg/codex-mcp.toml",
|
|
186
|
+
),
|
|
187
|
+
"gemini": (
|
|
188
|
+
".gemini/settings.json",
|
|
189
|
+
),
|
|
190
|
+
"kimi": (
|
|
191
|
+
".kimi/mcp.json",
|
|
192
|
+
),
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
_HOST_POLICY_REQUIRED_FIELDS: dict[str, tuple[str, ...]] = {
|
|
196
|
+
"claude": ("compilation_targets", "hooks", "subagents", "skills"),
|
|
197
|
+
"codex": ("compilation_targets", "skills", "agents_fragments", "rules", "automations"),
|
|
198
|
+
"gemini": ("compilation_targets", "mcp", "skills", "automations"),
|
|
199
|
+
"kimi": ("compilation_targets", "mcp", "skills", "automations"),
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
_REQUIRED_EXECUTION_PRIMITIVES = (
|
|
203
|
+
"release_run_coordinator_state",
|
|
204
|
+
"tdd_proof_chain_lock",
|
|
205
|
+
"rollback_manifest",
|
|
206
|
+
"intent_gate_state",
|
|
207
|
+
"profile_digest",
|
|
208
|
+
"session_health_state",
|
|
209
|
+
"council_verdicts",
|
|
210
|
+
"forge_starter_proof",
|
|
211
|
+
"claim_judge_outcome",
|
|
212
|
+
"compliance_governor_outcome",
|
|
213
|
+
"exec_kernel_state",
|
|
214
|
+
"worker_watchdog_replay",
|
|
215
|
+
"merge_writer_provenance",
|
|
216
|
+
"write_lease_provenance",
|
|
217
|
+
"tool_fabric_ledger",
|
|
218
|
+
"budget_envelope_state",
|
|
219
|
+
"issue_report",
|
|
220
|
+
"host_parity_report",
|
|
221
|
+
"music_omr_testbed_evidence",
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
_PHASE1_FORGE_SPECIALIST_SURFACES = (
|
|
225
|
+
"forge-profile-review",
|
|
226
|
+
"forge-release-audit",
|
|
227
|
+
"forge-validate",
|
|
228
|
+
)
|
|
229
|
+
|
|
230
|
+
_PHASE1_RELEASE_SURFACES = (
|
|
231
|
+
"OMG:profile-review",
|
|
232
|
+
"OMG:release-audit",
|
|
233
|
+
"OMG:validate",
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
_PHASE1_ATTESTATION_REQUIREMENTS = (
|
|
237
|
+
"registry.verify_artifact.sign_artifact_statement",
|
|
238
|
+
"registry.verify_artifact.verify_artifact_statement",
|
|
239
|
+
)
|
|
240
|
+
|
|
241
|
+
_PHASE1_COMPLIANCE_EXPECTATIONS = (
|
|
242
|
+
"runtime.compliance_governor.evaluate_release_compliance",
|
|
243
|
+
"runtime.compliance_governor.evaluate_tool_compliance",
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
_PHASE1_RELEASE_READINESS_CHECKS = (
|
|
247
|
+
"claim_judge",
|
|
248
|
+
"compliance_governor",
|
|
249
|
+
"execution_primitives",
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
_PHASE1_RUNTIME_BEHAVIOR_SURFACES = tuple(get_runtime_behavior_surfaces())
|
|
253
|
+
|
|
254
|
+
_REQUIRED_CONTEXT_METADATA = (
|
|
255
|
+
"context_checksum",
|
|
256
|
+
"profile_version",
|
|
257
|
+
"intent_gate_version",
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
_DEFAULT_EXECUTION_PRIMITIVE_MAX_AGE_SECONDS = 3600.0
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
def _ensure_list(
|
|
264
|
+
*,
|
|
265
|
+
bundle_id: str,
|
|
266
|
+
path: str,
|
|
267
|
+
value: Any,
|
|
268
|
+
errors: list[str],
|
|
269
|
+
min_items: int = 1,
|
|
270
|
+
) -> list[Any]:
|
|
271
|
+
if not isinstance(value, list):
|
|
272
|
+
errors.append(f"{bundle_id}: {path} must be a list")
|
|
273
|
+
return []
|
|
274
|
+
if len(value) < min_items:
|
|
275
|
+
errors.append(f"{bundle_id}: {path} must contain at least {min_items} item(s)")
|
|
276
|
+
return value
|
|
277
|
+
|
|
278
|
+
|
|
279
|
+
def _ensure_dict(*, bundle_id: str, path: str, value: Any, errors: list[str]) -> dict[str, Any]:
|
|
280
|
+
if not isinstance(value, dict):
|
|
281
|
+
errors.append(f"{bundle_id}: {path} must be an object")
|
|
282
|
+
return {}
|
|
283
|
+
return value
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _validate_host_rule(
|
|
287
|
+
*,
|
|
288
|
+
bundle_id: str,
|
|
289
|
+
host_name: str,
|
|
290
|
+
host_rule: Any,
|
|
291
|
+
required_fields: tuple[str, ...],
|
|
292
|
+
errors: list[str],
|
|
293
|
+
) -> None:
|
|
294
|
+
path = f"policy_model.host_rules.{host_name}"
|
|
295
|
+
host_payload = _ensure_dict(bundle_id=bundle_id, path=path, value=host_rule, errors=errors)
|
|
296
|
+
if not host_payload:
|
|
297
|
+
return
|
|
298
|
+
for field in required_fields:
|
|
299
|
+
if field not in host_payload:
|
|
300
|
+
errors.append(f"{bundle_id}: malformed host_rules entry for {host_name}: missing '{field}'")
|
|
301
|
+
continue
|
|
302
|
+
_ensure_list(
|
|
303
|
+
bundle_id=bundle_id,
|
|
304
|
+
path=f"{path}.{field}",
|
|
305
|
+
value=host_payload[field],
|
|
306
|
+
errors=errors,
|
|
307
|
+
min_items=1,
|
|
308
|
+
)
|
|
309
|
+
|
|
310
|
+
|
|
311
|
+
def _validate_policy_model(
|
|
312
|
+
bundle_id: str,
|
|
313
|
+
policy_model: Any,
|
|
314
|
+
*,
|
|
315
|
+
bundle_hosts: Iterable[str] = (),
|
|
316
|
+
) -> list[str]:
|
|
317
|
+
errors: list[str] = []
|
|
318
|
+
payload = _ensure_dict(bundle_id=bundle_id, path="policy_model", value=policy_model, errors=errors)
|
|
319
|
+
if not payload:
|
|
320
|
+
return errors
|
|
321
|
+
|
|
322
|
+
for field in REQUIRED_POLICY_MODEL_FIELDS:
|
|
323
|
+
if field not in payload:
|
|
324
|
+
errors.append(f"{bundle_id}: policy_model missing field {field}")
|
|
325
|
+
|
|
326
|
+
tier_names: set[str] = set()
|
|
327
|
+
for index, tier in enumerate(
|
|
328
|
+
_ensure_list(
|
|
329
|
+
bundle_id=bundle_id,
|
|
330
|
+
path="policy_model.trust_tiers",
|
|
331
|
+
value=payload.get("trust_tiers", []),
|
|
332
|
+
errors=errors,
|
|
333
|
+
)
|
|
334
|
+
):
|
|
335
|
+
tier_payload = _ensure_dict(
|
|
336
|
+
bundle_id=bundle_id,
|
|
337
|
+
path=f"policy_model.trust_tiers[{index}]",
|
|
338
|
+
value=tier,
|
|
339
|
+
errors=errors,
|
|
340
|
+
)
|
|
341
|
+
if not tier_payload:
|
|
342
|
+
continue
|
|
343
|
+
for field in ("name", "level", "label", "allowed_sources"):
|
|
344
|
+
if field not in tier_payload:
|
|
345
|
+
errors.append(f"{bundle_id}: policy_model.trust_tiers[{index}] missing field {field}")
|
|
346
|
+
if isinstance(tier_payload.get("name"), str) and tier_payload["name"].strip():
|
|
347
|
+
tier_names.add(tier_payload["name"].strip())
|
|
348
|
+
if "allowed_sources" in tier_payload:
|
|
349
|
+
_ensure_list(
|
|
350
|
+
bundle_id=bundle_id,
|
|
351
|
+
path=f"policy_model.trust_tiers[{index}].allowed_sources",
|
|
352
|
+
value=tier_payload.get("allowed_sources"),
|
|
353
|
+
errors=errors,
|
|
354
|
+
min_items=1,
|
|
355
|
+
)
|
|
356
|
+
|
|
357
|
+
for index, tool in enumerate(
|
|
358
|
+
_ensure_list(
|
|
359
|
+
bundle_id=bundle_id,
|
|
360
|
+
path="policy_model.tool_policies",
|
|
361
|
+
value=payload.get("tool_policies", []),
|
|
362
|
+
errors=errors,
|
|
363
|
+
)
|
|
364
|
+
):
|
|
365
|
+
tool_payload = _ensure_dict(
|
|
366
|
+
bundle_id=bundle_id,
|
|
367
|
+
path=f"policy_model.tool_policies[{index}]",
|
|
368
|
+
value=tool,
|
|
369
|
+
errors=errors,
|
|
370
|
+
)
|
|
371
|
+
if not tool_payload:
|
|
372
|
+
continue
|
|
373
|
+
for field in ("tool_name", "allowed_tiers", "requires_approval"):
|
|
374
|
+
if field not in tool_payload:
|
|
375
|
+
errors.append(f"{bundle_id}: policy_model.tool_policies[{index}] missing field {field}")
|
|
376
|
+
allowed_tiers = _ensure_list(
|
|
377
|
+
bundle_id=bundle_id,
|
|
378
|
+
path=f"policy_model.tool_policies[{index}].allowed_tiers",
|
|
379
|
+
value=tool_payload.get("allowed_tiers", []),
|
|
380
|
+
errors=errors,
|
|
381
|
+
min_items=1,
|
|
382
|
+
)
|
|
383
|
+
if tier_names:
|
|
384
|
+
unknown_tiers = sorted(
|
|
385
|
+
tier_name
|
|
386
|
+
for tier_name in allowed_tiers
|
|
387
|
+
if isinstance(tier_name, str) and tier_name not in tier_names
|
|
388
|
+
)
|
|
389
|
+
if unknown_tiers:
|
|
390
|
+
errors.append(
|
|
391
|
+
f"{bundle_id}: policy_model.tool_policies[{index}] references unknown tiers {unknown_tiers}"
|
|
392
|
+
)
|
|
393
|
+
|
|
394
|
+
for index, item in enumerate(
|
|
395
|
+
_ensure_list(
|
|
396
|
+
bundle_id=bundle_id,
|
|
397
|
+
path="policy_model.protected_paths",
|
|
398
|
+
value=payload.get("protected_paths", []),
|
|
399
|
+
errors=errors,
|
|
400
|
+
)
|
|
401
|
+
):
|
|
402
|
+
path_payload = _ensure_dict(
|
|
403
|
+
bundle_id=bundle_id,
|
|
404
|
+
path=f"policy_model.protected_paths[{index}]",
|
|
405
|
+
value=item,
|
|
406
|
+
errors=errors,
|
|
407
|
+
)
|
|
408
|
+
if not path_payload:
|
|
409
|
+
continue
|
|
410
|
+
for field in ("path_pattern", "required_tier"):
|
|
411
|
+
if field not in path_payload:
|
|
412
|
+
errors.append(f"{bundle_id}: policy_model.protected_paths[{index}] missing field {field}")
|
|
413
|
+
required_tier = path_payload.get("required_tier")
|
|
414
|
+
if tier_names and isinstance(required_tier, str) and required_tier not in tier_names:
|
|
415
|
+
errors.append(
|
|
416
|
+
f"{bundle_id}: policy_model.protected_paths[{index}] references unknown tier '{required_tier}'"
|
|
417
|
+
)
|
|
418
|
+
|
|
419
|
+
evidence_contract = _ensure_dict(
|
|
420
|
+
bundle_id=bundle_id,
|
|
421
|
+
path="policy_model.evidence_contract",
|
|
422
|
+
value=payload.get("evidence_contract", {}),
|
|
423
|
+
errors=errors,
|
|
424
|
+
)
|
|
425
|
+
for field in ("timestamp", "executor", "trace_id", "lineage"):
|
|
426
|
+
if field not in evidence_contract:
|
|
427
|
+
errors.append(f"{bundle_id}: policy_model.evidence_contract missing field {field}")
|
|
428
|
+
|
|
429
|
+
host_rules = _ensure_dict(
|
|
430
|
+
bundle_id=bundle_id,
|
|
431
|
+
path="policy_model.host_rules",
|
|
432
|
+
value=payload.get("host_rules", {}),
|
|
433
|
+
errors=errors,
|
|
434
|
+
)
|
|
435
|
+
declared_hosts = {str(host).strip() for host in bundle_hosts if str(host).strip()}
|
|
436
|
+
|
|
437
|
+
for host_name in get_canonical_hosts():
|
|
438
|
+
if host_name not in host_rules and host_name not in declared_hosts:
|
|
439
|
+
continue
|
|
440
|
+
required_fields = _HOST_POLICY_REQUIRED_FIELDS.get(host_name)
|
|
441
|
+
if required_fields is None:
|
|
442
|
+
continue
|
|
443
|
+
_validate_host_rule(
|
|
444
|
+
bundle_id=bundle_id,
|
|
445
|
+
host_name=host_name,
|
|
446
|
+
host_rule=host_rules.get(host_name),
|
|
447
|
+
required_fields=required_fields,
|
|
448
|
+
errors=errors,
|
|
449
|
+
)
|
|
450
|
+
return errors
|
|
451
|
+
|
|
452
|
+
|
|
453
|
+
def _policy_model_for_bundle(bundles: Iterable[dict[str, Any]], bundle_id: str) -> dict[str, Any] | None:
|
|
454
|
+
for bundle in bundles:
|
|
455
|
+
if str(bundle.get("id", "")) == bundle_id and isinstance(bundle.get("policy_model"), dict):
|
|
456
|
+
return dict(bundle["policy_model"])
|
|
457
|
+
return None
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _policy_protected_paths(policy_model: dict[str, Any] | None, *, channel: str) -> list[str]:
|
|
461
|
+
if not policy_model:
|
|
462
|
+
return _protected_paths_for_channel(channel)
|
|
463
|
+
values: list[str] = []
|
|
464
|
+
for item in policy_model.get("protected_paths", []):
|
|
465
|
+
if isinstance(item, dict):
|
|
466
|
+
pattern = str(item.get("path_pattern", "")).strip()
|
|
467
|
+
if pattern:
|
|
468
|
+
values.append(pattern)
|
|
469
|
+
return values or _protected_paths_for_channel(channel)
|
|
470
|
+
|
|
471
|
+
|
|
472
|
+
def _resolve_root(root_dir: str | Path | None) -> Path:
|
|
473
|
+
if root_dir is None:
|
|
474
|
+
return Path(__file__).resolve().parents[1]
|
|
475
|
+
return Path(root_dir).resolve()
|
|
476
|
+
|
|
477
|
+
|
|
478
|
+
def _resolve_output_root(root_dir: Path, output_root: str | Path | None) -> Path:
|
|
479
|
+
if output_root is None or str(output_root).strip() == "":
|
|
480
|
+
return root_dir
|
|
481
|
+
return Path(output_root).resolve()
|
|
482
|
+
|
|
483
|
+
|
|
484
|
+
def _load_json(path: Path) -> dict[str, Any]:
|
|
485
|
+
parsed = json.loads(path.read_text(encoding="utf-8"))
|
|
486
|
+
if not isinstance(parsed, dict):
|
|
487
|
+
raise ValueError(f"Expected JSON object in {path}")
|
|
488
|
+
return parsed
|
|
489
|
+
|
|
490
|
+
|
|
491
|
+
def _write_json(path: Path, payload: dict[str, Any]) -> None:
|
|
492
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
493
|
+
path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
def _write_text(path: Path, content: str) -> None:
|
|
497
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
498
|
+
path.write_text(content, encoding="utf-8")
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _build_phase1_release_contract() -> dict[str, list[str]]:
|
|
502
|
+
return {
|
|
503
|
+
"forge_specialist_surfaces": list(_PHASE1_FORGE_SPECIALIST_SURFACES),
|
|
504
|
+
"release_surfaces": list(_PHASE1_RELEASE_SURFACES),
|
|
505
|
+
"attestation_requirements": list(_PHASE1_ATTESTATION_REQUIREMENTS),
|
|
506
|
+
"compliance_governor_expectations": list(_PHASE1_COMPLIANCE_EXPECTATIONS),
|
|
507
|
+
"release_readiness_checks": list(_PHASE1_RELEASE_READINESS_CHECKS),
|
|
508
|
+
"runtime_behavior_surfaces": list(_PHASE1_RUNTIME_BEHAVIOR_SURFACES),
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
def _sha256_file(path: Path) -> str:
|
|
513
|
+
digest = hashlib.sha256()
|
|
514
|
+
with path.open("rb") as handle:
|
|
515
|
+
for chunk in iter(lambda: handle.read(65536), b""):
|
|
516
|
+
digest.update(chunk)
|
|
517
|
+
return digest.hexdigest()
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
def load_contract_doc(root_dir: str | Path | None = None) -> str:
|
|
521
|
+
if root_dir is not None:
|
|
522
|
+
root = _resolve_root(root_dir)
|
|
523
|
+
candidate = root / CONTRACT_DOC_PATH
|
|
524
|
+
if candidate.exists():
|
|
525
|
+
return candidate.read_text(encoding="utf-8")
|
|
526
|
+
return resolve_asset(CONTRACT_DOC_PATH).read_text(encoding="utf-8")
|
|
527
|
+
|
|
528
|
+
|
|
529
|
+
def load_contract_schema(root_dir: str | Path | None = None) -> dict[str, Any]:
|
|
530
|
+
if root_dir is not None:
|
|
531
|
+
root = _resolve_root(root_dir)
|
|
532
|
+
candidate = root / SCHEMA_PATH
|
|
533
|
+
if candidate.exists():
|
|
534
|
+
return _load_json(candidate)
|
|
535
|
+
return _load_json(resolve_asset(SCHEMA_PATH))
|
|
536
|
+
|
|
537
|
+
|
|
538
|
+
def load_contract_bundles(root_dir: str | Path | None = None) -> list[dict[str, Any]]:
|
|
539
|
+
root = _resolve_root(root_dir)
|
|
540
|
+
bundles: list[dict[str, Any]] = []
|
|
541
|
+
paths = sorted((root / BUNDLES_DIR).glob("*.yaml")) if (root / BUNDLES_DIR).exists() else resolve_assets(BUNDLES_DIR, suffix=".yaml")
|
|
542
|
+
for path in paths:
|
|
543
|
+
parsed = yaml.safe_load(path.read_text(encoding="utf-8"))
|
|
544
|
+
if not isinstance(parsed, dict):
|
|
545
|
+
raise ValueError(f"Expected mapping bundle manifest in {path}")
|
|
546
|
+
bundle = dict(parsed)
|
|
547
|
+
try:
|
|
548
|
+
bundle["_path"] = str(path.relative_to(root))
|
|
549
|
+
except ValueError:
|
|
550
|
+
bundle["_path"] = str(Path(BUNDLES_DIR) / path.name)
|
|
551
|
+
bundles.append(bundle)
|
|
552
|
+
return bundles
|
|
553
|
+
|
|
554
|
+
|
|
555
|
+
def _bundle_summary(bundle: dict[str, Any]) -> dict[str, Any]:
|
|
556
|
+
return {
|
|
557
|
+
"id": bundle.get("id", ""),
|
|
558
|
+
"kind": bundle.get("kind", ""),
|
|
559
|
+
"version": bundle.get("version", ""),
|
|
560
|
+
"title": bundle.get("title", ""),
|
|
561
|
+
"hosts": list(bundle.get("hosts", [])),
|
|
562
|
+
"path": bundle.get("_path", ""),
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def validate_contract_registry(root_dir: str | Path | None = None) -> dict[str, Any]:
|
|
567
|
+
root = _resolve_root(root_dir)
|
|
568
|
+
errors: list[str] = []
|
|
569
|
+
|
|
570
|
+
try:
|
|
571
|
+
doc_text = load_contract_doc(root)
|
|
572
|
+
except FileNotFoundError:
|
|
573
|
+
errors.append(f"missing contract doc: {CONTRACT_DOC_PATH}")
|
|
574
|
+
doc_text = ""
|
|
575
|
+
else:
|
|
576
|
+
for token in REQUIRED_DOC_TOKENS:
|
|
577
|
+
if token not in doc_text:
|
|
578
|
+
errors.append(f"contract doc missing token: {token}")
|
|
579
|
+
if CANONICAL_VERSION not in doc_text:
|
|
580
|
+
errors.append(f"contract doc missing version: {CANONICAL_VERSION}")
|
|
581
|
+
|
|
582
|
+
try:
|
|
583
|
+
schema_payload = load_contract_schema(root)
|
|
584
|
+
except FileNotFoundError:
|
|
585
|
+
errors.append(f"missing contract schema: {SCHEMA_PATH}")
|
|
586
|
+
schema_payload: dict[str, Any] = {}
|
|
587
|
+
else:
|
|
588
|
+
if str(schema_payload.get("version", "")) != CANONICAL_VERSION:
|
|
589
|
+
errors.append(f"contract schema version drift: {schema_payload.get('version')!r}")
|
|
590
|
+
|
|
591
|
+
bundles = load_contract_bundles(root)
|
|
592
|
+
if not bundles:
|
|
593
|
+
errors.append(f"missing bundles directory: {BUNDLES_DIR}")
|
|
594
|
+
|
|
595
|
+
bundle_ids = set()
|
|
596
|
+
bundle_summaries: list[dict[str, Any]] = []
|
|
597
|
+
for bundle in bundles:
|
|
598
|
+
bundle_summaries.append(_bundle_summary(bundle))
|
|
599
|
+
bundle_id = str(bundle.get("id", "")).strip()
|
|
600
|
+
if not bundle_id:
|
|
601
|
+
errors.append(f"bundle missing id: {bundle.get('_path', '<unknown>')}")
|
|
602
|
+
continue
|
|
603
|
+
if bundle_id in bundle_ids:
|
|
604
|
+
errors.append(f"duplicate bundle id: {bundle_id}")
|
|
605
|
+
bundle_ids.add(bundle_id)
|
|
606
|
+
for field in REQUIRED_BUNDLE_FIELDS:
|
|
607
|
+
if field not in bundle:
|
|
608
|
+
errors.append(f"{bundle_id}: missing field {field}")
|
|
609
|
+
if bundle.get("version") != CANONICAL_VERSION:
|
|
610
|
+
errors.append(f"{bundle_id}: version drift {bundle.get('version')!r}")
|
|
611
|
+
hosts = bundle.get("hosts", [])
|
|
612
|
+
if not isinstance(hosts, list) or not hosts:
|
|
613
|
+
errors.append(f"{bundle_id}: hosts must be a non-empty list")
|
|
614
|
+
else:
|
|
615
|
+
bad_hosts = [host for host in hosts if host not in SUPPORTED_HOSTS]
|
|
616
|
+
if bad_hosts:
|
|
617
|
+
errors.append(f"{bundle_id}: unsupported hosts {bad_hosts}")
|
|
618
|
+
if "policy_model" in bundle:
|
|
619
|
+
errors.extend(_validate_policy_model(bundle_id, bundle.get("policy_model"), bundle_hosts=hosts))
|
|
620
|
+
|
|
621
|
+
missing_bundles = [bundle_id for bundle_id in DEFAULT_REQUIRED_BUNDLES if bundle_id not in bundle_ids]
|
|
622
|
+
for bundle_id in missing_bundles:
|
|
623
|
+
errors.append(f"missing required bundle: {bundle_id}")
|
|
624
|
+
|
|
625
|
+
contract = {
|
|
626
|
+
"path": str(CONTRACT_DOC_PATH),
|
|
627
|
+
"schema_path": str(SCHEMA_PATH),
|
|
628
|
+
"version": CANONICAL_VERSION,
|
|
629
|
+
"bundle_count": len(bundle_summaries),
|
|
630
|
+
}
|
|
631
|
+
return {
|
|
632
|
+
"schema": "OmgContractValidationResult",
|
|
633
|
+
"status": "ok" if not errors else "error",
|
|
634
|
+
"contract": contract,
|
|
635
|
+
"bundles": bundle_summaries,
|
|
636
|
+
"errors": errors,
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
|
|
640
|
+
def _copy_contract_inputs(root: Path, output_root: Path) -> list[Path]:
|
|
641
|
+
copied: list[Path] = []
|
|
642
|
+
for rel_path in [CONTRACT_DOC_PATH, SCHEMA_PATH]:
|
|
643
|
+
src = resolve_asset(rel_path)
|
|
644
|
+
dst = output_root / rel_path
|
|
645
|
+
_write_text(dst, src.read_text(encoding="utf-8"))
|
|
646
|
+
copied.append(dst)
|
|
647
|
+
for bundle in load_contract_bundles(root):
|
|
648
|
+
rel_path = Path(str(bundle["_path"]))
|
|
649
|
+
src = resolve_asset(rel_path)
|
|
650
|
+
dst = output_root / rel_path
|
|
651
|
+
_write_text(dst, src.read_text(encoding="utf-8"))
|
|
652
|
+
copied.append(dst)
|
|
653
|
+
|
|
654
|
+
# Copy advanced plugin artifacts (plugin.json + all command markdown files)
|
|
655
|
+
advanced_plugin_json = Path("plugins") / "advanced" / "plugin.json"
|
|
656
|
+
try:
|
|
657
|
+
src = resolve_asset(advanced_plugin_json)
|
|
658
|
+
dst = output_root / advanced_plugin_json
|
|
659
|
+
_write_text(dst, src.read_text(encoding="utf-8"))
|
|
660
|
+
copied.append(dst)
|
|
661
|
+
except FileNotFoundError:
|
|
662
|
+
pass
|
|
663
|
+
|
|
664
|
+
advanced_commands = resolve_assets(Path("plugins") / "advanced" / "commands", suffix=".md")
|
|
665
|
+
for src in advanced_commands:
|
|
666
|
+
safe_name = src.name.replace(":", "-")
|
|
667
|
+
rel = Path("plugins") / "advanced" / "commands" / safe_name
|
|
668
|
+
dst = output_root / rel
|
|
669
|
+
_write_text(dst, src.read_text(encoding="utf-8"))
|
|
670
|
+
copied.append(dst)
|
|
671
|
+
|
|
672
|
+
return copied
|
|
673
|
+
|
|
674
|
+
|
|
675
|
+
def _base_mcp_servers() -> dict[str, Any]:
|
|
676
|
+
return {
|
|
677
|
+
"filesystem": {
|
|
678
|
+
"command": "npx",
|
|
679
|
+
"args": ["@modelcontextprotocol/server-filesystem@2026.1.14", "."],
|
|
680
|
+
},
|
|
681
|
+
"omg-control": {
|
|
682
|
+
"command": "python3",
|
|
683
|
+
"args": ["-m", "runtime.omg_mcp_server"],
|
|
684
|
+
},
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
|
|
688
|
+
def _plugin_mcp_servers() -> dict[str, Any]:
|
|
689
|
+
return {
|
|
690
|
+
"omg-control": {
|
|
691
|
+
"command": "python3",
|
|
692
|
+
"args": ["-m", "runtime.omg_mcp_server"],
|
|
693
|
+
},
|
|
694
|
+
}
|
|
695
|
+
|
|
696
|
+
|
|
697
|
+
def _build_claude_plugin() -> dict[str, Any]:
|
|
698
|
+
return {
|
|
699
|
+
"name": CANONICAL_PLUGIN_ID,
|
|
700
|
+
"version": CANONICAL_VERSION,
|
|
701
|
+
"description": "OMG plugin layer for Claude Code with native setup, orchestration, and interop.",
|
|
702
|
+
"author": {"name": "trac3r00"},
|
|
703
|
+
"repository": CANONICAL_REPO_URL,
|
|
704
|
+
"homepage": CANONICAL_REPO_URL,
|
|
705
|
+
"license": "MIT",
|
|
706
|
+
"keywords": [
|
|
707
|
+
"claude-code",
|
|
708
|
+
"plugin",
|
|
709
|
+
"orchestration",
|
|
710
|
+
"multi-agent",
|
|
711
|
+
"omg",
|
|
712
|
+
"codex",
|
|
713
|
+
"gemini",
|
|
714
|
+
"crazy-mode",
|
|
715
|
+
"escalation",
|
|
716
|
+
],
|
|
717
|
+
"mcpServers": "./.claude-plugin/mcp.json",
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
|
|
721
|
+
def _build_claude_marketplace() -> dict[str, Any]:
|
|
722
|
+
return {
|
|
723
|
+
"name": CANONICAL_MARKETPLACE_ID,
|
|
724
|
+
"description": "Marketplace metadata for the OMG Claude plugin",
|
|
725
|
+
"owner": {"name": "trac3r00"},
|
|
726
|
+
"metadata": {
|
|
727
|
+
"description": "OMG - Oh-My-God for Claude Code and supported agent hosts",
|
|
728
|
+
"version": CANONICAL_VERSION,
|
|
729
|
+
"homepage": CANONICAL_REPO_URL,
|
|
730
|
+
"repository": CANONICAL_REPO_URL,
|
|
731
|
+
},
|
|
732
|
+
"plugins": [
|
|
733
|
+
{
|
|
734
|
+
"name": CANONICAL_PLUGIN_ID,
|
|
735
|
+
"description": "OMG plugin layer for Claude Code and supported agent hosts with native setup, orchestration, and interop.",
|
|
736
|
+
"version": CANONICAL_VERSION,
|
|
737
|
+
"source": "./",
|
|
738
|
+
"author": {"name": "trac3r00"},
|
|
739
|
+
"license": "MIT",
|
|
740
|
+
"category": "productivity",
|
|
741
|
+
"tags": [
|
|
742
|
+
"orchestration",
|
|
743
|
+
"automation",
|
|
744
|
+
"multi-agent",
|
|
745
|
+
"omg",
|
|
746
|
+
"codex",
|
|
747
|
+
"gemini",
|
|
748
|
+
"crazy-mode",
|
|
749
|
+
],
|
|
750
|
+
}
|
|
751
|
+
],
|
|
752
|
+
"version": CANONICAL_VERSION,
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
|
|
756
|
+
def _bundle_map(bundles: Iterable[dict[str, Any]]) -> dict[str, dict[str, Any]]:
|
|
757
|
+
return {str(bundle["id"]): bundle for bundle in bundles}
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def _compile_hook_settings(bundle: dict[str, Any]) -> dict[str, Any]:
|
|
761
|
+
events = bundle.get("compiled_hooks", {})
|
|
762
|
+
if not isinstance(events, dict):
|
|
763
|
+
return {}
|
|
764
|
+
|
|
765
|
+
compiled: dict[str, Any] = {}
|
|
766
|
+
for event_name, items in events.items():
|
|
767
|
+
if not isinstance(items, list):
|
|
768
|
+
continue
|
|
769
|
+
compiled_entries: list[dict[str, Any]] = []
|
|
770
|
+
for item in items:
|
|
771
|
+
if not isinstance(item, dict):
|
|
772
|
+
continue
|
|
773
|
+
command = str(item.get("command", "")).strip()
|
|
774
|
+
if not command:
|
|
775
|
+
continue
|
|
776
|
+
hook_payload: dict[str, Any] = {"type": "command", "command": command}
|
|
777
|
+
timeout = item.get("timeout")
|
|
778
|
+
if isinstance(timeout, int):
|
|
779
|
+
hook_payload["timeout"] = timeout
|
|
780
|
+
entry: dict[str, Any] = {"hooks": [hook_payload]}
|
|
781
|
+
if "matcher" in item:
|
|
782
|
+
entry["matcher"] = str(item.get("matcher", ""))
|
|
783
|
+
compiled_entries.append(entry)
|
|
784
|
+
if compiled_entries:
|
|
785
|
+
compiled[str(event_name)] = compiled_entries
|
|
786
|
+
return compiled
|
|
787
|
+
|
|
788
|
+
|
|
789
|
+
def _protected_paths_for_channel(channel: str) -> list[str]:
|
|
790
|
+
paths = [".omg/**", ".agents/**", ".codex/**", ".claude/**"]
|
|
791
|
+
if channel == "enterprise":
|
|
792
|
+
paths.extend(["registry/**", "dist/**"])
|
|
793
|
+
return paths
|
|
794
|
+
|
|
795
|
+
|
|
796
|
+
def _default_claude_hook_registrations() -> dict[str, list[dict[str, Any]]]:
|
|
797
|
+
"""Default OMG hook registrations for each required Claude event."""
|
|
798
|
+
return {
|
|
799
|
+
"UserPromptSubmit": [
|
|
800
|
+
{
|
|
801
|
+
"hooks": [
|
|
802
|
+
{
|
|
803
|
+
"type": "command",
|
|
804
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/user-prompt-submit.py"',
|
|
805
|
+
"timeout": 10,
|
|
806
|
+
}
|
|
807
|
+
],
|
|
808
|
+
}
|
|
809
|
+
],
|
|
810
|
+
"PreToolUse": [
|
|
811
|
+
{
|
|
812
|
+
"hooks": [
|
|
813
|
+
{
|
|
814
|
+
"type": "command",
|
|
815
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/firewall.py"',
|
|
816
|
+
"timeout": 10,
|
|
817
|
+
}
|
|
818
|
+
],
|
|
819
|
+
"matcher": "Bash",
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
"hooks": [
|
|
823
|
+
{
|
|
824
|
+
"type": "command",
|
|
825
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/secret-guard.py"',
|
|
826
|
+
"timeout": 10,
|
|
827
|
+
}
|
|
828
|
+
],
|
|
829
|
+
"matcher": "Read|Write|Edit|MultiEdit",
|
|
830
|
+
},
|
|
831
|
+
],
|
|
832
|
+
"PostToolUse": [
|
|
833
|
+
{
|
|
834
|
+
"hooks": [
|
|
835
|
+
{
|
|
836
|
+
"type": "command",
|
|
837
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/tool-ledger.py"',
|
|
838
|
+
"timeout": 10,
|
|
839
|
+
}
|
|
840
|
+
],
|
|
841
|
+
"matcher": "Write|Edit|MultiEdit",
|
|
842
|
+
},
|
|
843
|
+
],
|
|
844
|
+
"PostToolUseFailure": [
|
|
845
|
+
{
|
|
846
|
+
"hooks": [
|
|
847
|
+
{
|
|
848
|
+
"type": "command",
|
|
849
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/post-tool-failure.py"',
|
|
850
|
+
}
|
|
851
|
+
],
|
|
852
|
+
}
|
|
853
|
+
],
|
|
854
|
+
"InstructionsLoaded": [
|
|
855
|
+
{
|
|
856
|
+
"hooks": [
|
|
857
|
+
{
|
|
858
|
+
"type": "command",
|
|
859
|
+
"command": '"$HOME/.claude/omg-runtime/.venv/bin/python" "$HOME/.claude/hooks/instructions-loaded.py"',
|
|
860
|
+
"timeout": 10,
|
|
861
|
+
}
|
|
862
|
+
],
|
|
863
|
+
}
|
|
864
|
+
],
|
|
865
|
+
}
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def _build_claude_subagents(protected_paths: list[str]) -> list[dict[str, Any]]:
|
|
869
|
+
return [
|
|
870
|
+
{
|
|
871
|
+
"name": "architect-planner",
|
|
872
|
+
"description": "Read-only planning subagent. Analyses structure, proposes plans, produces evidence. No mutations.",
|
|
873
|
+
"tools": ["Read", "Grep", "Glob", "Bash(git log *)", "Bash(git diff *)", "Bash(python3 scripts/omg.py *)", "WebSearch"],
|
|
874
|
+
"bypassPermissions": False,
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
"name": "explorer-indexer",
|
|
878
|
+
"description": "Read-only codebase exploration subagent. Searches, indexes, and answers structural questions.",
|
|
879
|
+
"tools": ["Read", "Grep", "Glob", "Bash(grep *)", "Bash(find *)", "Bash(git log *)", "Bash(git diff *)"],
|
|
880
|
+
"bypassPermissions": False,
|
|
881
|
+
},
|
|
882
|
+
{
|
|
883
|
+
"name": "implementer",
|
|
884
|
+
"description": "Write-capable implementation subagent governed by protected-path policy and mutation gate.",
|
|
885
|
+
"tools": ["Read", "Write", "Edit", "MultiEdit", "Grep", "Glob", "Bash(git *)", "Bash(python3 *)", "Bash(pytest *)"],
|
|
886
|
+
"bypassPermissions": False,
|
|
887
|
+
"protectedPaths": protected_paths,
|
|
888
|
+
},
|
|
889
|
+
{
|
|
890
|
+
"name": "security-reviewer",
|
|
891
|
+
"description": "Read-only security review subagent with scoped tool access.",
|
|
892
|
+
"tools": ["Read", "Grep", "Glob", "Bash(grep *)", "Bash(find *)", "Bash(git log *)", "Bash(git diff *)"],
|
|
893
|
+
"bypassPermissions": False,
|
|
894
|
+
},
|
|
895
|
+
{
|
|
896
|
+
"name": "verifier",
|
|
897
|
+
"description": "Read-only verification subagent. Runs tests, lints, and produces evidence without mutations.",
|
|
898
|
+
"tools": ["Read", "Grep", "Glob", "Bash(python3 -m pytest *)", "Bash(python3 scripts/omg.py *)", "Bash(git diff *)"],
|
|
899
|
+
"bypassPermissions": False,
|
|
900
|
+
},
|
|
901
|
+
{
|
|
902
|
+
"name": "causal-tracer",
|
|
903
|
+
"description": "Read-only causal analysis subagent. Traces errors, diffs, and logs to root causes.",
|
|
904
|
+
"tools": ["Read", "Grep", "Glob", "Bash(git log *)", "Bash(git diff *)", "Bash(git blame *)", "Bash(grep *)"],
|
|
905
|
+
"bypassPermissions": False,
|
|
906
|
+
},
|
|
907
|
+
]
|
|
908
|
+
|
|
909
|
+
|
|
910
|
+
def _build_claude_skills(policy_model: dict[str, Any] | None) -> list[dict[str, Any]]:
|
|
911
|
+
"""Build Claude skill definitions from the policy model host_rules."""
|
|
912
|
+
skill_refs: list[str] = []
|
|
913
|
+
if isinstance(policy_model, dict):
|
|
914
|
+
host_rules = policy_model.get("host_rules", {})
|
|
915
|
+
if isinstance(host_rules, dict):
|
|
916
|
+
claude_rules = host_rules.get("claude", {})
|
|
917
|
+
if isinstance(claude_rules, dict):
|
|
918
|
+
skill_refs = [str(s) for s in claude_rules.get("skills", []) if str(s).strip()]
|
|
919
|
+
skills: list[dict[str, Any]] = []
|
|
920
|
+
for ref in skill_refs:
|
|
921
|
+
skills.append({"name": ref, "source": f".agents/skills/{ref}/"})
|
|
922
|
+
return skills
|
|
923
|
+
|
|
924
|
+
|
|
925
|
+
def _validate_compiled_claude_output(output_root: Path) -> list[str]:
|
|
926
|
+
"""Validate compiled Claude settings.json contains required hooks and subagents."""
|
|
927
|
+
settings_path = output_root / "settings.json"
|
|
928
|
+
if not settings_path.exists():
|
|
929
|
+
return ["claude: missing compiled settings.json"]
|
|
930
|
+
|
|
931
|
+
settings = _load_json(settings_path)
|
|
932
|
+
errors: list[str] = []
|
|
933
|
+
|
|
934
|
+
hooks = settings.get("hooks", {})
|
|
935
|
+
for event in REQUIRED_CLAUDE_HOOK_EVENTS:
|
|
936
|
+
if event not in hooks or not hooks[event]:
|
|
937
|
+
errors.append(f"claude: missing required hook event '{event}'")
|
|
938
|
+
|
|
939
|
+
omg = settings.get("_omg", {})
|
|
940
|
+
generated = omg.get("generated", {})
|
|
941
|
+
subagents = generated.get("subagents", [])
|
|
942
|
+
subagent_names = {sa.get("name") for sa in subagents if isinstance(sa, dict)}
|
|
943
|
+
for name in REQUIRED_CLAUDE_SUBAGENT_NAMES:
|
|
944
|
+
if name not in subagent_names:
|
|
945
|
+
errors.append(f"claude: missing required subagent '{name}'")
|
|
946
|
+
|
|
947
|
+
for sa in subagents:
|
|
948
|
+
if isinstance(sa, dict) and sa.get("bypassPermissions"):
|
|
949
|
+
errors.append(
|
|
950
|
+
f"claude: subagent '{sa.get('name', '<unknown>')}' has bypassPermissions enabled"
|
|
951
|
+
)
|
|
952
|
+
|
|
953
|
+
return errors
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _compile_claude_outputs(
|
|
957
|
+
*,
|
|
958
|
+
root: Path,
|
|
959
|
+
output_root: Path,
|
|
960
|
+
bundles: list[dict[str, Any]],
|
|
961
|
+
channel: str,
|
|
962
|
+
policy_model: dict[str, Any] | None,
|
|
963
|
+
) -> list[Path]:
|
|
964
|
+
artifacts: list[Path] = []
|
|
965
|
+
|
|
966
|
+
_write_json(output_root / ".claude-plugin" / "plugin.json", _build_claude_plugin())
|
|
967
|
+
artifacts.append(output_root / ".claude-plugin" / "plugin.json")
|
|
968
|
+
|
|
969
|
+
_write_json(output_root / ".claude-plugin" / "marketplace.json", _build_claude_marketplace())
|
|
970
|
+
artifacts.append(output_root / ".claude-plugin" / "marketplace.json")
|
|
971
|
+
|
|
972
|
+
_write_json(output_root / ".claude-plugin" / "mcp.json", {"mcpServers": _plugin_mcp_servers()})
|
|
973
|
+
artifacts.append(output_root / ".claude-plugin" / "mcp.json")
|
|
974
|
+
|
|
975
|
+
mcp_payload = {"mcpServers": _base_mcp_servers()}
|
|
976
|
+
_write_json(output_root / ".mcp.json", mcp_payload)
|
|
977
|
+
artifacts.append(output_root / ".mcp.json")
|
|
978
|
+
|
|
979
|
+
settings_path = root / "settings.json"
|
|
980
|
+
if not settings_path.exists():
|
|
981
|
+
settings_path = resolve_asset("settings.json")
|
|
982
|
+
settings = _load_json(settings_path)
|
|
983
|
+
hook_bundle = _bundle_map(bundles)["hook-governor"]
|
|
984
|
+
compiled_hooks = _compile_hook_settings(hook_bundle)
|
|
985
|
+
defaults = _default_claude_hook_registrations()
|
|
986
|
+
for event in REQUIRED_CLAUDE_HOOK_EVENTS:
|
|
987
|
+
if event not in compiled_hooks or not compiled_hooks[event]:
|
|
988
|
+
compiled_hooks[event] = defaults[event]
|
|
989
|
+
settings["hooks"] = compiled_hooks
|
|
990
|
+
|
|
991
|
+
protected_paths = _policy_protected_paths(policy_model, channel=channel)
|
|
992
|
+
subagents = _build_claude_subagents(protected_paths)
|
|
993
|
+
skills = _build_claude_skills(policy_model)
|
|
994
|
+
|
|
995
|
+
omg_settings = dict(settings.get("_omg", {}))
|
|
996
|
+
omg_settings["_version"] = CANONICAL_VERSION
|
|
997
|
+
phase1_release_contract = _build_phase1_release_contract()
|
|
998
|
+
omg_settings["generated"] = {
|
|
999
|
+
"contract_version": CANONICAL_VERSION,
|
|
1000
|
+
"channel": channel,
|
|
1001
|
+
"required_bundles": list(DEFAULT_REQUIRED_BUNDLES),
|
|
1002
|
+
"protected_paths": protected_paths,
|
|
1003
|
+
"emulated_events": list(hook_bundle.get("lifecycle_hooks", {}).get("emulated", [])),
|
|
1004
|
+
"policy_model": policy_model or {},
|
|
1005
|
+
"subagents": subagents,
|
|
1006
|
+
"skills": skills,
|
|
1007
|
+
"phase1_release_contract": phase1_release_contract,
|
|
1008
|
+
}
|
|
1009
|
+
settings["_omg"] = omg_settings
|
|
1010
|
+
_write_json(output_root / "settings.json", settings)
|
|
1011
|
+
artifacts.append(output_root / "settings.json")
|
|
1012
|
+
|
|
1013
|
+
return artifacts
|
|
1014
|
+
|
|
1015
|
+
|
|
1016
|
+
def _yaml_string(value: str) -> str:
|
|
1017
|
+
return json.dumps(value, ensure_ascii=True)
|
|
1018
|
+
|
|
1019
|
+
|
|
1020
|
+
def _render_codex_skill(bundle: dict[str, Any], channel: str) -> str:
|
|
1021
|
+
execution_modes = ", ".join(str(mode) for mode in bundle.get("execution_contract", {}).get("modes", []))
|
|
1022
|
+
mcp_servers = ", ".join(str(name) for name in bundle.get("mcp_contract", {}).get("servers", []))
|
|
1023
|
+
return (
|
|
1024
|
+
f"---\n"
|
|
1025
|
+
f"name: omg-{bundle['id']}\n"
|
|
1026
|
+
f"description: {_yaml_string(str(bundle['description']))}\n"
|
|
1027
|
+
f"---\n\n"
|
|
1028
|
+
f"# {bundle['title']}\n\n"
|
|
1029
|
+
f"- Channel: `{channel}`\n"
|
|
1030
|
+
f"- Execution modes: `{execution_modes}`\n"
|
|
1031
|
+
f"- MCP servers: `{mcp_servers}`\n"
|
|
1032
|
+
f"- Evidence outputs: `{', '.join(bundle.get('evidence_outputs', {}).get('artifacts', []))}`\n"
|
|
1033
|
+
)
|
|
1034
|
+
|
|
1035
|
+
|
|
1036
|
+
def _render_openai_yaml(bundle: dict[str, Any], channel: str) -> str:
|
|
1037
|
+
invocation = bundle.get("invocation_policy", {})
|
|
1038
|
+
servers = bundle.get("mcp_contract", {}).get("servers", [])
|
|
1039
|
+
tools = bundle.get("tool_policy", {}).get("allowed_tools", {}).get("codex", [])
|
|
1040
|
+
lines = [
|
|
1041
|
+
f"name: omg-{bundle['id']}",
|
|
1042
|
+
f"description: {_yaml_string(str(bundle['description']))}",
|
|
1043
|
+
f"allow_implicit_invocation: {'true' if invocation.get('allow_implicit_invocation') else 'false'}",
|
|
1044
|
+
"metadata:",
|
|
1045
|
+
f" channel: {channel}",
|
|
1046
|
+
f" bundle_id: {bundle['id']}",
|
|
1047
|
+
f" title: {_yaml_string(str(bundle['title']))}",
|
|
1048
|
+
"mcp_servers:",
|
|
1049
|
+
]
|
|
1050
|
+
for server in servers:
|
|
1051
|
+
lines.append(f" - {server}")
|
|
1052
|
+
lines.append("allowed_tools:")
|
|
1053
|
+
for tool in tools:
|
|
1054
|
+
lines.append(f" - {_yaml_string(str(tool))}")
|
|
1055
|
+
return "\n".join(lines) + "\n"
|
|
1056
|
+
|
|
1057
|
+
|
|
1058
|
+
def _codex_skill_refs(policy_model: dict[str, Any] | None) -> list[str]:
|
|
1059
|
+
"""Extract skill references from policy_model.host_rules.codex.skills."""
|
|
1060
|
+
if not isinstance(policy_model, dict):
|
|
1061
|
+
return []
|
|
1062
|
+
host_rules = policy_model.get("host_rules", {})
|
|
1063
|
+
if not isinstance(host_rules, dict):
|
|
1064
|
+
return []
|
|
1065
|
+
codex_rules = host_rules.get("codex", {})
|
|
1066
|
+
if not isinstance(codex_rules, dict):
|
|
1067
|
+
return []
|
|
1068
|
+
return [str(s) for s in codex_rules.get("skills", []) if str(s).strip()]
|
|
1069
|
+
|
|
1070
|
+
|
|
1071
|
+
def _codex_evidence_fields(policy_model: dict[str, Any] | None) -> list[str]:
|
|
1072
|
+
"""Extract required evidence contract fields from the policy model."""
|
|
1073
|
+
if not isinstance(policy_model, dict):
|
|
1074
|
+
return []
|
|
1075
|
+
ec = policy_model.get("evidence_contract", {})
|
|
1076
|
+
if not isinstance(ec, dict):
|
|
1077
|
+
return []
|
|
1078
|
+
return sorted(ec.keys())
|
|
1079
|
+
|
|
1080
|
+
|
|
1081
|
+
def _codex_protected_planning_skills(bundles: Iterable[dict[str, Any]]) -> list[str]:
|
|
1082
|
+
protected: list[str] = []
|
|
1083
|
+
for bundle in bundles:
|
|
1084
|
+
if "codex" not in bundle.get("hosts", []):
|
|
1085
|
+
continue
|
|
1086
|
+
if str(bundle.get("kind", "")).strip().lower() != "planning":
|
|
1087
|
+
continue
|
|
1088
|
+
invocation = bundle.get("invocation_policy", {})
|
|
1089
|
+
if not isinstance(invocation, dict):
|
|
1090
|
+
continue
|
|
1091
|
+
if invocation.get("allow_implicit_invocation") is False:
|
|
1092
|
+
protected.append(f"omg/{bundle['id']}")
|
|
1093
|
+
return sorted(set(protected))
|
|
1094
|
+
|
|
1095
|
+
|
|
1096
|
+
def _render_codex_agents_fragment(
|
|
1097
|
+
*,
|
|
1098
|
+
channel: str,
|
|
1099
|
+
protected_paths: list[str],
|
|
1100
|
+
codex_rules: list[str],
|
|
1101
|
+
codex_automations: list[str],
|
|
1102
|
+
codex_skills: list[str],
|
|
1103
|
+
evidence_fields: list[str],
|
|
1104
|
+
protected_planning_skills: list[str],
|
|
1105
|
+
phase1_release_contract: dict[str, list[str]],
|
|
1106
|
+
) -> str:
|
|
1107
|
+
"""Render a comprehensive AGENTS.fragment.md for Codex host."""
|
|
1108
|
+
sections: list[str] = []
|
|
1109
|
+
|
|
1110
|
+
# Header
|
|
1111
|
+
sections.append(f"# OMG Codex Governance (channel: {channel})\n")
|
|
1112
|
+
|
|
1113
|
+
# Build & Test
|
|
1114
|
+
sections.append("## Build & Test\n")
|
|
1115
|
+
sections.append("```bash")
|
|
1116
|
+
sections.append("python3 -m pytest tests -q")
|
|
1117
|
+
sections.append("python3 scripts/omg.py contract validate")
|
|
1118
|
+
sections.append(f"python3 scripts/omg.py contract compile --host codex --channel {channel}")
|
|
1119
|
+
sections.append("```\n")
|
|
1120
|
+
|
|
1121
|
+
# Protected Paths
|
|
1122
|
+
sections.append("## Protected Paths\n")
|
|
1123
|
+
sections.append("The following paths require tier-gated review before mutation:\n")
|
|
1124
|
+
for path in protected_paths:
|
|
1125
|
+
sections.append(f"- `{path}`")
|
|
1126
|
+
sections.append("")
|
|
1127
|
+
|
|
1128
|
+
sections.append("## Release Audit\n")
|
|
1129
|
+
sections.append("Release readiness requires claim and compliance outcomes:\n")
|
|
1130
|
+
for check in phase1_release_contract.get("release_readiness_checks", []):
|
|
1131
|
+
sections.append(f"- `{check}`")
|
|
1132
|
+
sections.append("Attestation requirements:")
|
|
1133
|
+
for requirement in phase1_release_contract.get("attestation_requirements", []):
|
|
1134
|
+
sections.append(f"- `{requirement}`")
|
|
1135
|
+
sections.append("")
|
|
1136
|
+
|
|
1137
|
+
# Evidence Contract
|
|
1138
|
+
sections.append("## Evidence Contract\n")
|
|
1139
|
+
sections.append("Every production action must emit evidence containing these fields:\n")
|
|
1140
|
+
if evidence_fields:
|
|
1141
|
+
for field in evidence_fields:
|
|
1142
|
+
sections.append(f"- `{field}`")
|
|
1143
|
+
else:
|
|
1144
|
+
sections.append("- `timestamp`")
|
|
1145
|
+
sections.append("- `executor`")
|
|
1146
|
+
sections.append("- `trace_id`")
|
|
1147
|
+
sections.append("- `lineage`")
|
|
1148
|
+
sections.append("")
|
|
1149
|
+
|
|
1150
|
+
# Required Skills
|
|
1151
|
+
sections.append("## Required Skills\n")
|
|
1152
|
+
if codex_skills:
|
|
1153
|
+
for skill in codex_skills:
|
|
1154
|
+
sections.append(f"- `{skill}`")
|
|
1155
|
+
else:
|
|
1156
|
+
sections.append("- `omg/control-plane`")
|
|
1157
|
+
sections.append("")
|
|
1158
|
+
|
|
1159
|
+
sections.append("## Protected Planning Surface\n")
|
|
1160
|
+
if protected_planning_skills:
|
|
1161
|
+
sections.append("Council planning skills are protected and explicit-invocation only:")
|
|
1162
|
+
sections.append("")
|
|
1163
|
+
for skill in protected_planning_skills:
|
|
1164
|
+
sections.append(f"- `{skill}`")
|
|
1165
|
+
else:
|
|
1166
|
+
sections.append("- No protected planning skills configured.")
|
|
1167
|
+
sections.append("")
|
|
1168
|
+
|
|
1169
|
+
# Web Search Policy
|
|
1170
|
+
sections.append("## Web Search Policy\n")
|
|
1171
|
+
sections.append("- Prefer cached results over live network requests.")
|
|
1172
|
+
sections.append("- Do NOT initiate live web searches unless explicitly instructed.")
|
|
1173
|
+
sections.append("- Use `context7` or local documentation before external lookups.")
|
|
1174
|
+
sections.append("- Set `cached_web_search: prefer_cached` as the default.\n")
|
|
1175
|
+
|
|
1176
|
+
# Approval Constraints
|
|
1177
|
+
sections.append("## Approval Constraints\n")
|
|
1178
|
+
sections.append("- Destructive file operations require explicit user approval.")
|
|
1179
|
+
sections.append("- `git push --force` and branch deletions require explicit approval.")
|
|
1180
|
+
sections.append("- Production deployments require explicit approval.")
|
|
1181
|
+
sections.append("- Mutations to protected paths require tier-gated approval.\n")
|
|
1182
|
+
|
|
1183
|
+
# Rules & Automations (compact summary)
|
|
1184
|
+
sections.append("## Rules & Automations\n")
|
|
1185
|
+
rules_str = ", ".join(codex_rules) if codex_rules else "protected_paths, explicit_invocation"
|
|
1186
|
+
auto_str = ", ".join(codex_automations) if codex_automations else "contract-compile"
|
|
1187
|
+
sections.append(f"- Rules: `{rules_str}`")
|
|
1188
|
+
sections.append(f"- Automations: `{auto_str}`")
|
|
1189
|
+
sections.append("- Defer to the repo's `AGENTS.md` / `AGENTS.override.md` instruction hierarchy before OMG-specific guidance.")
|
|
1190
|
+
sections.append("- Do not mirror or override Codex built-in slash commands; OMG guidance applies through MCP, skills, and generated rules.")
|
|
1191
|
+
sections.append("- Require explicit invocation for protected production planning skills.")
|
|
1192
|
+
sections.append("")
|
|
1193
|
+
|
|
1194
|
+
return "\n".join(sections)
|
|
1195
|
+
|
|
1196
|
+
|
|
1197
|
+
def _render_codex_rules(
|
|
1198
|
+
*,
|
|
1199
|
+
channel: str,
|
|
1200
|
+
protected_paths: list[str],
|
|
1201
|
+
codex_skills: list[str],
|
|
1202
|
+
protected_planning_skills: list[str],
|
|
1203
|
+
) -> str:
|
|
1204
|
+
"""Render a codex-rules.md config fragment encoding defaults."""
|
|
1205
|
+
lines: list[str] = []
|
|
1206
|
+
lines.append(f"# OMG Codex Rules (channel: {channel})\n")
|
|
1207
|
+
|
|
1208
|
+
lines.append("## Defaults\n")
|
|
1209
|
+
lines.append("- `cached_web_search: prefer_cached`")
|
|
1210
|
+
lines.append("- `live_network: deny_by_default`")
|
|
1211
|
+
lines.append("- `destructive_approval: required`\n")
|
|
1212
|
+
|
|
1213
|
+
lines.append("## Protected Paths\n")
|
|
1214
|
+
for path in protected_paths:
|
|
1215
|
+
lines.append(f"- `{path}`")
|
|
1216
|
+
lines.append("")
|
|
1217
|
+
|
|
1218
|
+
lines.append("## Required Skills\n")
|
|
1219
|
+
for skill in (codex_skills or ["omg/control-plane"]):
|
|
1220
|
+
lines.append(f"- `{skill}`")
|
|
1221
|
+
lines.append("")
|
|
1222
|
+
|
|
1223
|
+
lines.append("## Host Interop\n")
|
|
1224
|
+
lines.append("- Respect the repo `AGENTS.md` / `AGENTS.override.md` chain before applying OMG-specific rules.")
|
|
1225
|
+
lines.append("- Keep OMG guidance separate from Codex built-in slash commands.")
|
|
1226
|
+
lines.append("")
|
|
1227
|
+
|
|
1228
|
+
lines.append("## Protected Planning Surface\n")
|
|
1229
|
+
if protected_planning_skills:
|
|
1230
|
+
for skill in protected_planning_skills:
|
|
1231
|
+
lines.append(f"- `{skill}` (explicit invocation only)")
|
|
1232
|
+
else:
|
|
1233
|
+
lines.append("- none")
|
|
1234
|
+
lines.append("")
|
|
1235
|
+
|
|
1236
|
+
lines.append("## Approval Matrix\n")
|
|
1237
|
+
lines.append("| Action | Approval Required |")
|
|
1238
|
+
lines.append("|--------|------------------|")
|
|
1239
|
+
lines.append("| Read / Grep | No |")
|
|
1240
|
+
lines.append("| Write to protected paths | Yes |")
|
|
1241
|
+
lines.append("| Bash (python3:*) | Yes (balanced+ tier) |")
|
|
1242
|
+
lines.append("| git push --force | Yes |")
|
|
1243
|
+
lines.append("| Production deploy | Yes |")
|
|
1244
|
+
lines.append("")
|
|
1245
|
+
|
|
1246
|
+
return "\n".join(lines)
|
|
1247
|
+
|
|
1248
|
+
|
|
1249
|
+
def _validate_compiled_codex_output(output_root: Path) -> list[str]:
|
|
1250
|
+
"""Validate compiled Codex output contains required AGENTS sections and artifacts."""
|
|
1251
|
+
errors: list[str] = []
|
|
1252
|
+
shared_dir = output_root / ".agents" / "skills" / "omg"
|
|
1253
|
+
|
|
1254
|
+
for required_file in REQUIRED_CODEX_OUTPUTS:
|
|
1255
|
+
path = shared_dir / required_file
|
|
1256
|
+
if not path.exists():
|
|
1257
|
+
errors.append(f"codex: missing required output '{required_file}'")
|
|
1258
|
+
|
|
1259
|
+
agents_path = shared_dir / "AGENTS.fragment.md"
|
|
1260
|
+
if agents_path.exists():
|
|
1261
|
+
content = agents_path.read_text(encoding="utf-8")
|
|
1262
|
+
for section in REQUIRED_CODEX_AGENTS_SECTIONS:
|
|
1263
|
+
if section not in content:
|
|
1264
|
+
errors.append(f"codex: AGENTS.fragment.md missing required section '{section}'")
|
|
1265
|
+
else:
|
|
1266
|
+
errors.append("codex: cannot validate AGENTS.fragment.md — file missing")
|
|
1267
|
+
|
|
1268
|
+
return errors
|
|
1269
|
+
|
|
1270
|
+
|
|
1271
|
+
def _compile_codex_outputs(
|
|
1272
|
+
*,
|
|
1273
|
+
output_root: Path,
|
|
1274
|
+
bundles: list[dict[str, Any]],
|
|
1275
|
+
channel: str,
|
|
1276
|
+
policy_model: dict[str, Any] | None,
|
|
1277
|
+
) -> list[Path]:
|
|
1278
|
+
artifacts: list[Path] = []
|
|
1279
|
+
shared_dir = output_root / ".agents" / "skills" / "omg"
|
|
1280
|
+
shared_dir.mkdir(parents=True, exist_ok=True)
|
|
1281
|
+
|
|
1282
|
+
protected_paths = _policy_protected_paths(policy_model, channel=channel)
|
|
1283
|
+
codex_rules: list[str] = []
|
|
1284
|
+
codex_automations: list[str] = []
|
|
1285
|
+
if isinstance(policy_model, dict):
|
|
1286
|
+
host_rules = policy_model.get("host_rules", {})
|
|
1287
|
+
if isinstance(host_rules, dict):
|
|
1288
|
+
codex_policy = host_rules.get("codex", {})
|
|
1289
|
+
if isinstance(codex_policy, dict):
|
|
1290
|
+
codex_rules = [str(item) for item in codex_policy.get("rules", []) if str(item).strip()]
|
|
1291
|
+
codex_automations = [
|
|
1292
|
+
str(item) for item in codex_policy.get("automations", []) if str(item).strip()
|
|
1293
|
+
]
|
|
1294
|
+
|
|
1295
|
+
codex_skills = _codex_skill_refs(policy_model)
|
|
1296
|
+
evidence_fields = _codex_evidence_fields(policy_model)
|
|
1297
|
+
protected_planning_skills = _codex_protected_planning_skills(bundles)
|
|
1298
|
+
phase1_release_contract = _build_phase1_release_contract()
|
|
1299
|
+
|
|
1300
|
+
agents_fragment = _render_codex_agents_fragment(
|
|
1301
|
+
channel=channel,
|
|
1302
|
+
protected_paths=protected_paths,
|
|
1303
|
+
codex_rules=codex_rules,
|
|
1304
|
+
codex_automations=codex_automations,
|
|
1305
|
+
codex_skills=codex_skills,
|
|
1306
|
+
evidence_fields=evidence_fields,
|
|
1307
|
+
protected_planning_skills=protected_planning_skills,
|
|
1308
|
+
phase1_release_contract=phase1_release_contract,
|
|
1309
|
+
)
|
|
1310
|
+
_write_text(shared_dir / "AGENTS.fragment.md", agents_fragment)
|
|
1311
|
+
artifacts.append(shared_dir / "AGENTS.fragment.md")
|
|
1312
|
+
|
|
1313
|
+
rules_content = _render_codex_rules(
|
|
1314
|
+
channel=channel,
|
|
1315
|
+
protected_paths=protected_paths,
|
|
1316
|
+
codex_skills=codex_skills,
|
|
1317
|
+
protected_planning_skills=protected_planning_skills,
|
|
1318
|
+
)
|
|
1319
|
+
_write_text(shared_dir / "codex-rules.md", rules_content)
|
|
1320
|
+
artifacts.append(shared_dir / "codex-rules.md")
|
|
1321
|
+
|
|
1322
|
+
from runtime.mcp_config_writers import write_codex_mcp_stdio_config
|
|
1323
|
+
|
|
1324
|
+
codex_mcp_path = shared_dir / "codex-mcp.toml"
|
|
1325
|
+
write_codex_mcp_stdio_config(
|
|
1326
|
+
command="python3",
|
|
1327
|
+
args=["-m", "runtime.omg_mcp_server"],
|
|
1328
|
+
server_name="omg-control",
|
|
1329
|
+
config_path=codex_mcp_path,
|
|
1330
|
+
)
|
|
1331
|
+
artifacts.append(codex_mcp_path)
|
|
1332
|
+
|
|
1333
|
+
for bundle in bundles:
|
|
1334
|
+
if "codex" not in bundle.get("hosts", []):
|
|
1335
|
+
continue
|
|
1336
|
+
skill_dir = shared_dir / str(bundle["id"])
|
|
1337
|
+
_write_text(skill_dir / "SKILL.md", _render_codex_skill(bundle, channel))
|
|
1338
|
+
_write_text(skill_dir / "openai.yaml", _render_openai_yaml(bundle, channel))
|
|
1339
|
+
artifacts.extend([skill_dir / "SKILL.md", skill_dir / "openai.yaml"])
|
|
1340
|
+
|
|
1341
|
+
return artifacts
|
|
1342
|
+
|
|
1343
|
+
|
|
1344
|
+
def _compile_gemini_outputs(output_root: Path, channel: str) -> dict[str, Any]:
|
|
1345
|
+
from runtime.mcp_config_writers import write_gemini_mcp_stdio_config
|
|
1346
|
+
|
|
1347
|
+
config_path = output_root / ".gemini" / "settings.json"
|
|
1348
|
+
write_gemini_mcp_stdio_config(
|
|
1349
|
+
command="python3",
|
|
1350
|
+
args=["-m", "runtime.omg_mcp_server"],
|
|
1351
|
+
server_name="omg-control",
|
|
1352
|
+
config_path=config_path,
|
|
1353
|
+
)
|
|
1354
|
+
payload = _load_json(config_path)
|
|
1355
|
+
payload["_omg"] = {
|
|
1356
|
+
"_version": CANONICAL_VERSION,
|
|
1357
|
+
"generated": {
|
|
1358
|
+
"contract_version": CANONICAL_VERSION,
|
|
1359
|
+
"channel": channel,
|
|
1360
|
+
"required_bundles": list(DEFAULT_REQUIRED_BUNDLES),
|
|
1361
|
+
"phase1_release_contract": _build_phase1_release_contract(),
|
|
1362
|
+
},
|
|
1363
|
+
}
|
|
1364
|
+
_write_json(config_path, payload)
|
|
1365
|
+
return {"host": "gemini", "artifacts": [config_path]}
|
|
1366
|
+
|
|
1367
|
+
|
|
1368
|
+
def _compile_kimi_outputs(output_root: Path, channel: str) -> dict[str, Any]:
|
|
1369
|
+
from runtime.mcp_config_writers import write_kimi_mcp_stdio_config
|
|
1370
|
+
|
|
1371
|
+
config_path = output_root / ".kimi" / "mcp.json"
|
|
1372
|
+
write_kimi_mcp_stdio_config(
|
|
1373
|
+
command="python3",
|
|
1374
|
+
args=["-m", "runtime.omg_mcp_server"],
|
|
1375
|
+
server_name="omg-control",
|
|
1376
|
+
config_path=config_path,
|
|
1377
|
+
)
|
|
1378
|
+
payload = _load_json(config_path)
|
|
1379
|
+
payload["_omg"] = {
|
|
1380
|
+
"_version": CANONICAL_VERSION,
|
|
1381
|
+
"generated": {
|
|
1382
|
+
"contract_version": CANONICAL_VERSION,
|
|
1383
|
+
"channel": channel,
|
|
1384
|
+
"required_bundles": list(DEFAULT_REQUIRED_BUNDLES),
|
|
1385
|
+
"phase1_release_contract": _build_phase1_release_contract(),
|
|
1386
|
+
},
|
|
1387
|
+
}
|
|
1388
|
+
_write_json(config_path, payload)
|
|
1389
|
+
return {"host": "kimi", "artifacts": [config_path]}
|
|
1390
|
+
|
|
1391
|
+
|
|
1392
|
+
def _copy_release_bundle(
|
|
1393
|
+
*,
|
|
1394
|
+
output_root: Path,
|
|
1395
|
+
channel: str,
|
|
1396
|
+
artifacts: list[Path],
|
|
1397
|
+
) -> list[Path]:
|
|
1398
|
+
bundle_root = output_root / "dist" / channel / "bundle"
|
|
1399
|
+
if bundle_root.exists():
|
|
1400
|
+
shutil.rmtree(bundle_root)
|
|
1401
|
+
copied: list[Path] = []
|
|
1402
|
+
for path in sorted(set(artifacts)):
|
|
1403
|
+
rel_path = path.relative_to(output_root)
|
|
1404
|
+
dst = bundle_root / rel_path
|
|
1405
|
+
dst.parent.mkdir(parents=True, exist_ok=True)
|
|
1406
|
+
shutil.copy2(path, dst)
|
|
1407
|
+
copied.append(dst)
|
|
1408
|
+
return copied
|
|
1409
|
+
|
|
1410
|
+
|
|
1411
|
+
def _build_dist_manifest(output_root: Path, *, channel: str, hosts: list[str], artifacts: list[Path]) -> Path:
|
|
1412
|
+
dist_root = output_root / "dist" / channel
|
|
1413
|
+
artifact_entries: list[dict[str, str]] = []
|
|
1414
|
+
attestation_rows: list[dict[str, str]] = []
|
|
1415
|
+
|
|
1416
|
+
for path in sorted(set(artifacts)):
|
|
1417
|
+
rel_path = str(path.relative_to(dist_root))
|
|
1418
|
+
digest = _sha256_file(path)
|
|
1419
|
+
artifact_entries.append({"path": rel_path, "sha256": digest})
|
|
1420
|
+
|
|
1421
|
+
statement = sign_artifact_statement(
|
|
1422
|
+
artifact_path=rel_path,
|
|
1423
|
+
subject_digest=digest,
|
|
1424
|
+
)
|
|
1425
|
+
|
|
1426
|
+
statement_rel = f"attestations/{rel_path}.statement.json"
|
|
1427
|
+
statement_abs = dist_root / statement_rel
|
|
1428
|
+
_write_json(statement_abs, statement)
|
|
1429
|
+
|
|
1430
|
+
sig_rel = f"attestations/{rel_path}.minisig"
|
|
1431
|
+
sig_abs = dist_root / sig_rel
|
|
1432
|
+
sig_value = statement.get("signature", {}).get("value", "")
|
|
1433
|
+
_write_text(sig_abs, sig_value + "\n")
|
|
1434
|
+
|
|
1435
|
+
attestation_rows.append({
|
|
1436
|
+
"artifact_path": rel_path,
|
|
1437
|
+
"statement_path": statement_rel,
|
|
1438
|
+
"signature_path": sig_rel,
|
|
1439
|
+
"signer_key_id": statement.get("signature", {}).get("keyid", ""),
|
|
1440
|
+
"algorithm": "ed25519-minisign",
|
|
1441
|
+
})
|
|
1442
|
+
|
|
1443
|
+
payload = {
|
|
1444
|
+
"schema": "OmgCompiledArtifactManifest",
|
|
1445
|
+
"channel": channel,
|
|
1446
|
+
"hosts": list(hosts),
|
|
1447
|
+
"contract_version": CANONICAL_VERSION,
|
|
1448
|
+
"artifacts": artifact_entries,
|
|
1449
|
+
"attestations": attestation_rows,
|
|
1450
|
+
}
|
|
1451
|
+
out_path = dist_root / "manifest.json"
|
|
1452
|
+
_write_json(out_path, payload)
|
|
1453
|
+
return out_path
|
|
1454
|
+
|
|
1455
|
+
|
|
1456
|
+
def _write_release_surface_manifest(output_root: Path, *, channel: str) -> Path:
|
|
1457
|
+
payload = {
|
|
1458
|
+
"generated_by": "omg release compile-surfaces",
|
|
1459
|
+
"version": CANONICAL_VERSION,
|
|
1460
|
+
"generated_at": datetime.now(timezone.utc).isoformat(),
|
|
1461
|
+
"surfaces": get_public_surfaces(),
|
|
1462
|
+
}
|
|
1463
|
+
out_path = output_root / "dist" / channel / "release-surface.json"
|
|
1464
|
+
_write_json(out_path, payload)
|
|
1465
|
+
return out_path
|
|
1466
|
+
|
|
1467
|
+
|
|
1468
|
+
def compile_contract_outputs(
|
|
1469
|
+
*,
|
|
1470
|
+
root_dir: str | Path | None = None,
|
|
1471
|
+
output_root: str | Path | None = None,
|
|
1472
|
+
hosts: list[str] | tuple[str, ...] | None = None,
|
|
1473
|
+
channel: str = "public",
|
|
1474
|
+
) -> dict[str, Any]:
|
|
1475
|
+
root = _resolve_root(root_dir)
|
|
1476
|
+
output = _resolve_output_root(root, output_root)
|
|
1477
|
+
validation = validate_contract_registry(root)
|
|
1478
|
+
if validation["status"] != "ok":
|
|
1479
|
+
return {
|
|
1480
|
+
"schema": "OmgContractCompileResult",
|
|
1481
|
+
"status": "error",
|
|
1482
|
+
"channel": channel,
|
|
1483
|
+
"hosts": list(hosts or SUPPORTED_HOSTS),
|
|
1484
|
+
"errors": validation["errors"],
|
|
1485
|
+
"artifacts": [],
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
if channel not in SUPPORTED_CHANNELS:
|
|
1489
|
+
return {
|
|
1490
|
+
"schema": "OmgContractCompileResult",
|
|
1491
|
+
"status": "error",
|
|
1492
|
+
"channel": channel,
|
|
1493
|
+
"hosts": list(hosts or SUPPORTED_HOSTS),
|
|
1494
|
+
"errors": [f"unsupported channel: {channel}"],
|
|
1495
|
+
"artifacts": [],
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
selected_hosts = list(hosts or SUPPORTED_HOSTS)
|
|
1499
|
+
bad_hosts = [host for host in selected_hosts if host not in SUPPORTED_HOSTS]
|
|
1500
|
+
if bad_hosts:
|
|
1501
|
+
return {
|
|
1502
|
+
"schema": "OmgContractCompileResult",
|
|
1503
|
+
"status": "error",
|
|
1504
|
+
"channel": channel,
|
|
1505
|
+
"hosts": selected_hosts,
|
|
1506
|
+
"errors": [f"unsupported hosts: {bad_hosts}"],
|
|
1507
|
+
"artifacts": [],
|
|
1508
|
+
}
|
|
1509
|
+
|
|
1510
|
+
bundles = load_contract_bundles(root)
|
|
1511
|
+
policy_model = _policy_model_for_bundle(bundles, "control-plane")
|
|
1512
|
+
artifacts = _copy_contract_inputs(root, output)
|
|
1513
|
+
|
|
1514
|
+
if "claude" in selected_hosts:
|
|
1515
|
+
artifacts.extend(
|
|
1516
|
+
_compile_claude_outputs(
|
|
1517
|
+
root=root,
|
|
1518
|
+
output_root=output,
|
|
1519
|
+
bundles=bundles,
|
|
1520
|
+
channel=channel,
|
|
1521
|
+
policy_model=policy_model,
|
|
1522
|
+
)
|
|
1523
|
+
)
|
|
1524
|
+
claude_errors = _validate_compiled_claude_output(output)
|
|
1525
|
+
if claude_errors:
|
|
1526
|
+
return {
|
|
1527
|
+
"schema": "OmgContractCompileResult",
|
|
1528
|
+
"status": "error",
|
|
1529
|
+
"channel": channel,
|
|
1530
|
+
"hosts": selected_hosts,
|
|
1531
|
+
"errors": claude_errors,
|
|
1532
|
+
"artifacts": [],
|
|
1533
|
+
}
|
|
1534
|
+
if "codex" in selected_hosts:
|
|
1535
|
+
artifacts.extend(
|
|
1536
|
+
_compile_codex_outputs(
|
|
1537
|
+
output_root=output,
|
|
1538
|
+
bundles=bundles,
|
|
1539
|
+
channel=channel,
|
|
1540
|
+
policy_model=policy_model,
|
|
1541
|
+
)
|
|
1542
|
+
)
|
|
1543
|
+
codex_errors = _validate_compiled_codex_output(output)
|
|
1544
|
+
if codex_errors:
|
|
1545
|
+
return {
|
|
1546
|
+
"schema": "OmgContractCompileResult",
|
|
1547
|
+
"status": "error",
|
|
1548
|
+
"channel": channel,
|
|
1549
|
+
"hosts": selected_hosts,
|
|
1550
|
+
"errors": codex_errors,
|
|
1551
|
+
"artifacts": [],
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
if "gemini" in selected_hosts:
|
|
1555
|
+
artifacts.extend(_compile_gemini_outputs(output, channel)["artifacts"])
|
|
1556
|
+
|
|
1557
|
+
if "kimi" in selected_hosts:
|
|
1558
|
+
artifacts.extend(_compile_kimi_outputs(output, channel)["artifacts"])
|
|
1559
|
+
|
|
1560
|
+
bundled_artifacts = _copy_release_bundle(output_root=output, channel=channel, artifacts=artifacts)
|
|
1561
|
+
manifest_path = _build_dist_manifest(output, channel=channel, hosts=selected_hosts, artifacts=bundled_artifacts)
|
|
1562
|
+
artifacts.append(manifest_path)
|
|
1563
|
+
release_surface_path = _write_release_surface_manifest(output, channel=channel)
|
|
1564
|
+
artifacts.append(release_surface_path)
|
|
1565
|
+
|
|
1566
|
+
return {
|
|
1567
|
+
"schema": "OmgContractCompileResult",
|
|
1568
|
+
"status": "ok",
|
|
1569
|
+
"channel": channel,
|
|
1570
|
+
"hosts": selected_hosts,
|
|
1571
|
+
"artifacts": [str(path.relative_to(output)) for path in artifacts],
|
|
1572
|
+
"manifest": str(manifest_path.relative_to(output)),
|
|
1573
|
+
}
|
|
1574
|
+
|
|
1575
|
+
|
|
1576
|
+
_METHOD_PHASES = (
|
|
1577
|
+
"deep-interview",
|
|
1578
|
+
"approved-spec",
|
|
1579
|
+
"done-when-contract",
|
|
1580
|
+
"test-lock",
|
|
1581
|
+
"implementation-plan",
|
|
1582
|
+
"review",
|
|
1583
|
+
"release",
|
|
1584
|
+
)
|
|
1585
|
+
|
|
1586
|
+
_RELEASE_READINESS_CACHE_TTL_SECONDS = 3600.0
|
|
1587
|
+
|
|
1588
|
+
|
|
1589
|
+
def _load_release_readiness_cache(root: Path, channel: str) -> dict[str, Any] | None:
|
|
1590
|
+
cache_path = root / ".omg" / "cache" / "release-readiness-identity.json"
|
|
1591
|
+
if not cache_path.exists():
|
|
1592
|
+
return None
|
|
1593
|
+
|
|
1594
|
+
try:
|
|
1595
|
+
payload = json.loads(cache_path.read_text(encoding="utf-8"))
|
|
1596
|
+
except (OSError, json.JSONDecodeError):
|
|
1597
|
+
return None
|
|
1598
|
+
if not isinstance(payload, dict):
|
|
1599
|
+
return None
|
|
1600
|
+
if payload.get("channel") != channel:
|
|
1601
|
+
return None
|
|
1602
|
+
if payload.get("version") != CANONICAL_VERSION:
|
|
1603
|
+
return None
|
|
1604
|
+
if payload.get("blockers"):
|
|
1605
|
+
return None
|
|
1606
|
+
|
|
1607
|
+
cached_at = payload.get("cached_at", 0)
|
|
1608
|
+
if not isinstance(cached_at, (int, float)):
|
|
1609
|
+
return None
|
|
1610
|
+
if time.time() - float(cached_at) > _RELEASE_READINESS_CACHE_TTL_SECONDS:
|
|
1611
|
+
return None
|
|
1612
|
+
return payload
|
|
1613
|
+
|
|
1614
|
+
|
|
1615
|
+
def write_release_readiness_cache(result: dict[str, Any], root: Path) -> None:
|
|
1616
|
+
blockers = result.get("blockers")
|
|
1617
|
+
if blockers != []:
|
|
1618
|
+
return
|
|
1619
|
+
|
|
1620
|
+
cache_path = root / ".omg" / "cache" / "release-readiness-identity.json"
|
|
1621
|
+
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
|
1622
|
+
payload = dict(result)
|
|
1623
|
+
payload["version"] = CANONICAL_VERSION
|
|
1624
|
+
payload["cached_at"] = time.time()
|
|
1625
|
+
payload["cache_hit"] = False
|
|
1626
|
+
cache_path.write_text(json.dumps(payload, indent=2, ensure_ascii=True) + "\n", encoding="utf-8")
|
|
1627
|
+
|
|
1628
|
+
|
|
1629
|
+
def compile_method_artifacts(
|
|
1630
|
+
*,
|
|
1631
|
+
root_dir: str | Path | None = None,
|
|
1632
|
+
output_root: str | Path | None = None,
|
|
1633
|
+
hosts: list[str] | tuple[str, ...] | None = None,
|
|
1634
|
+
channel: str = "public",
|
|
1635
|
+
) -> dict[str, Any]:
|
|
1636
|
+
root = _resolve_root(root_dir)
|
|
1637
|
+
output = _resolve_output_root(root, output_root)
|
|
1638
|
+
selected_hosts = list(hosts or SUPPORTED_HOSTS)
|
|
1639
|
+
method_dir = output / ".omg" / "evidence" / "method"
|
|
1640
|
+
method_dir.mkdir(parents=True, exist_ok=True)
|
|
1641
|
+
issued_at = datetime.now(timezone.utc).isoformat()
|
|
1642
|
+
|
|
1643
|
+
phase_artifacts: list[str] = []
|
|
1644
|
+
for phase in _METHOD_PHASES:
|
|
1645
|
+
phase_payload: dict[str, Any] = {
|
|
1646
|
+
"schema": "OmgMethodPhase",
|
|
1647
|
+
"phase": phase,
|
|
1648
|
+
"contract_version": CANONICAL_VERSION,
|
|
1649
|
+
"channel": channel,
|
|
1650
|
+
"hosts": selected_hosts,
|
|
1651
|
+
"issued_at": issued_at,
|
|
1652
|
+
}
|
|
1653
|
+
phase_path = method_dir / f"{phase}.json"
|
|
1654
|
+
_write_json(phase_path, phase_payload)
|
|
1655
|
+
raw = json.dumps(phase_payload, sort_keys=True, separators=(",", ":")).encode()
|
|
1656
|
+
digest = hashlib.sha256(raw).hexdigest()
|
|
1657
|
+
try:
|
|
1658
|
+
from registry.verify_artifact import sign_artifact_statement
|
|
1659
|
+
statement = sign_artifact_statement(
|
|
1660
|
+
artifact_path=str(phase_path),
|
|
1661
|
+
subject_digest=digest,
|
|
1662
|
+
trusted_comment=f"omg-method:{phase}:{CANONICAL_VERSION}",
|
|
1663
|
+
)
|
|
1664
|
+
except Exception:
|
|
1665
|
+
statement = {
|
|
1666
|
+
"schema": "OmgMethodPhaseStatement",
|
|
1667
|
+
"phase": phase,
|
|
1668
|
+
"subject_digest": digest,
|
|
1669
|
+
"issued_at": issued_at,
|
|
1670
|
+
"signer": {"keyid": "offline", "algorithm": "sha256-only", "mode": "local-offline"},
|
|
1671
|
+
}
|
|
1672
|
+
stmt_path = method_dir / f"{phase}.statement.json"
|
|
1673
|
+
_write_json(stmt_path, statement)
|
|
1674
|
+
phase_artifacts.append(phase)
|
|
1675
|
+
|
|
1676
|
+
manifest: dict[str, Any] = {
|
|
1677
|
+
"schema": "OmgMethodManifest",
|
|
1678
|
+
"phases": list(_METHOD_PHASES),
|
|
1679
|
+
"contract_version": CANONICAL_VERSION,
|
|
1680
|
+
"channel": channel,
|
|
1681
|
+
"hosts": selected_hosts,
|
|
1682
|
+
"issued_at": issued_at,
|
|
1683
|
+
}
|
|
1684
|
+
manifest_path = method_dir / "manifest.json"
|
|
1685
|
+
_write_json(manifest_path, manifest)
|
|
1686
|
+
|
|
1687
|
+
return {
|
|
1688
|
+
"schema": "OmgMethodCompileResult",
|
|
1689
|
+
"status": "ok",
|
|
1690
|
+
"channel": channel,
|
|
1691
|
+
"hosts": selected_hosts,
|
|
1692
|
+
"phases": phase_artifacts,
|
|
1693
|
+
"manifest": str(manifest_path.relative_to(output)),
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
|
|
1697
|
+
def _provider_statuses() -> dict[str, dict[str, Any]]:
|
|
1698
|
+
ready_override = {
|
|
1699
|
+
item.strip()
|
|
1700
|
+
for item in os.environ.get("OMG_RELEASE_READY_PROVIDERS", "").split(",")
|
|
1701
|
+
if item.strip()
|
|
1702
|
+
}
|
|
1703
|
+
statuses: dict[str, dict[str, Any]] = {}
|
|
1704
|
+
|
|
1705
|
+
for provider_name in SUPPORTED_HOSTS:
|
|
1706
|
+
if provider_name in ready_override:
|
|
1707
|
+
statuses[provider_name] = {"ready": True, "source": "env"}
|
|
1708
|
+
continue
|
|
1709
|
+
|
|
1710
|
+
if provider_name == "claude":
|
|
1711
|
+
claude_bin = os.environ.get("OMG_CLAUDE_BIN", "claude")
|
|
1712
|
+
cmd = os.environ.get("OMG_CLAUDE_WORKER_CMD", "").strip()
|
|
1713
|
+
ready = bool(cmd) or shutil.which(claude_bin) is not None
|
|
1714
|
+
statuses[provider_name] = {
|
|
1715
|
+
"ready": ready,
|
|
1716
|
+
"source": "env-cmd" if cmd else "path",
|
|
1717
|
+
"detail": cmd or claude_bin,
|
|
1718
|
+
}
|
|
1719
|
+
continue
|
|
1720
|
+
|
|
1721
|
+
if provider_name == "gemini":
|
|
1722
|
+
import runtime.providers.gemini_provider # noqa: F401
|
|
1723
|
+
elif provider_name == "kimi":
|
|
1724
|
+
import runtime.providers.kimi_provider # noqa: F401
|
|
1725
|
+
else:
|
|
1726
|
+
import runtime.providers.codex_provider # noqa: F401
|
|
1727
|
+
from runtime.cli_provider import get_provider
|
|
1728
|
+
|
|
1729
|
+
provider = get_provider(provider_name)
|
|
1730
|
+
ready = bool(provider and provider.detect())
|
|
1731
|
+
statuses[provider_name] = {"ready": ready, "source": "provider"}
|
|
1732
|
+
|
|
1733
|
+
return statuses
|
|
1734
|
+
|
|
1735
|
+
|
|
1736
|
+
def _check_mcp_fabric() -> dict[str, Any]:
|
|
1737
|
+
import runtime.omg_mcp_server as omg_mcp_server
|
|
1738
|
+
|
|
1739
|
+
prompts = asyncio.run(omg_mcp_server.mcp.list_prompts())
|
|
1740
|
+
resources = asyncio.run(omg_mcp_server.mcp.list_resources())
|
|
1741
|
+
instructions = getattr(omg_mcp_server.mcp, "instructions", "")
|
|
1742
|
+
return {
|
|
1743
|
+
"ready": isinstance(instructions, str) and bool(instructions.strip()) and len(prompts) >= 1 and len(resources) >= 1,
|
|
1744
|
+
"prompt_count": len(prompts),
|
|
1745
|
+
"resource_count": len(resources),
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
|
|
1749
|
+
def _check_plugin_command_paths(root: Path) -> dict[str, Any]:
|
|
1750
|
+
blockers: list[str] = []
|
|
1751
|
+
details: dict[str, Any] = {}
|
|
1752
|
+
|
|
1753
|
+
plugin_specs: list[tuple[str, Path, Path]] = [
|
|
1754
|
+
("core", root / "plugins" / "core" / "plugin.json", root),
|
|
1755
|
+
("advanced", root / "plugins" / "advanced" / "plugin.json", root / "plugins" / "advanced"),
|
|
1756
|
+
]
|
|
1757
|
+
|
|
1758
|
+
for plugin_name, manifest_path, resolve_root in plugin_specs:
|
|
1759
|
+
plugin_detail: dict[str, Any] = {"manifest": str(manifest_path), "commands": {}}
|
|
1760
|
+
if not manifest_path.exists():
|
|
1761
|
+
blockers.append(f"plugin_command_paths: missing manifest {manifest_path.relative_to(root)}")
|
|
1762
|
+
plugin_detail["status"] = "error"
|
|
1763
|
+
details[plugin_name] = plugin_detail
|
|
1764
|
+
continue
|
|
1765
|
+
|
|
1766
|
+
try:
|
|
1767
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
1768
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
1769
|
+
blockers.append(f"plugin_command_paths: unreadable manifest {manifest_path.relative_to(root)}: {exc}")
|
|
1770
|
+
plugin_detail["status"] = "error"
|
|
1771
|
+
details[plugin_name] = plugin_detail
|
|
1772
|
+
continue
|
|
1773
|
+
|
|
1774
|
+
commands = manifest.get("commands", {})
|
|
1775
|
+
missing: list[str] = []
|
|
1776
|
+
for cmd_name, cmd_config in commands.items():
|
|
1777
|
+
cmd_path = cmd_config.get("path", "")
|
|
1778
|
+
resolved = resolve_root / cmd_path
|
|
1779
|
+
plugin_detail["commands"][cmd_name] = str(cmd_path)
|
|
1780
|
+
if not resolved.exists():
|
|
1781
|
+
missing.append(cmd_path)
|
|
1782
|
+
blockers.append(
|
|
1783
|
+
f"plugin_command_paths: {plugin_name} command '{cmd_name}' missing source {cmd_path}"
|
|
1784
|
+
)
|
|
1785
|
+
|
|
1786
|
+
plugin_detail["missing"] = missing
|
|
1787
|
+
plugin_detail["status"] = "ok" if not missing else "error"
|
|
1788
|
+
details[plugin_name] = plugin_detail
|
|
1789
|
+
|
|
1790
|
+
return {
|
|
1791
|
+
"status": "ok" if not blockers else "error",
|
|
1792
|
+
"blockers": blockers,
|
|
1793
|
+
"details": details,
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
|
|
1797
|
+
def _check_version_identity_drift(root: Path) -> dict[str, Any]:
|
|
1798
|
+
canonical_version = CANONICAL_VERSION
|
|
1799
|
+
blockers: list[str] = []
|
|
1800
|
+
drift_details: dict[str, str] = {}
|
|
1801
|
+
|
|
1802
|
+
from runtime.release_surfaces import AUTHORED_SURFACES
|
|
1803
|
+
|
|
1804
|
+
sync_script = Path(__file__).resolve().parents[1] / "scripts" / "sync-release-identity.py"
|
|
1805
|
+
if not sync_script.exists():
|
|
1806
|
+
return {
|
|
1807
|
+
"status": "error",
|
|
1808
|
+
"canonical_version": canonical_version,
|
|
1809
|
+
"blockers": ["version_drift: missing scripts/sync-release-identity.py"],
|
|
1810
|
+
"drift_details": {},
|
|
1811
|
+
}
|
|
1812
|
+
|
|
1813
|
+
spec = importlib.util.spec_from_file_location("sync_release_identity", sync_script)
|
|
1814
|
+
if spec is None or spec.loader is None:
|
|
1815
|
+
return {
|
|
1816
|
+
"status": "error",
|
|
1817
|
+
"canonical_version": canonical_version,
|
|
1818
|
+
"blockers": ["version_drift: unable to load scripts/sync-release-identity.py"],
|
|
1819
|
+
"drift_details": {},
|
|
1820
|
+
}
|
|
1821
|
+
|
|
1822
|
+
module = importlib.util.module_from_spec(spec)
|
|
1823
|
+
spec.loader.exec_module(module)
|
|
1824
|
+
check_surface = getattr(module, "check_surface", None)
|
|
1825
|
+
if not callable(check_surface):
|
|
1826
|
+
return {
|
|
1827
|
+
"status": "error",
|
|
1828
|
+
"canonical_version": canonical_version,
|
|
1829
|
+
"blockers": ["version_drift: scripts/sync-release-identity.py missing check_surface"],
|
|
1830
|
+
"drift_details": {},
|
|
1831
|
+
}
|
|
1832
|
+
|
|
1833
|
+
for surface in AUTHORED_SURFACES:
|
|
1834
|
+
raw_drifts = check_surface(root, surface, canonical_version)
|
|
1835
|
+
if not isinstance(raw_drifts, list):
|
|
1836
|
+
blockers.append(
|
|
1837
|
+
f"version_drift: {surface.file_path} has version <invalid drift payload>, expected {canonical_version}"
|
|
1838
|
+
)
|
|
1839
|
+
continue
|
|
1840
|
+
for drift in raw_drifts:
|
|
1841
|
+
if not isinstance(drift, (tuple, list)) or len(drift) != 2:
|
|
1842
|
+
continue
|
|
1843
|
+
label, found = drift
|
|
1844
|
+
found_value = "<not found>" if found is None else str(found)
|
|
1845
|
+
blockers.append(
|
|
1846
|
+
f"version_drift: {label} has version {found_value}, expected {canonical_version}"
|
|
1847
|
+
)
|
|
1848
|
+
drift_details[str(label)] = found_value
|
|
1849
|
+
|
|
1850
|
+
return {
|
|
1851
|
+
"status": "ok" if not blockers else "error",
|
|
1852
|
+
"canonical_version": canonical_version,
|
|
1853
|
+
"blockers": blockers,
|
|
1854
|
+
"drift_details": drift_details,
|
|
1855
|
+
}
|
|
1856
|
+
|
|
1857
|
+
|
|
1858
|
+
def _check_release_surface_drift(root: Path, output_root: Path) -> dict[str, Any]:
|
|
1859
|
+
blockers: list[str] = []
|
|
1860
|
+
checks: dict[str, Any] = {}
|
|
1861
|
+
|
|
1862
|
+
registry_errors = validate_registry()
|
|
1863
|
+
if registry_errors:
|
|
1864
|
+
return {
|
|
1865
|
+
"status": "error",
|
|
1866
|
+
"blockers": [f"release_surface_drift: registry invalid: {e}" for e in registry_errors],
|
|
1867
|
+
"checks": {"registry_valid": False},
|
|
1868
|
+
}
|
|
1869
|
+
checks["registry_valid"] = True
|
|
1870
|
+
|
|
1871
|
+
manifest_path = output_root / "dist" / "public" / "release-surface.json"
|
|
1872
|
+
if not manifest_path.exists():
|
|
1873
|
+
blockers.append("release_surface_drift: dist/public/release-surface.json not found")
|
|
1874
|
+
return {"status": "error", "blockers": blockers, "checks": checks}
|
|
1875
|
+
|
|
1876
|
+
try:
|
|
1877
|
+
manifest = json.loads(manifest_path.read_text(encoding="utf-8"))
|
|
1878
|
+
except (json.JSONDecodeError, OSError) as exc:
|
|
1879
|
+
blockers.append(f"release_surface_drift: release-surface.json unreadable: {exc}")
|
|
1880
|
+
return {"status": "error", "blockers": blockers, "checks": checks}
|
|
1881
|
+
|
|
1882
|
+
surfaces = get_public_surfaces()
|
|
1883
|
+
manifest_surfaces = manifest.get("surfaces", [])
|
|
1884
|
+
manifest_ids = {s.get("id") for s in manifest_surfaces if isinstance(s, dict)}
|
|
1885
|
+
registry_ids = {s["id"] for s in surfaces}
|
|
1886
|
+
|
|
1887
|
+
public_launchers = [
|
|
1888
|
+
s for s in surfaces
|
|
1889
|
+
if s.get("category") == "launcher" and s.get("scope") == "public"
|
|
1890
|
+
]
|
|
1891
|
+
launcher_names = {s.get("launcher_name") for s in public_launchers}
|
|
1892
|
+
launcher_in_manifest = any(
|
|
1893
|
+
s.get("launcher_name") == "omg" and s.get("scope") == "public"
|
|
1894
|
+
for s in manifest_surfaces
|
|
1895
|
+
if isinstance(s, dict) and s.get("category") == "launcher"
|
|
1896
|
+
)
|
|
1897
|
+
checks["launcher_omg_in_manifest"] = launcher_in_manifest
|
|
1898
|
+
if "omg" in launcher_names and not launcher_in_manifest:
|
|
1899
|
+
blockers.append("release_surface_drift: launcher 'omg' missing from compiled manifest")
|
|
1900
|
+
|
|
1901
|
+
pkg_path = root / "package.json"
|
|
1902
|
+
if pkg_path.exists():
|
|
1903
|
+
try:
|
|
1904
|
+
pkg = json.loads(pkg_path.read_text(encoding="utf-8"))
|
|
1905
|
+
except (json.JSONDecodeError, OSError):
|
|
1906
|
+
pkg = {}
|
|
1907
|
+
else:
|
|
1908
|
+
pkg = {}
|
|
1909
|
+
|
|
1910
|
+
expected_bin_key = next(
|
|
1911
|
+
(s.get("bin_key") for s in surfaces if s.get("id") == "npm_bin_key"),
|
|
1912
|
+
"omg",
|
|
1913
|
+
)
|
|
1914
|
+
bin_field = pkg.get("bin", {})
|
|
1915
|
+
has_bin_key = isinstance(bin_field, dict) and expected_bin_key in bin_field
|
|
1916
|
+
checks["npm_bin_key"] = has_bin_key
|
|
1917
|
+
if not has_bin_key:
|
|
1918
|
+
blockers.append(f"release_surface_drift: package.json missing npm bin.{expected_bin_key}")
|
|
1919
|
+
|
|
1920
|
+
expected_check_name = next(
|
|
1921
|
+
(s.get("check_name") for s in surfaces if s.get("id") == "github_check_run_name"),
|
|
1922
|
+
"OMG PR Reviewer",
|
|
1923
|
+
)
|
|
1924
|
+
manifest_check_names = [
|
|
1925
|
+
s.get("check_name")
|
|
1926
|
+
for s in manifest_surfaces
|
|
1927
|
+
if isinstance(s, dict) and s.get("category") == "check_name"
|
|
1928
|
+
]
|
|
1929
|
+
check_name_match = expected_check_name in manifest_check_names
|
|
1930
|
+
checks["github_check_name"] = check_name_match
|
|
1931
|
+
if not check_name_match:
|
|
1932
|
+
blockers.append(
|
|
1933
|
+
f"release_surface_drift: GitHub check name '{expected_check_name}' not in compiled manifest"
|
|
1934
|
+
)
|
|
1935
|
+
|
|
1936
|
+
action_path_entry: str = next(
|
|
1937
|
+
(s["path"] for s in surfaces if s.get("id") == "action_yaml" and "path" in s),
|
|
1938
|
+
"action.yml",
|
|
1939
|
+
)
|
|
1940
|
+
action_exists = (root / action_path_entry).exists()
|
|
1941
|
+
checks["action_yml_exists"] = action_exists
|
|
1942
|
+
if not action_exists:
|
|
1943
|
+
blockers.append(f"release_surface_drift: {action_path_entry} not found")
|
|
1944
|
+
|
|
1945
|
+
release_text_result = compile_release_surfaces(root, check_only=True)
|
|
1946
|
+
checks["release_text_drift"] = release_text_result
|
|
1947
|
+
for item in release_text_result.get("drift", []):
|
|
1948
|
+
label = item if isinstance(item, str) else f"{item.get('surface', 'unknown')}: {item.get('reason', 'drift')}"
|
|
1949
|
+
blockers.append(f"release_text_drift: {label}")
|
|
1950
|
+
|
|
1951
|
+
docs_result = check_docs(root)
|
|
1952
|
+
checks["docs_drift"] = docs_result
|
|
1953
|
+
for item in docs_result.get("drift", []):
|
|
1954
|
+
blockers.append(f"docs_drift: {item}")
|
|
1955
|
+
|
|
1956
|
+
return {
|
|
1957
|
+
"status": "ok" if not blockers else "error",
|
|
1958
|
+
"blockers": blockers,
|
|
1959
|
+
"checks": checks,
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
|
|
1963
|
+
def _check_doctor_output(output_root: Path) -> dict[str, Any]:
|
|
1964
|
+
evidence_dir = output_root / ".omg" / "evidence"
|
|
1965
|
+
doctor_path = evidence_dir / "doctor.json"
|
|
1966
|
+
if not doctor_path.exists():
|
|
1967
|
+
return {
|
|
1968
|
+
"status": "error",
|
|
1969
|
+
"path": "",
|
|
1970
|
+
"doctor": {},
|
|
1971
|
+
"blockers": ["doctor_check_missing: missing .omg/evidence/doctor.json"],
|
|
1972
|
+
}
|
|
1973
|
+
try:
|
|
1974
|
+
payload = _load_json(doctor_path)
|
|
1975
|
+
except Exception as exc:
|
|
1976
|
+
return {
|
|
1977
|
+
"status": "error",
|
|
1978
|
+
"path": str(doctor_path.relative_to(output_root)),
|
|
1979
|
+
"doctor": {},
|
|
1980
|
+
"blockers": [f"doctor_check_missing: invalid doctor output ({exc})"],
|
|
1981
|
+
}
|
|
1982
|
+
|
|
1983
|
+
blockers: list[str] = []
|
|
1984
|
+
if payload.get("schema") != "DoctorResult":
|
|
1985
|
+
blockers.append("doctor_check_missing: doctor evidence schema mismatch")
|
|
1986
|
+
if payload.get("status") != "pass":
|
|
1987
|
+
blockers.append("doctor_check_missing: doctor status is not pass")
|
|
1988
|
+
checks = payload.get("checks", [])
|
|
1989
|
+
if not isinstance(checks, list) or not checks:
|
|
1990
|
+
blockers.append("doctor_check_missing: doctor checks missing")
|
|
1991
|
+
|
|
1992
|
+
return {
|
|
1993
|
+
"status": "ok" if not blockers else "error",
|
|
1994
|
+
"path": str(doctor_path.relative_to(output_root)),
|
|
1995
|
+
"doctor": payload,
|
|
1996
|
+
"blockers": blockers,
|
|
1997
|
+
}
|
|
1998
|
+
|
|
1999
|
+
|
|
2000
|
+
def _check_proof_surface(root: Path) -> dict[str, Any]:
|
|
2001
|
+
proof_path = root / "docs" / "proof.md"
|
|
2002
|
+
if not proof_path.exists():
|
|
2003
|
+
return {
|
|
2004
|
+
"status": "error",
|
|
2005
|
+
"path": "docs/proof.md",
|
|
2006
|
+
"blockers": ["prose_only_proof: docs/proof.md missing"],
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
content = proof_path.read_text(encoding="utf-8")
|
|
2010
|
+
lowered = content.lower()
|
|
2011
|
+
hardcoded_counts = bool(
|
|
2012
|
+
re.search(r"\b\d+\s*/\s*\d+\b", lowered)
|
|
2013
|
+
or re.search(r"\b\d+\s+(tests?|checks?|providers?)\s+(passed|pass|green|successful)\b", lowered)
|
|
2014
|
+
or re.search(r"\ball\s+tests?\s+passed\b", lowered)
|
|
2015
|
+
)
|
|
2016
|
+
artifact_refs = (
|
|
2017
|
+
".omg/evidence/",
|
|
2018
|
+
".omg/tracebank/",
|
|
2019
|
+
".omg/evals/",
|
|
2020
|
+
".omg/lineage/",
|
|
2021
|
+
)
|
|
2022
|
+
has_artifact_refs = any(token in content for token in artifact_refs)
|
|
2023
|
+
|
|
2024
|
+
blockers: list[str] = []
|
|
2025
|
+
if hardcoded_counts and not has_artifact_refs:
|
|
2026
|
+
blockers.append("prose_only_proof: hardcoded proof counts without machine artifact references")
|
|
2027
|
+
|
|
2028
|
+
return {
|
|
2029
|
+
"status": "ok" if not blockers else "error",
|
|
2030
|
+
"path": str(proof_path.relative_to(root)),
|
|
2031
|
+
"hardcoded_counts": hardcoded_counts,
|
|
2032
|
+
"has_artifact_refs": has_artifact_refs,
|
|
2033
|
+
"blockers": blockers,
|
|
2034
|
+
}
|
|
2035
|
+
|
|
2036
|
+
|
|
2037
|
+
def _is_loopback_hostname(hostname: str) -> bool:
|
|
2038
|
+
lowered = hostname.strip().lower()
|
|
2039
|
+
return lowered in {"localhost", "127.0.0.1", "::1"}
|
|
2040
|
+
|
|
2041
|
+
|
|
2042
|
+
def _collect_http_urls(line: str) -> list[str]:
|
|
2043
|
+
return re.findall(r"https?://[^\s)\]>'\"]+", line)
|
|
2044
|
+
|
|
2045
|
+
|
|
2046
|
+
def _check_same_machine_scope(root: Path, output_root: Path) -> dict[str, Any]:
|
|
2047
|
+
blockers: list[str] = []
|
|
2048
|
+
scanned: list[str] = []
|
|
2049
|
+
|
|
2050
|
+
for rel_path in ("README.md", "docs/proof.md", "OMG_COMPAT_CONTRACT.md"):
|
|
2051
|
+
path = root / rel_path
|
|
2052
|
+
if not path.exists():
|
|
2053
|
+
continue
|
|
2054
|
+
scanned.append(rel_path)
|
|
2055
|
+
for line in path.read_text(encoding="utf-8").splitlines():
|
|
2056
|
+
if "production" not in line.lower():
|
|
2057
|
+
continue
|
|
2058
|
+
for url in _collect_http_urls(line):
|
|
2059
|
+
parsed = urlparse(url)
|
|
2060
|
+
if parsed.scheme != "http":
|
|
2061
|
+
continue
|
|
2062
|
+
host = parsed.hostname or ""
|
|
2063
|
+
if host and not _is_loopback_hostname(host):
|
|
2064
|
+
blockers.append(
|
|
2065
|
+
f"same_machine_scope_violation: {rel_path} claims production over non-loopback HTTP ({url})"
|
|
2066
|
+
)
|
|
2067
|
+
|
|
2068
|
+
mcp_path = output_root / ".mcp.json"
|
|
2069
|
+
if mcp_path.exists():
|
|
2070
|
+
scanned.append(str(mcp_path.relative_to(output_root)))
|
|
2071
|
+
mcp_payload = _load_json(mcp_path)
|
|
2072
|
+
servers = mcp_payload.get("mcpServers", {})
|
|
2073
|
+
if isinstance(servers, dict):
|
|
2074
|
+
for server_name, server_cfg in servers.items():
|
|
2075
|
+
if not isinstance(server_cfg, dict):
|
|
2076
|
+
continue
|
|
2077
|
+
for key in ("url", "httpUrl"):
|
|
2078
|
+
raw_url = str(server_cfg.get(key, "")).strip()
|
|
2079
|
+
if not raw_url:
|
|
2080
|
+
continue
|
|
2081
|
+
parsed = urlparse(raw_url)
|
|
2082
|
+
if parsed.scheme != "http":
|
|
2083
|
+
continue
|
|
2084
|
+
host = parsed.hostname or ""
|
|
2085
|
+
if host and not _is_loopback_hostname(host):
|
|
2086
|
+
blockers.append(
|
|
2087
|
+
"same_machine_scope_violation: "
|
|
2088
|
+
f".mcp.json server '{server_name}' uses non-loopback HTTP endpoint ({raw_url})"
|
|
2089
|
+
)
|
|
2090
|
+
|
|
2091
|
+
return {
|
|
2092
|
+
"status": "ok" if not blockers else "error",
|
|
2093
|
+
"scanned": scanned,
|
|
2094
|
+
"blockers": blockers,
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
|
|
2098
|
+
def _check_provider_host_parity(output_root: Path, providers: dict[str, dict[str, Any]]) -> dict[str, Any]:
|
|
2099
|
+
blockers: list[str] = []
|
|
2100
|
+
required_for_provider = {
|
|
2101
|
+
"claude": (
|
|
2102
|
+
output_root / "settings.json",
|
|
2103
|
+
output_root / ".claude-plugin" / "plugin.json",
|
|
2104
|
+
),
|
|
2105
|
+
"codex": (
|
|
2106
|
+
output_root / ".agents" / "skills" / "omg" / "AGENTS.fragment.md",
|
|
2107
|
+
output_root / ".agents" / "skills" / "omg" / "codex-mcp.toml",
|
|
2108
|
+
),
|
|
2109
|
+
"gemini": (
|
|
2110
|
+
output_root / ".gemini" / "settings.json",
|
|
2111
|
+
),
|
|
2112
|
+
"kimi": (
|
|
2113
|
+
output_root / ".kimi" / "mcp.json",
|
|
2114
|
+
),
|
|
2115
|
+
}
|
|
2116
|
+
for provider, status in providers.items():
|
|
2117
|
+
if not status.get("ready"):
|
|
2118
|
+
continue
|
|
2119
|
+
for required_path in required_for_provider.get(provider, ()):
|
|
2120
|
+
if not required_path.exists():
|
|
2121
|
+
blockers.append(
|
|
2122
|
+
"provider_host_parity: "
|
|
2123
|
+
f"provider '{provider}' ready but host artifact missing {required_path.relative_to(output_root)}"
|
|
2124
|
+
)
|
|
2125
|
+
return {
|
|
2126
|
+
"status": "ok" if not blockers else "error",
|
|
2127
|
+
"blockers": blockers,
|
|
2128
|
+
}
|
|
2129
|
+
|
|
2130
|
+
|
|
2131
|
+
def _check_host_semantic_parity(
|
|
2132
|
+
output_root: Path,
|
|
2133
|
+
required_hosts: set[str],
|
|
2134
|
+
release_run_id: str = "",
|
|
2135
|
+
) -> dict[str, Any]:
|
|
2136
|
+
evidence_dir = output_root / ".omg" / "evidence"
|
|
2137
|
+
parity_files = sorted(evidence_dir.glob("host-parity-*.json")) if evidence_dir.exists() else []
|
|
2138
|
+
report_path = parity_files[-1] if parity_files else None
|
|
2139
|
+
require_report_env = os.environ.get("OMG_REQUIRE_HOST_PARITY_REPORT", "").strip().lower() in {
|
|
2140
|
+
"1",
|
|
2141
|
+
"true",
|
|
2142
|
+
"yes",
|
|
2143
|
+
"on",
|
|
2144
|
+
}
|
|
2145
|
+
canonical_required_hosts = {
|
|
2146
|
+
host
|
|
2147
|
+
for host in required_hosts
|
|
2148
|
+
if host in RELEASE_BLOCKING_HOSTS
|
|
2149
|
+
}
|
|
2150
|
+
require_report = require_report_env or bool(canonical_required_hosts)
|
|
2151
|
+
|
|
2152
|
+
if report_path is None:
|
|
2153
|
+
return {
|
|
2154
|
+
"status": "error" if require_report else "missing",
|
|
2155
|
+
"report": None,
|
|
2156
|
+
"blockers": ["host_semantic_parity: missing host parity report"] if require_report else [],
|
|
2157
|
+
}
|
|
2158
|
+
|
|
2159
|
+
try:
|
|
2160
|
+
report = _load_json(report_path)
|
|
2161
|
+
except Exception as exc:
|
|
2162
|
+
return {
|
|
2163
|
+
"status": "error",
|
|
2164
|
+
"report": str(report_path.relative_to(output_root)),
|
|
2165
|
+
"blockers": [f"host_semantic_parity: failed to parse report ({exc})"],
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
canonical_hosts = {
|
|
2169
|
+
str(host).strip().lower()
|
|
2170
|
+
for host in report.get("canonical_hosts", [])
|
|
2171
|
+
if str(host).strip()
|
|
2172
|
+
}
|
|
2173
|
+
missing_hosts = sorted(host for host in canonical_required_hosts if host not in canonical_hosts)
|
|
2174
|
+
overall_status = str(report.get("overall_status", "")).strip().lower()
|
|
2175
|
+
parity_results = report.get("parity_results", {})
|
|
2176
|
+
passed = bool(parity_results.get("passed")) if isinstance(parity_results, dict) else False
|
|
2177
|
+
host_results = parity_results.get("host_results", {}) if isinstance(parity_results, dict) else {}
|
|
2178
|
+
|
|
2179
|
+
blockers: list[str] = []
|
|
2180
|
+
report_run_id = str(report.get("run_id", "")).strip()
|
|
2181
|
+
if release_run_id and report_run_id and report_run_id != release_run_id:
|
|
2182
|
+
blockers.append("host_parity_report:cross_run")
|
|
2183
|
+
if missing_hosts:
|
|
2184
|
+
blockers.append(f"host_semantic_parity: report missing canonical hosts {missing_hosts}")
|
|
2185
|
+
if overall_status and overall_status != "ok":
|
|
2186
|
+
blockers.append(f"host_semantic_parity: report overall_status={overall_status}")
|
|
2187
|
+
if isinstance(parity_results, dict) and not passed:
|
|
2188
|
+
blockers.append("host_semantic_parity: parity check reported drift")
|
|
2189
|
+
if not isinstance(host_results, dict):
|
|
2190
|
+
blockers.append("host_semantic_parity: report missing host results")
|
|
2191
|
+
else:
|
|
2192
|
+
for host in sorted(canonical_required_hosts):
|
|
2193
|
+
host_result = host_results.get(host)
|
|
2194
|
+
normalized = host_result.get("normalized", {}) if isinstance(host_result, dict) else {}
|
|
2195
|
+
source_class = str(normalized.get("source_class", "")).strip().lower() if isinstance(normalized, dict) else ""
|
|
2196
|
+
source_path = str(normalized.get("source_path", "")).strip() if isinstance(normalized, dict) else ""
|
|
2197
|
+
if source_class != "compiled_or_replayed" or not source_path:
|
|
2198
|
+
blockers.append(f"host_semantic_parity: synthetic payload rejected for {host}")
|
|
2199
|
+
|
|
2200
|
+
return {
|
|
2201
|
+
"status": "ok" if not blockers else "error",
|
|
2202
|
+
"report": str(report_path.relative_to(output_root)),
|
|
2203
|
+
"required_hosts": sorted(canonical_required_hosts),
|
|
2204
|
+
"blockers": blockers,
|
|
2205
|
+
}
|
|
2206
|
+
|
|
2207
|
+
|
|
2208
|
+
def _has_waiver(risk: dict[str, Any]) -> bool:
|
|
2209
|
+
return bool(
|
|
2210
|
+
risk.get("waived")
|
|
2211
|
+
or risk.get("waiver")
|
|
2212
|
+
or risk.get("waiver_id")
|
|
2213
|
+
or risk.get("waiver_evidence")
|
|
2214
|
+
)
|
|
2215
|
+
|
|
2216
|
+
|
|
2217
|
+
def _check_high_risk_security_waivers(payload: dict[str, Any]) -> list[str]:
|
|
2218
|
+
blockers: list[str] = []
|
|
2219
|
+
unresolved = payload.get("unresolved_risks", [])
|
|
2220
|
+
if isinstance(unresolved, list):
|
|
2221
|
+
for item in unresolved:
|
|
2222
|
+
if isinstance(item, dict):
|
|
2223
|
+
severity = str(item.get("severity") or item.get("risk_level") or "").lower()
|
|
2224
|
+
if severity in {"high", "critical"} and not _has_waiver(item):
|
|
2225
|
+
blockers.append("security_blocker_unwaived: unresolved high-risk item without waiver evidence")
|
|
2226
|
+
break
|
|
2227
|
+
elif isinstance(item, str):
|
|
2228
|
+
lowered = item.lower()
|
|
2229
|
+
is_high = "high" in lowered or "critical" in lowered
|
|
2230
|
+
waived = "waiv" in lowered
|
|
2231
|
+
if is_high and not waived:
|
|
2232
|
+
blockers.append("security_blocker_unwaived: unresolved high-risk item without waiver evidence")
|
|
2233
|
+
break
|
|
2234
|
+
|
|
2235
|
+
scans = payload.get("security_scans", [])
|
|
2236
|
+
if isinstance(scans, list):
|
|
2237
|
+
for scan in scans:
|
|
2238
|
+
if not isinstance(scan, dict):
|
|
2239
|
+
continue
|
|
2240
|
+
findings = scan.get("findings", [])
|
|
2241
|
+
if not isinstance(findings, list):
|
|
2242
|
+
continue
|
|
2243
|
+
for finding in findings:
|
|
2244
|
+
if not isinstance(finding, dict):
|
|
2245
|
+
continue
|
|
2246
|
+
severity = str(finding.get("severity", "")).lower()
|
|
2247
|
+
if severity in {"high", "critical"} and not _has_waiver(finding):
|
|
2248
|
+
blockers.append("security_blocker_unwaived: high-risk security finding without waiver evidence")
|
|
2249
|
+
return blockers
|
|
2250
|
+
return blockers
|
|
2251
|
+
|
|
2252
|
+
|
|
2253
|
+
def _check_policy_pack_signatures(root: Path) -> dict[str, Any]:
|
|
2254
|
+
enforcing = os.environ.get("OMG_REQUIRE_TRUSTED_POLICY_PACKS") == "1"
|
|
2255
|
+
if not enforcing:
|
|
2256
|
+
return {"status": "ok", "enforcing": False, "blockers": []}
|
|
2257
|
+
|
|
2258
|
+
packs_dir = root / "registry" / "policy-packs"
|
|
2259
|
+
blockers: list[str] = []
|
|
2260
|
+
|
|
2261
|
+
for pack_id in POLICY_PACK_IDS:
|
|
2262
|
+
lock_path = packs_dir / f"{pack_id}.lock.json"
|
|
2263
|
+
sig_path = packs_dir / f"{pack_id}.signature.json"
|
|
2264
|
+
|
|
2265
|
+
if not lock_path.is_file() or not sig_path.is_file():
|
|
2266
|
+
blockers.append(f"policy_pack_signature: {pack_id} unsigned: lock or sig file missing")
|
|
2267
|
+
continue
|
|
2268
|
+
|
|
2269
|
+
try:
|
|
2270
|
+
lock_data = json.loads(lock_path.read_text(encoding="utf-8"))
|
|
2271
|
+
sig_data = json.loads(sig_path.read_text(encoding="utf-8"))
|
|
2272
|
+
except Exception:
|
|
2273
|
+
blockers.append(f"policy_pack_signature: {pack_id} unsigned: lock or sig file missing")
|
|
2274
|
+
continue
|
|
2275
|
+
|
|
2276
|
+
expected_digest = str(lock_data.get("canonical_digest", "")).strip()
|
|
2277
|
+
if not expected_digest:
|
|
2278
|
+
blockers.append(f"policy_pack_signature: {pack_id} unsigned: lock or sig file missing")
|
|
2279
|
+
continue
|
|
2280
|
+
|
|
2281
|
+
# Tampered check: recompute pack content digest and compare with lockfile
|
|
2282
|
+
pack_path = packs_dir / f"{pack_id}.yaml"
|
|
2283
|
+
if pack_path.is_file():
|
|
2284
|
+
try:
|
|
2285
|
+
pack_content = yaml.safe_load(pack_path.read_text(encoding="utf-8"))
|
|
2286
|
+
if isinstance(pack_content, dict):
|
|
2287
|
+
canonical = json.dumps(
|
|
2288
|
+
pack_content, sort_keys=True, separators=(",", ":"), ensure_ascii=True
|
|
2289
|
+
).encode("utf-8")
|
|
2290
|
+
actual_digest = hashlib.sha256(canonical).hexdigest()
|
|
2291
|
+
if actual_digest != expected_digest:
|
|
2292
|
+
blockers.append(
|
|
2293
|
+
f"policy_pack_signature: {pack_id} tampered: pack content changed since signing"
|
|
2294
|
+
)
|
|
2295
|
+
continue
|
|
2296
|
+
except Exception:
|
|
2297
|
+
pass
|
|
2298
|
+
|
|
2299
|
+
# Lockfile signer_public_key vs trust root mismatch check
|
|
2300
|
+
lockfile_signer_pk = str(lock_data.get("signer_public_key", "")).strip()
|
|
2301
|
+
if lockfile_signer_pk:
|
|
2302
|
+
signer_key_id = str(lock_data.get("signer_key_id", "")).strip()
|
|
2303
|
+
if signer_key_id:
|
|
2304
|
+
try:
|
|
2305
|
+
trusted_signers = _load_trusted_signers()
|
|
2306
|
+
signer = trusted_signers.get(signer_key_id)
|
|
2307
|
+
if signer and str(signer.get("public_key", "")).strip() != lockfile_signer_pk:
|
|
2308
|
+
blockers.append(
|
|
2309
|
+
f"policy_pack_signature: {pack_id} lockfile_mismatch: signer_public_key doesn't match trust root"
|
|
2310
|
+
)
|
|
2311
|
+
continue
|
|
2312
|
+
except Exception:
|
|
2313
|
+
pass
|
|
2314
|
+
|
|
2315
|
+
result = verify_approval_artifact(sig_data, expected_digest)
|
|
2316
|
+
if not result.get("valid"):
|
|
2317
|
+
reason = str(result.get("reason", "")).strip()
|
|
2318
|
+
if reason == "unknown signer key id":
|
|
2319
|
+
blockers.append(
|
|
2320
|
+
f"policy_pack_signature: {pack_id} untrusted: signer key not in trust root"
|
|
2321
|
+
)
|
|
2322
|
+
elif reason == "invalid approval signature":
|
|
2323
|
+
blockers.append(
|
|
2324
|
+
f"policy_pack_signature: {pack_id} invalid_signature: cryptographic verification failed"
|
|
2325
|
+
)
|
|
2326
|
+
else:
|
|
2327
|
+
blockers.append(f"policy_pack_signature: {pack_id} failed: {reason}")
|
|
2328
|
+
|
|
2329
|
+
return {
|
|
2330
|
+
"status": "ok" if not blockers else "error",
|
|
2331
|
+
"enforcing": True,
|
|
2332
|
+
"blockers": blockers,
|
|
2333
|
+
}
|
|
2334
|
+
|
|
2335
|
+
|
|
2336
|
+
def build_release_readiness(
|
|
2337
|
+
*,
|
|
2338
|
+
root_dir: str | Path | None = None,
|
|
2339
|
+
output_root: str | Path | None = None,
|
|
2340
|
+
channel: str = "dual",
|
|
2341
|
+
) -> dict[str, Any]:
|
|
2342
|
+
root = _resolve_root(root_dir)
|
|
2343
|
+
output = _resolve_output_root(root, output_root)
|
|
2344
|
+
cache_enabled = root == output
|
|
2345
|
+
if cache_enabled:
|
|
2346
|
+
cached = _load_release_readiness_cache(root, channel)
|
|
2347
|
+
if cached is not None:
|
|
2348
|
+
cached_result = dict(cached)
|
|
2349
|
+
cached_result["cache_hit"] = True
|
|
2350
|
+
return cached_result
|
|
2351
|
+
|
|
2352
|
+
stalled_workers = get_worker_watchdog(str(root)).get_stalled_workers()
|
|
2353
|
+
if stalled_workers:
|
|
2354
|
+
stalled_run_ids = [str(item.get("run_id", "")).strip() for item in stalled_workers if str(item.get("run_id", "")).strip()]
|
|
2355
|
+
blocker_suffix = ", ".join(stalled_run_ids) if stalled_run_ids else "unknown"
|
|
2356
|
+
return {
|
|
2357
|
+
"schema": "OmgReleaseReadinessResult",
|
|
2358
|
+
"status": "error",
|
|
2359
|
+
"channel": channel,
|
|
2360
|
+
"blockers": [f"worker_watchdog_stalled: stalled worker detected ({blocker_suffix})"],
|
|
2361
|
+
"checks": {
|
|
2362
|
+
"worker_watchdog": {
|
|
2363
|
+
"status": "error",
|
|
2364
|
+
"stalled": stalled_workers,
|
|
2365
|
+
}
|
|
2366
|
+
},
|
|
2367
|
+
"cache_hit": False,
|
|
2368
|
+
}
|
|
2369
|
+
|
|
2370
|
+
blockers: list[str] = []
|
|
2371
|
+
checks: dict[str, Any] = {}
|
|
2372
|
+
provider_override = {
|
|
2373
|
+
item.strip().lower()
|
|
2374
|
+
for item in os.environ.get("OMG_RELEASE_READY_PROVIDERS", "").split(",")
|
|
2375
|
+
if item.strip()
|
|
2376
|
+
}
|
|
2377
|
+
required_provider_hosts: set[str] = (
|
|
2378
|
+
{host for host in provider_override if host in RELEASE_BLOCKING_HOSTS}
|
|
2379
|
+
if provider_override
|
|
2380
|
+
else set(RELEASE_BLOCKING_HOSTS)
|
|
2381
|
+
)
|
|
2382
|
+
|
|
2383
|
+
validation = validate_contract_registry(root)
|
|
2384
|
+
checks["contract_validation"] = validation
|
|
2385
|
+
if validation["status"] != "ok":
|
|
2386
|
+
blockers.extend(validation["errors"])
|
|
2387
|
+
|
|
2388
|
+
required_channels = ["public", "enterprise"] if channel == "dual" else [channel]
|
|
2389
|
+
for required_channel in required_channels:
|
|
2390
|
+
dist_root = output / "dist" / required_channel
|
|
2391
|
+
manifest_path = dist_root / "manifest.json"
|
|
2392
|
+
if not manifest_path.exists():
|
|
2393
|
+
blockers.append(f"missing compiled manifest: dist/{required_channel}/manifest.json")
|
|
2394
|
+
continue
|
|
2395
|
+
manifest = _load_json(manifest_path)
|
|
2396
|
+
manifest_errors: list[str] = []
|
|
2397
|
+
for artifact in manifest.get("artifacts", []):
|
|
2398
|
+
if not isinstance(artifact, dict):
|
|
2399
|
+
continue
|
|
2400
|
+
rel_path = str(artifact.get("path", ""))
|
|
2401
|
+
expected_sha = str(artifact.get("sha256", ""))
|
|
2402
|
+
artifact_path = dist_root / rel_path
|
|
2403
|
+
if not artifact_path.exists():
|
|
2404
|
+
manifest_errors.append(f"{required_channel}: missing bundled artifact {rel_path}")
|
|
2405
|
+
continue
|
|
2406
|
+
if _sha256_file(artifact_path) != expected_sha:
|
|
2407
|
+
manifest_errors.append(f"{required_channel}: sha mismatch for {rel_path}")
|
|
2408
|
+
manifest_paths = {str(a.get("path", "")) for a in manifest.get("artifacts", []) if isinstance(a, dict)}
|
|
2409
|
+
declared_hosts = [str(host).strip().lower() for host in manifest.get("hosts", []) if str(host).strip()]
|
|
2410
|
+
if not declared_hosts:
|
|
2411
|
+
declared_hosts = get_canonical_hosts()
|
|
2412
|
+
missing_required_hosts = sorted(host for host in required_provider_hosts if host not in declared_hosts)
|
|
2413
|
+
if missing_required_hosts:
|
|
2414
|
+
manifest_errors.append(
|
|
2415
|
+
f"{required_channel}: canonical_host_compile_parity_missing {missing_required_hosts}"
|
|
2416
|
+
)
|
|
2417
|
+
required_provider_hosts.update(declared_hosts)
|
|
2418
|
+
for host_name in declared_hosts:
|
|
2419
|
+
for host_path in HOST_COMPILED_ARTIFACTS.get(host_name, ()):
|
|
2420
|
+
bundled_host_path = f"bundle/{host_path}"
|
|
2421
|
+
if bundled_host_path not in manifest_paths:
|
|
2422
|
+
manifest_errors.append(
|
|
2423
|
+
f"{required_channel}: host_parity_missing {host_name} {bundled_host_path}"
|
|
2424
|
+
)
|
|
2425
|
+
for req_path in _get_required_advanced_plugin_artifacts(root):
|
|
2426
|
+
if req_path not in manifest_paths:
|
|
2427
|
+
manifest_errors.append(f"{required_channel}: advanced_plugin_missing {req_path}")
|
|
2428
|
+
if manifest_errors:
|
|
2429
|
+
blockers.extend(manifest_errors)
|
|
2430
|
+
manifest["integrity_errors"] = manifest_errors
|
|
2431
|
+
checks[f"dist_{required_channel}"] = manifest
|
|
2432
|
+
|
|
2433
|
+
required_outputs = [
|
|
2434
|
+
output / host_artifact
|
|
2435
|
+
for host_name in RELEASE_BLOCKING_HOSTS
|
|
2436
|
+
for host_artifact in HOST_COMPILED_ARTIFACTS.get(host_name, ())
|
|
2437
|
+
]
|
|
2438
|
+
required_outputs.extend(
|
|
2439
|
+
[
|
|
2440
|
+
output / ".agents" / "skills" / "omg" / "control-plane" / "SKILL.md",
|
|
2441
|
+
output / ".agents" / "skills" / "omg" / "control-plane" / "openai.yaml",
|
|
2442
|
+
]
|
|
2443
|
+
)
|
|
2444
|
+
missing_outputs = [str(path.relative_to(output)) for path in required_outputs if not path.exists()]
|
|
2445
|
+
if missing_outputs:
|
|
2446
|
+
blockers.append(f"missing compiled outputs: {', '.join(missing_outputs)}")
|
|
2447
|
+
checks["compiled_outputs"] = {"missing": missing_outputs}
|
|
2448
|
+
|
|
2449
|
+
required_bundle_outputs: list[Path] = []
|
|
2450
|
+
for bundle_id in DEFAULT_REQUIRED_BUNDLES:
|
|
2451
|
+
required_bundle_outputs.extend(
|
|
2452
|
+
[
|
|
2453
|
+
output / ".agents" / "skills" / "omg" / bundle_id / "SKILL.md",
|
|
2454
|
+
output / ".agents" / "skills" / "omg" / bundle_id / "openai.yaml",
|
|
2455
|
+
]
|
|
2456
|
+
)
|
|
2457
|
+
missing_bundle_outputs = [str(path.relative_to(output)) for path in required_bundle_outputs if not path.exists()]
|
|
2458
|
+
if missing_bundle_outputs:
|
|
2459
|
+
blockers.append(f"missing bundle outputs: {', '.join(missing_bundle_outputs)}")
|
|
2460
|
+
checks["bundle_outputs"] = {"missing": missing_bundle_outputs}
|
|
2461
|
+
|
|
2462
|
+
evidence_check = _check_recent_evidence(output)
|
|
2463
|
+
checks["evidence"] = evidence_check
|
|
2464
|
+
blockers.extend(evidence_check.get("blockers", []))
|
|
2465
|
+
|
|
2466
|
+
doctor_check = _check_doctor_output(output)
|
|
2467
|
+
checks["doctor"] = doctor_check
|
|
2468
|
+
blockers.extend(doctor_check.get("blockers", []))
|
|
2469
|
+
|
|
2470
|
+
eval_check = _check_eval_gate(output)
|
|
2471
|
+
checks["eval_gate"] = eval_check
|
|
2472
|
+
blockers.extend(eval_check.get("blockers", []))
|
|
2473
|
+
|
|
2474
|
+
proof_chain_check = _check_proof_chain(output)
|
|
2475
|
+
checks["proof_chain"] = proof_chain_check
|
|
2476
|
+
blockers.extend(proof_chain_check.get("blockers", []))
|
|
2477
|
+
|
|
2478
|
+
execution_primitives = _check_execution_primitives(output_root=output, evidence_profile="release")
|
|
2479
|
+
checks["execution_primitives"] = execution_primitives
|
|
2480
|
+
blockers.extend(execution_primitives.get("blockers", []))
|
|
2481
|
+
|
|
2482
|
+
claim_judge_compliance = _check_claim_judge_compliance(output)
|
|
2483
|
+
checks["claim_judge_compliance"] = claim_judge_compliance
|
|
2484
|
+
blockers.extend(claim_judge_compliance.get("blockers", []))
|
|
2485
|
+
|
|
2486
|
+
security_blockers = [
|
|
2487
|
+
blocker
|
|
2488
|
+
for blocker in evidence_check.get("blockers", [])
|
|
2489
|
+
if isinstance(blocker, str) and blocker.startswith("security_blocker_unwaived:")
|
|
2490
|
+
]
|
|
2491
|
+
checks["security_blocker_unwaived"] = {
|
|
2492
|
+
"status": "ok" if not security_blockers else "error",
|
|
2493
|
+
"blockers": security_blockers,
|
|
2494
|
+
}
|
|
2495
|
+
|
|
2496
|
+
proof_surface_check = _check_proof_surface(root)
|
|
2497
|
+
checks["proof_surface"] = proof_surface_check
|
|
2498
|
+
blockers.extend(proof_surface_check.get("blockers", []))
|
|
2499
|
+
|
|
2500
|
+
same_machine_scope = _check_same_machine_scope(root, output)
|
|
2501
|
+
checks["same_machine_scope"] = same_machine_scope
|
|
2502
|
+
blockers.extend(same_machine_scope.get("blockers", []))
|
|
2503
|
+
|
|
2504
|
+
package_check = _check_packaged_install_smoke(root)
|
|
2505
|
+
checks["package_smoke"] = package_check
|
|
2506
|
+
blockers.extend(package_check.get("blockers", []))
|
|
2507
|
+
|
|
2508
|
+
package_parity = check_package_parity(root)
|
|
2509
|
+
checks["package_parity"] = package_parity
|
|
2510
|
+
blockers.extend(package_parity.get("blockers", []))
|
|
2511
|
+
|
|
2512
|
+
plugin_cmd_check = _check_plugin_command_paths(root)
|
|
2513
|
+
checks["plugin_command_paths"] = plugin_cmd_check
|
|
2514
|
+
blockers.extend(plugin_cmd_check.get("blockers", []))
|
|
2515
|
+
|
|
2516
|
+
version_drift_check = _check_version_identity_drift(root)
|
|
2517
|
+
checks["version_identity_drift"] = version_drift_check
|
|
2518
|
+
blockers.extend(version_drift_check.get("blockers", []))
|
|
2519
|
+
|
|
2520
|
+
surface_drift_check = _check_release_surface_drift(root, output)
|
|
2521
|
+
checks["release_surface_drift"] = surface_drift_check
|
|
2522
|
+
blockers.extend(surface_drift_check.get("blockers", []))
|
|
2523
|
+
|
|
2524
|
+
policy_sig_check = _check_policy_pack_signatures(root)
|
|
2525
|
+
checks["policy_pack_signatures"] = policy_sig_check
|
|
2526
|
+
blockers.extend(policy_sig_check.get("blockers", []))
|
|
2527
|
+
|
|
2528
|
+
if channel == "dual":
|
|
2529
|
+
bundle_promotion_parity = _check_bundle_promotion_parity(root, output)
|
|
2530
|
+
checks["bundle_promotion_parity"] = bundle_promotion_parity
|
|
2531
|
+
blockers.extend(bundle_promotion_parity.get("blockers", []))
|
|
2532
|
+
|
|
2533
|
+
try:
|
|
2534
|
+
with ThreadPoolExecutor(max_workers=4) as pool:
|
|
2535
|
+
provider_future = pool.submit(_provider_statuses)
|
|
2536
|
+
providers = provider_future.result(timeout=15)
|
|
2537
|
+
except FuturesTimeoutError:
|
|
2538
|
+
providers = {provider_name: {"ready": False, "source": "timeout"} for provider_name in SUPPORTED_HOSTS}
|
|
2539
|
+
blockers.append("provider_status_timeout: provider readiness checks exceeded 15 seconds")
|
|
2540
|
+
except Exception as exc:
|
|
2541
|
+
providers = {provider_name: {"ready": False, "source": "error", "detail": str(exc)} for provider_name in SUPPORTED_HOSTS}
|
|
2542
|
+
blockers.append("provider_status_error: provider readiness checks failed")
|
|
2543
|
+
|
|
2544
|
+
checks["providers"] = providers
|
|
2545
|
+
for provider_name, status in providers.items():
|
|
2546
|
+
if provider_name not in required_provider_hosts:
|
|
2547
|
+
continue
|
|
2548
|
+
if not status.get("ready"):
|
|
2549
|
+
blockers.append(f"provider not ready: {provider_name}")
|
|
2550
|
+
|
|
2551
|
+
required_providers = {
|
|
2552
|
+
provider_name: status
|
|
2553
|
+
for provider_name, status in providers.items()
|
|
2554
|
+
if provider_name in required_provider_hosts
|
|
2555
|
+
}
|
|
2556
|
+
provider_parity = _check_provider_host_parity(output, required_providers)
|
|
2557
|
+
checks["provider_host_parity"] = provider_parity
|
|
2558
|
+
blockers.extend(provider_parity.get("blockers", []))
|
|
2559
|
+
|
|
2560
|
+
host_semantic_parity = _check_host_semantic_parity(
|
|
2561
|
+
output,
|
|
2562
|
+
required_provider_hosts,
|
|
2563
|
+
release_run_id=str(evidence_check.get("run_id", "")).strip(),
|
|
2564
|
+
)
|
|
2565
|
+
checks["host_semantic_parity"] = host_semantic_parity
|
|
2566
|
+
blockers.extend(host_semantic_parity.get("blockers", []))
|
|
2567
|
+
|
|
2568
|
+
worktree_ready = shutil.which("git") is not None and (root / ".git").exists()
|
|
2569
|
+
checks["worktree"] = {"ready": worktree_ready}
|
|
2570
|
+
if not worktree_ready:
|
|
2571
|
+
blockers.append("git worktree support not available")
|
|
2572
|
+
|
|
2573
|
+
mcp_status = _check_mcp_fabric()
|
|
2574
|
+
checks["mcp_fabric"] = mcp_status
|
|
2575
|
+
if not mcp_status.get("ready"):
|
|
2576
|
+
blockers.append("mcp fabric incomplete")
|
|
2577
|
+
|
|
2578
|
+
result = {
|
|
2579
|
+
"schema": "OmgReleaseReadinessResult",
|
|
2580
|
+
"status": "ok" if not blockers else "error",
|
|
2581
|
+
"channel": channel,
|
|
2582
|
+
"blockers": blockers,
|
|
2583
|
+
"checks": checks,
|
|
2584
|
+
"cache_hit": False,
|
|
2585
|
+
}
|
|
2586
|
+
|
|
2587
|
+
if cache_enabled:
|
|
2588
|
+
write_release_readiness_cache(result, root)
|
|
2589
|
+
return result
|
|
2590
|
+
|
|
2591
|
+
|
|
2592
|
+
def _check_recent_evidence(output_root: Path) -> dict[str, Any]:
|
|
2593
|
+
latest = _latest_evidence_pack(output_root)
|
|
2594
|
+
if latest is None:
|
|
2595
|
+
return {"status": "missing", "blockers": []}
|
|
2596
|
+
|
|
2597
|
+
evidence_path, payload = latest
|
|
2598
|
+
blockers: list[str] = []
|
|
2599
|
+
if not payload.get("security_scans"):
|
|
2600
|
+
blockers.append("cosmetic evidence: security_scans is empty")
|
|
2601
|
+
if not payload.get("provenance"):
|
|
2602
|
+
blockers.append("cosmetic evidence: provenance is empty")
|
|
2603
|
+
if not payload.get("timestamp") and not payload.get("created_at"):
|
|
2604
|
+
blockers.append("missing_attribution: evidence missing timestamp")
|
|
2605
|
+
if not payload.get("executor"):
|
|
2606
|
+
blockers.append("missing_attribution: evidence missing executor")
|
|
2607
|
+
if not payload.get("environment"):
|
|
2608
|
+
blockers.append("missing_attribution: evidence missing environment")
|
|
2609
|
+
if not payload.get("trace_ids"):
|
|
2610
|
+
blockers.append("missing trace ids in evidence")
|
|
2611
|
+
if not payload.get("trace_id") and not payload.get("trace_ids"):
|
|
2612
|
+
blockers.append("missing trace_id in evidence")
|
|
2613
|
+
if not payload.get("lineage"):
|
|
2614
|
+
blockers.append("missing lineage in evidence")
|
|
2615
|
+
tests = payload.get("tests", [])
|
|
2616
|
+
if isinstance(tests, list):
|
|
2617
|
+
for item in tests:
|
|
2618
|
+
if isinstance(item, dict) and item.get("name") == "worker_implementation" and not item.get("passed", False):
|
|
2619
|
+
blockers.append("simulated worker evidence detected")
|
|
2620
|
+
break
|
|
2621
|
+
blockers.extend(_check_test_intent_claims(payload))
|
|
2622
|
+
blockers.extend(_check_high_risk_security_waivers(payload))
|
|
2623
|
+
return {
|
|
2624
|
+
"status": "ok" if not blockers else "error",
|
|
2625
|
+
"evidence_file": str(evidence_path.relative_to(output_root)),
|
|
2626
|
+
"run_id": str(payload.get("run_id", "")).strip(),
|
|
2627
|
+
"blockers": blockers,
|
|
2628
|
+
}
|
|
2629
|
+
|
|
2630
|
+
|
|
2631
|
+
def _latest_evidence_pack(output_root: Path) -> tuple[Path, dict[str, Any]] | None:
|
|
2632
|
+
evidence_dir = output_root / ".omg" / "evidence"
|
|
2633
|
+
if not evidence_dir.exists():
|
|
2634
|
+
return None
|
|
2635
|
+
|
|
2636
|
+
evidence_files = sorted(path for path in evidence_dir.glob("*.json") if path.is_file())
|
|
2637
|
+
evidence_payloads: list[tuple[Path, dict[str, Any]]] = []
|
|
2638
|
+
for path in evidence_files:
|
|
2639
|
+
try:
|
|
2640
|
+
payload = _load_json(path)
|
|
2641
|
+
except Exception:
|
|
2642
|
+
continue
|
|
2643
|
+
if payload.get("schema") != "EvidencePack":
|
|
2644
|
+
continue
|
|
2645
|
+
try:
|
|
2646
|
+
payload = _normalize_evidence_pack(payload)
|
|
2647
|
+
except ValueError as exc:
|
|
2648
|
+
return path, {"schema": "EvidencePack", "invalid": f"invalid evidence pack: {exc}"}
|
|
2649
|
+
evidence_payloads.append((path, payload))
|
|
2650
|
+
|
|
2651
|
+
if not evidence_payloads:
|
|
2652
|
+
return None
|
|
2653
|
+
return evidence_payloads[-1]
|
|
2654
|
+
|
|
2655
|
+
|
|
2656
|
+
def _required_fields_for_module(module: str) -> list[str]:
|
|
2657
|
+
metadata = schema_versions().get(module, {})
|
|
2658
|
+
required = metadata.get("required_fields", []) if isinstance(metadata, dict) else []
|
|
2659
|
+
if isinstance(required, list):
|
|
2660
|
+
return [str(field) for field in required if str(field).strip()]
|
|
2661
|
+
return []
|
|
2662
|
+
|
|
2663
|
+
|
|
2664
|
+
def _missing_context_metadata(payload: dict[str, Any]) -> list[str]:
|
|
2665
|
+
missing: list[str] = []
|
|
2666
|
+
for key in _REQUIRED_CONTEXT_METADATA:
|
|
2667
|
+
value = str(payload.get(key, "")).strip()
|
|
2668
|
+
if not value:
|
|
2669
|
+
missing.append(key)
|
|
2670
|
+
return missing
|
|
2671
|
+
|
|
2672
|
+
|
|
2673
|
+
def _env_truthy(name: str) -> bool:
|
|
2674
|
+
return os.environ.get(name, "").strip().lower() in {"1", "true", "yes", "on"}
|
|
2675
|
+
|
|
2676
|
+
|
|
2677
|
+
def _execution_primitive_max_age_seconds() -> float:
|
|
2678
|
+
raw = str(os.environ.get("OMG_EXECUTION_PRIMITIVE_MAX_AGE_SECONDS", "")).strip()
|
|
2679
|
+
if not raw:
|
|
2680
|
+
return _DEFAULT_EXECUTION_PRIMITIVE_MAX_AGE_SECONDS
|
|
2681
|
+
try:
|
|
2682
|
+
value = float(raw)
|
|
2683
|
+
except ValueError:
|
|
2684
|
+
return _DEFAULT_EXECUTION_PRIMITIVE_MAX_AGE_SECONDS
|
|
2685
|
+
return value if value >= 0 else _DEFAULT_EXECUTION_PRIMITIVE_MAX_AGE_SECONDS
|
|
2686
|
+
|
|
2687
|
+
|
|
2688
|
+
def _as_non_empty_list(value: Any) -> list[Any]:
|
|
2689
|
+
if not isinstance(value, list):
|
|
2690
|
+
return []
|
|
2691
|
+
items: list[Any] = []
|
|
2692
|
+
for item in value:
|
|
2693
|
+
if isinstance(item, str) and not item.strip():
|
|
2694
|
+
continue
|
|
2695
|
+
if item in ({}, []):
|
|
2696
|
+
continue
|
|
2697
|
+
items.append(item)
|
|
2698
|
+
return items
|
|
2699
|
+
|
|
2700
|
+
|
|
2701
|
+
def _normalize_exclusion_token(value: Any) -> str:
|
|
2702
|
+
if isinstance(value, str):
|
|
2703
|
+
return value.strip()
|
|
2704
|
+
if isinstance(value, dict):
|
|
2705
|
+
for field in ("id", "test", "name", "reason"):
|
|
2706
|
+
token = str(value.get(field, "")).strip()
|
|
2707
|
+
if token:
|
|
2708
|
+
return token
|
|
2709
|
+
return json.dumps(value, sort_keys=True, ensure_ascii=True)
|
|
2710
|
+
return str(value).strip()
|
|
2711
|
+
|
|
2712
|
+
|
|
2713
|
+
def _extract_signed_statement(payload: dict[str, Any]) -> dict[str, Any] | None:
|
|
2714
|
+
if "_type" in payload and "subject" in payload and "predicateType" in payload:
|
|
2715
|
+
return payload
|
|
2716
|
+
for key in ("attestation_statement", "statement", "attestation"):
|
|
2717
|
+
candidate = payload.get(key)
|
|
2718
|
+
if isinstance(candidate, dict):
|
|
2719
|
+
return candidate
|
|
2720
|
+
return None
|
|
2721
|
+
|
|
2722
|
+
|
|
2723
|
+
def _resolve_relative_path(*, output_root: Path, rel_path: str) -> Path | None:
|
|
2724
|
+
candidate = Path(rel_path)
|
|
2725
|
+
if not rel_path or candidate.is_absolute():
|
|
2726
|
+
return None
|
|
2727
|
+
resolved = (output_root / candidate).resolve()
|
|
2728
|
+
root = output_root.resolve()
|
|
2729
|
+
try:
|
|
2730
|
+
resolved.relative_to(root)
|
|
2731
|
+
except ValueError:
|
|
2732
|
+
return None
|
|
2733
|
+
return resolved
|
|
2734
|
+
|
|
2735
|
+
|
|
2736
|
+
def _load_json_or_none(path: Path) -> dict[str, Any] | None:
|
|
2737
|
+
try:
|
|
2738
|
+
payload = _load_json(path)
|
|
2739
|
+
except Exception:
|
|
2740
|
+
return None
|
|
2741
|
+
return payload if isinstance(payload, dict) else None
|
|
2742
|
+
|
|
2743
|
+
|
|
2744
|
+
def _jsonl_has_run_id(path: Path, run_id: str) -> bool:
|
|
2745
|
+
try:
|
|
2746
|
+
lines = path.read_text(encoding="utf-8").splitlines()
|
|
2747
|
+
except OSError:
|
|
2748
|
+
return False
|
|
2749
|
+
for line in lines:
|
|
2750
|
+
if not line.strip():
|
|
2751
|
+
continue
|
|
2752
|
+
try:
|
|
2753
|
+
payload = json.loads(line)
|
|
2754
|
+
except json.JSONDecodeError:
|
|
2755
|
+
continue
|
|
2756
|
+
if isinstance(payload, dict) and str(payload.get("run_id", "")).strip() == run_id:
|
|
2757
|
+
return True
|
|
2758
|
+
return False
|
|
2759
|
+
|
|
2760
|
+
|
|
2761
|
+
def _is_stale_execution_primitive(path: Path, *, evidence_mtime: float, max_age_seconds: float) -> bool:
|
|
2762
|
+
try:
|
|
2763
|
+
primitive_mtime = path.stat().st_mtime
|
|
2764
|
+
except OSError:
|
|
2765
|
+
return False
|
|
2766
|
+
return (evidence_mtime - primitive_mtime) > max_age_seconds
|
|
2767
|
+
|
|
2768
|
+
|
|
2769
|
+
def _check_excluded_failures_waiver(
|
|
2770
|
+
*,
|
|
2771
|
+
output_root: Path,
|
|
2772
|
+
evidence_payload: dict[str, Any],
|
|
2773
|
+
run_id: str,
|
|
2774
|
+
) -> dict[str, Any]:
|
|
2775
|
+
excluded_failures = _as_non_empty_list(evidence_payload.get("excluded_failures"))
|
|
2776
|
+
blockers: list[str] = []
|
|
2777
|
+
waiver_path = str(evidence_payload.get("excluded_failures_waiver_path", "")).strip()
|
|
2778
|
+
if not waiver_path:
|
|
2779
|
+
waiver = evidence_payload.get("excluded_failures_waiver")
|
|
2780
|
+
if isinstance(waiver, dict):
|
|
2781
|
+
waiver_path = str(waiver.get("path", "")).strip()
|
|
2782
|
+
|
|
2783
|
+
if not excluded_failures:
|
|
2784
|
+
return {
|
|
2785
|
+
"status": "ok",
|
|
2786
|
+
"excluded_failures": [],
|
|
2787
|
+
"waiver_path": waiver_path,
|
|
2788
|
+
"blockers": [],
|
|
2789
|
+
}
|
|
2790
|
+
|
|
2791
|
+
if not waiver_path:
|
|
2792
|
+
blockers.append("excluded_failures_without_signed_waiver: missing waiver artifact path")
|
|
2793
|
+
return {
|
|
2794
|
+
"status": "error",
|
|
2795
|
+
"excluded_failures": excluded_failures,
|
|
2796
|
+
"waiver_path": waiver_path,
|
|
2797
|
+
"blockers": blockers,
|
|
2798
|
+
}
|
|
2799
|
+
|
|
2800
|
+
resolved_path = _resolve_relative_path(output_root=output_root, rel_path=waiver_path)
|
|
2801
|
+
if resolved_path is None:
|
|
2802
|
+
blockers.append("excluded_failures_without_signed_waiver: waiver path outside output root")
|
|
2803
|
+
return {
|
|
2804
|
+
"status": "error",
|
|
2805
|
+
"excluded_failures": excluded_failures,
|
|
2806
|
+
"waiver_path": waiver_path,
|
|
2807
|
+
"blockers": blockers,
|
|
2808
|
+
}
|
|
2809
|
+
|
|
2810
|
+
waiver_payload = _load_json_or_none(resolved_path)
|
|
2811
|
+
if not isinstance(waiver_payload, dict):
|
|
2812
|
+
blockers.append("excluded_failures_without_signed_waiver: unreadable waiver artifact")
|
|
2813
|
+
return {
|
|
2814
|
+
"status": "error",
|
|
2815
|
+
"excluded_failures": excluded_failures,
|
|
2816
|
+
"waiver_path": waiver_path,
|
|
2817
|
+
"blockers": blockers,
|
|
2818
|
+
}
|
|
2819
|
+
|
|
2820
|
+
statement = _extract_signed_statement(waiver_payload)
|
|
2821
|
+
if not isinstance(statement, dict) or not verify_artifact_statement(statement):
|
|
2822
|
+
blockers.append("excluded_failures_without_signed_waiver: invalid waiver signature")
|
|
2823
|
+
return {
|
|
2824
|
+
"status": "error",
|
|
2825
|
+
"excluded_failures": excluded_failures,
|
|
2826
|
+
"waiver_path": waiver_path,
|
|
2827
|
+
"blockers": blockers,
|
|
2828
|
+
}
|
|
2829
|
+
|
|
2830
|
+
waiver_run_id = str(waiver_payload.get("run_id", "")).strip()
|
|
2831
|
+
if run_id and waiver_run_id and waiver_run_id != run_id:
|
|
2832
|
+
blockers.append("excluded_failures_without_signed_waiver: waiver run_id mismatch")
|
|
2833
|
+
|
|
2834
|
+
authorized_failures = _as_non_empty_list(waiver_payload.get("excluded_failures"))
|
|
2835
|
+
if not authorized_failures:
|
|
2836
|
+
blockers.append("excluded_failures_without_signed_waiver: waiver missing excluded_failures list")
|
|
2837
|
+
else:
|
|
2838
|
+
required_tokens = {_normalize_exclusion_token(item) for item in excluded_failures}
|
|
2839
|
+
authorized_tokens = {_normalize_exclusion_token(item) for item in authorized_failures}
|
|
2840
|
+
if not required_tokens.issubset(authorized_tokens):
|
|
2841
|
+
blockers.append("excluded_failures_without_signed_waiver: waiver does not authorize all exclusions")
|
|
2842
|
+
|
|
2843
|
+
return {
|
|
2844
|
+
"status": "ok" if not blockers else "error",
|
|
2845
|
+
"excluded_failures": excluded_failures,
|
|
2846
|
+
"waiver_path": waiver_path,
|
|
2847
|
+
"blockers": blockers,
|
|
2848
|
+
}
|
|
2849
|
+
|
|
2850
|
+
|
|
2851
|
+
def _check_execution_primitives(*, output_root: Path, evidence_profile: str | None = None) -> dict[str, Any]:
|
|
2852
|
+
blockers: list[str] = []
|
|
2853
|
+
missing: list[str] = []
|
|
2854
|
+
invalid: list[str] = []
|
|
2855
|
+
evidence_paths: dict[str, str] = {key: "" for key in _REQUIRED_EXECUTION_PRIMITIVES}
|
|
2856
|
+
require_exec_kernel_evidence = _env_truthy("OMG_REQUIRE_EXEC_KERNEL_EVIDENCE")
|
|
2857
|
+
require_governed_tool_evidence = _env_truthy("OMG_REQUIRE_GOVERNED_TOOL_EVIDENCE")
|
|
2858
|
+
excluded_failures_policy: dict[str, Any] = {
|
|
2859
|
+
"status": "ok",
|
|
2860
|
+
"excluded_failures": [],
|
|
2861
|
+
"waiver_path": "",
|
|
2862
|
+
"blockers": [],
|
|
2863
|
+
}
|
|
2864
|
+
resolved_profile = (evidence_profile or "").strip()
|
|
2865
|
+
required_evidence_requirements = requirements_for_profile(resolved_profile)
|
|
2866
|
+
active_run_id = get_active_coordinator_run_id(str(output_root)) or ""
|
|
2867
|
+
|
|
2868
|
+
latest = _latest_evidence_pack(output_root)
|
|
2869
|
+
if latest is None:
|
|
2870
|
+
missing.extend(list(_REQUIRED_EXECUTION_PRIMITIVES))
|
|
2871
|
+
blockers.extend(f"missing_execution_primitive: {item}" for item in missing)
|
|
2872
|
+
return {
|
|
2873
|
+
"status": "error",
|
|
2874
|
+
"run_id": "",
|
|
2875
|
+
"evidence_profile": resolved_profile,
|
|
2876
|
+
"required_evidence_requirements": list(required_evidence_requirements),
|
|
2877
|
+
"require_exec_kernel_evidence": require_exec_kernel_evidence,
|
|
2878
|
+
"require_governed_tool_evidence": require_governed_tool_evidence,
|
|
2879
|
+
"required": list(_REQUIRED_EXECUTION_PRIMITIVES),
|
|
2880
|
+
"missing": missing,
|
|
2881
|
+
"invalid": invalid,
|
|
2882
|
+
"evidence_paths": evidence_paths,
|
|
2883
|
+
"blockers": blockers,
|
|
2884
|
+
}
|
|
2885
|
+
|
|
2886
|
+
evidence_path, evidence_payload = latest
|
|
2887
|
+
invalid_evidence = str(evidence_payload.get("invalid", "")).strip()
|
|
2888
|
+
if invalid_evidence:
|
|
2889
|
+
invalid.append(f"release_evidence_pack:{invalid_evidence}")
|
|
2890
|
+
blockers.append(f"invalid_execution_primitive: release_evidence_pack: {invalid_evidence}")
|
|
2891
|
+
return {
|
|
2892
|
+
"status": "error",
|
|
2893
|
+
"run_id": "",
|
|
2894
|
+
"evidence_profile": resolved_profile,
|
|
2895
|
+
"required_evidence_requirements": list(required_evidence_requirements),
|
|
2896
|
+
"require_exec_kernel_evidence": require_exec_kernel_evidence,
|
|
2897
|
+
"require_governed_tool_evidence": require_governed_tool_evidence,
|
|
2898
|
+
"required": list(_REQUIRED_EXECUTION_PRIMITIVES),
|
|
2899
|
+
"missing": list(_REQUIRED_EXECUTION_PRIMITIVES),
|
|
2900
|
+
"invalid": invalid,
|
|
2901
|
+
"evidence_paths": evidence_paths,
|
|
2902
|
+
"blockers": blockers,
|
|
2903
|
+
}
|
|
2904
|
+
|
|
2905
|
+
if not resolved_profile:
|
|
2906
|
+
resolved_profile = str(evidence_payload.get("evidence_profile", "")).strip()
|
|
2907
|
+
required_evidence_requirements = requirements_for_profile(resolved_profile)
|
|
2908
|
+
|
|
2909
|
+
try:
|
|
2910
|
+
evidence_mtime = evidence_path.stat().st_mtime
|
|
2911
|
+
except OSError:
|
|
2912
|
+
evidence_mtime = datetime.now(timezone.utc).timestamp()
|
|
2913
|
+
max_age_seconds = _execution_primitive_max_age_seconds()
|
|
2914
|
+
|
|
2915
|
+
run_id = str(evidence_payload.get("run_id", "")).strip()
|
|
2916
|
+
if not run_id:
|
|
2917
|
+
invalid.append("run_id_unresolved")
|
|
2918
|
+
blockers.append("invalid_execution_primitive: run_id_unresolved")
|
|
2919
|
+
if active_run_id and run_id and run_id != active_run_id:
|
|
2920
|
+
invalid.append("run_id_cross_run")
|
|
2921
|
+
blockers.append("execution_primitive:cross_run")
|
|
2922
|
+
|
|
2923
|
+
evidence_metadata_missing = _missing_context_metadata(evidence_payload)
|
|
2924
|
+
if evidence_metadata_missing:
|
|
2925
|
+
invalid.append("release_evidence_pack:missing_context_metadata")
|
|
2926
|
+
blockers.append(
|
|
2927
|
+
"invalid_execution_primitive: release_evidence_pack: "
|
|
2928
|
+
f"missing_context_metadata={','.join(sorted(evidence_metadata_missing))}"
|
|
2929
|
+
)
|
|
2930
|
+
|
|
2931
|
+
excluded_failures_policy = _check_excluded_failures_waiver(
|
|
2932
|
+
output_root=output_root,
|
|
2933
|
+
evidence_payload=evidence_payload,
|
|
2934
|
+
run_id=run_id,
|
|
2935
|
+
)
|
|
2936
|
+
if excluded_failures_policy.get("status") != "ok":
|
|
2937
|
+
invalid.append("excluded_failures:missing_signed_waiver")
|
|
2938
|
+
blockers.extend(
|
|
2939
|
+
item
|
|
2940
|
+
for item in excluded_failures_policy.get("blockers", [])
|
|
2941
|
+
if isinstance(item, str)
|
|
2942
|
+
)
|
|
2943
|
+
|
|
2944
|
+
checks: list[tuple[str, str, str]] = [
|
|
2945
|
+
("release_run_coordinator_state", "release_run_coordinator", "ReleaseRunCoordinatorState"),
|
|
2946
|
+
("rollback_manifest", "rollback_manifest", "RollbackManifest"),
|
|
2947
|
+
("intent_gate_state", "intent_gate", "IntentGateDecision"),
|
|
2948
|
+
("session_health_state", "session_health", "SessionHealth"),
|
|
2949
|
+
("council_verdicts", "council_verdicts", "CouncilVerdicts"),
|
|
2950
|
+
]
|
|
2951
|
+
resolved_state_payloads: dict[str, dict[str, Any]] = {}
|
|
2952
|
+
for token, module, schema_name in checks:
|
|
2953
|
+
matched_path, matched_payload = _find_state_for_run(output_root=output_root, module=module, run_id=run_id)
|
|
2954
|
+
if matched_path is None or matched_payload is None:
|
|
2955
|
+
missing.append(token)
|
|
2956
|
+
blockers.append(f"missing_execution_primitive: {token}")
|
|
2957
|
+
continue
|
|
2958
|
+
evidence_paths[token] = str(matched_path.relative_to(output_root)).replace("\\", "/")
|
|
2959
|
+
resolved_state_payloads[token] = matched_payload
|
|
2960
|
+
payload_run_id = str(matched_payload.get("run_id", "")).strip()
|
|
2961
|
+
if run_id and payload_run_id and payload_run_id != run_id:
|
|
2962
|
+
invalid.append(f"{token}:cross_run")
|
|
2963
|
+
blockers.append(f"cross_run_execution_primitive: {token}")
|
|
2964
|
+
schema = str(matched_payload.get("schema", "")).strip()
|
|
2965
|
+
if schema != schema_name:
|
|
2966
|
+
invalid.append(f"{token}:schema_mismatch")
|
|
2967
|
+
blockers.append(f"invalid_execution_primitive: {token}: schema_mismatch")
|
|
2968
|
+
continue
|
|
2969
|
+
required_fields = _required_fields_for_module(module)
|
|
2970
|
+
missing_fields = [field for field in required_fields if field not in matched_payload]
|
|
2971
|
+
if missing_fields:
|
|
2972
|
+
invalid.append(f"{token}:missing_fields")
|
|
2973
|
+
blockers.append(
|
|
2974
|
+
f"invalid_execution_primitive: {token}: missing_fields={','.join(sorted(missing_fields))}"
|
|
2975
|
+
)
|
|
2976
|
+
if token in {"intent_gate_state", "session_health_state", "council_verdicts"}:
|
|
2977
|
+
metadata_missing = _missing_context_metadata(matched_payload)
|
|
2978
|
+
if metadata_missing:
|
|
2979
|
+
invalid.append(f"{token}:missing_context_metadata")
|
|
2980
|
+
blockers.append(
|
|
2981
|
+
f"invalid_execution_primitive: {token}: "
|
|
2982
|
+
f"missing_context_metadata={','.join(sorted(metadata_missing))}"
|
|
2983
|
+
)
|
|
2984
|
+
if _is_stale_execution_primitive(
|
|
2985
|
+
matched_path,
|
|
2986
|
+
evidence_mtime=evidence_mtime,
|
|
2987
|
+
max_age_seconds=max_age_seconds,
|
|
2988
|
+
):
|
|
2989
|
+
invalid.append(f"{token}:stale")
|
|
2990
|
+
blockers.append(f"stale_execution_primitive: {token}")
|
|
2991
|
+
|
|
2992
|
+
claims_payload = evidence_payload.get("claims")
|
|
2993
|
+
claims = claims_payload if isinstance(claims_payload, list) else []
|
|
2994
|
+
if not claims or not run_id:
|
|
2995
|
+
missing.append("claim_judge_outcome")
|
|
2996
|
+
blockers.append("missing_execution_primitive: claim_judge_outcome")
|
|
2997
|
+
else:
|
|
2998
|
+
release_evidence: dict[str, object] = {"run_id": run_id, "claims": claims}
|
|
2999
|
+
artifact = evidence_payload.get("artifact")
|
|
3000
|
+
if isinstance(artifact, dict):
|
|
3001
|
+
release_evidence["artifact"] = artifact
|
|
3002
|
+
for _passthrough_key in ("test_delta", "test_intent_lock", "proof_chain"):
|
|
3003
|
+
_passthrough_value = evidence_payload.get(_passthrough_key)
|
|
3004
|
+
if isinstance(_passthrough_value, dict):
|
|
3005
|
+
release_evidence[_passthrough_key] = _passthrough_value
|
|
3006
|
+
compliance = evaluate_release_compliance(
|
|
3007
|
+
project_dir=str(output_root),
|
|
3008
|
+
run_id=run_id,
|
|
3009
|
+
release_evidence=release_evidence,
|
|
3010
|
+
)
|
|
3011
|
+
claim_dir = output_root / ".omg" / "evidence"
|
|
3012
|
+
claim_candidates = sorted(claim_dir.glob("claim-judge-*.json")) if claim_dir.exists() else []
|
|
3013
|
+
if claim_candidates:
|
|
3014
|
+
claim_path = claim_candidates[-1]
|
|
3015
|
+
evidence_paths["claim_judge_outcome"] = str(claim_path.relative_to(output_root)).replace("\\", "/")
|
|
3016
|
+
else:
|
|
3017
|
+
missing.append("claim_judge_outcome")
|
|
3018
|
+
blockers.append("missing_execution_primitive: claim_judge_outcome")
|
|
3019
|
+
if str(compliance.get("status", "")).strip().lower() == "blocked":
|
|
3020
|
+
invalid.append("claim_judge_outcome:blocked")
|
|
3021
|
+
reason = str(compliance.get("reason", "claim_judge_blocked")).strip()
|
|
3022
|
+
blockers.append(f"invalid_execution_primitive: claim_judge_outcome: {reason}")
|
|
3023
|
+
|
|
3024
|
+
release_state = resolved_state_payloads.get("release_run_coordinator_state", {})
|
|
3025
|
+
if not release_state:
|
|
3026
|
+
missing.append("compliance_governor_outcome")
|
|
3027
|
+
blockers.append("missing_execution_primitive: compliance_governor_outcome")
|
|
3028
|
+
else:
|
|
3029
|
+
evidence_paths["compliance_governor_outcome"] = evidence_paths.get("release_run_coordinator_state", "")
|
|
3030
|
+
authority = str(release_state.get("compliance_authority", "")).strip()
|
|
3031
|
+
reason = str(release_state.get("compliance_reason", "")).strip()
|
|
3032
|
+
if not authority or not reason:
|
|
3033
|
+
invalid.append("compliance_governor_outcome:missing_fields")
|
|
3034
|
+
blockers.append("invalid_execution_primitive: compliance_governor_outcome: missing_fields")
|
|
3035
|
+
artifact_verdict = str(release_state.get("artifact_verdict", "")).strip()
|
|
3036
|
+
if artifact_verdict:
|
|
3037
|
+
artifact_alg = str(release_state.get("artifact_alg", "")).strip()
|
|
3038
|
+
artifact_key_id = str(release_state.get("artifact_key_id", "")).strip()
|
|
3039
|
+
if not artifact_alg or not artifact_key_id:
|
|
3040
|
+
invalid.append("compliance_governor_outcome:missing_artifact_audit")
|
|
3041
|
+
blockers.append(
|
|
3042
|
+
"invalid_execution_primitive: compliance_governor_outcome: missing_artifact_audit_fields"
|
|
3043
|
+
)
|
|
3044
|
+
|
|
3045
|
+
profile_path = output_root / ".omg" / "state" / "profile.yaml"
|
|
3046
|
+
if not profile_path.exists():
|
|
3047
|
+
missing.append("profile_digest")
|
|
3048
|
+
blockers.append("missing_execution_primitive: profile_digest")
|
|
3049
|
+
else:
|
|
3050
|
+
evidence_paths["profile_digest"] = str(profile_path.relative_to(output_root)).replace("\\", "/")
|
|
3051
|
+
try:
|
|
3052
|
+
profile_payload = yaml.safe_load(profile_path.read_text(encoding="utf-8"))
|
|
3053
|
+
except Exception:
|
|
3054
|
+
profile_payload = None
|
|
3055
|
+
profile_version = ""
|
|
3056
|
+
if isinstance(profile_payload, dict):
|
|
3057
|
+
profile_version = str(
|
|
3058
|
+
profile_payload.get("profile_version")
|
|
3059
|
+
or profile_payload.get("version")
|
|
3060
|
+
or ""
|
|
3061
|
+
).strip()
|
|
3062
|
+
if not profile_version:
|
|
3063
|
+
provenance = profile_payload.get("profile_provenance")
|
|
3064
|
+
if isinstance(provenance, dict):
|
|
3065
|
+
profile_version = str(
|
|
3066
|
+
provenance.get("checksum")
|
|
3067
|
+
or provenance.get("version")
|
|
3068
|
+
or ""
|
|
3069
|
+
).strip()
|
|
3070
|
+
if not profile_version:
|
|
3071
|
+
invalid.append("profile_digest:missing_profile_version")
|
|
3072
|
+
blockers.append("invalid_execution_primitive: profile_digest: missing_profile_version")
|
|
3073
|
+
|
|
3074
|
+
lock_path, lock_payload = _find_test_intent_lock(output_root=output_root, run_id=run_id, evidence_payload=evidence_payload)
|
|
3075
|
+
if lock_path is None or lock_payload is None:
|
|
3076
|
+
missing.append("tdd_proof_chain_lock")
|
|
3077
|
+
blockers.append("missing_execution_primitive: tdd_proof_chain_lock")
|
|
3078
|
+
else:
|
|
3079
|
+
evidence_paths["tdd_proof_chain_lock"] = str(lock_path.relative_to(output_root)).replace("\\", "/")
|
|
3080
|
+
lock_run_id = str(lock_payload.get("run_id", "")).strip()
|
|
3081
|
+
if run_id and lock_run_id and lock_run_id != run_id:
|
|
3082
|
+
invalid.append("tdd_proof_chain_lock:cross_run")
|
|
3083
|
+
blockers.append("cross_run_execution_primitive: tdd_proof_chain_lock")
|
|
3084
|
+
lock_status = str(lock_payload.get("status", "")).strip().lower()
|
|
3085
|
+
if lock_status in {"", "error", "blocked"}:
|
|
3086
|
+
invalid.append("tdd_proof_chain_lock:status_invalid")
|
|
3087
|
+
blockers.append("invalid_execution_primitive: tdd_proof_chain_lock: status_invalid")
|
|
3088
|
+
if _is_stale_execution_primitive(
|
|
3089
|
+
lock_path,
|
|
3090
|
+
evidence_mtime=evidence_mtime,
|
|
3091
|
+
max_age_seconds=max_age_seconds,
|
|
3092
|
+
):
|
|
3093
|
+
invalid.append("tdd_proof_chain_lock:stale")
|
|
3094
|
+
blockers.append("stale_execution_primitive: tdd_proof_chain_lock")
|
|
3095
|
+
|
|
3096
|
+
forge_path, forge_payload = _find_forge_starter_proof(output_root=output_root, run_id=run_id)
|
|
3097
|
+
if forge_path is None or forge_payload is None:
|
|
3098
|
+
missing.append("forge_starter_proof")
|
|
3099
|
+
blockers.append("missing_execution_primitive: forge_starter_proof")
|
|
3100
|
+
else:
|
|
3101
|
+
evidence_paths["forge_starter_proof"] = str(forge_path.relative_to(output_root)).replace("\\", "/")
|
|
3102
|
+
forge_run_id = str(forge_payload.get("run_id", "")).strip()
|
|
3103
|
+
if run_id and forge_run_id and forge_run_id != run_id:
|
|
3104
|
+
invalid.append("forge_starter_proof:cross_run")
|
|
3105
|
+
blockers.append("cross_run_execution_primitive: forge_starter_proof")
|
|
3106
|
+
forge_schema = str(forge_payload.get("schema", "")).strip()
|
|
3107
|
+
if forge_schema != "ForgeSpecialistDispatchEvidence":
|
|
3108
|
+
invalid.append("forge_starter_proof:schema_mismatch")
|
|
3109
|
+
blockers.append("invalid_execution_primitive: forge_starter_proof: schema_mismatch")
|
|
3110
|
+
if forge_payload.get("proof_backed") is not True:
|
|
3111
|
+
invalid.append("forge_starter_proof:not_proof_backed")
|
|
3112
|
+
blockers.append("invalid_execution_primitive: forge_starter_proof: proof_backed_false")
|
|
3113
|
+
forge_metadata_missing = _missing_context_metadata(forge_payload)
|
|
3114
|
+
if forge_metadata_missing:
|
|
3115
|
+
invalid.append("forge_starter_proof:missing_context_metadata")
|
|
3116
|
+
blockers.append(
|
|
3117
|
+
"invalid_execution_primitive: forge_starter_proof: "
|
|
3118
|
+
f"missing_context_metadata={','.join(sorted(forge_metadata_missing))}"
|
|
3119
|
+
)
|
|
3120
|
+
if _is_stale_execution_primitive(
|
|
3121
|
+
forge_path,
|
|
3122
|
+
evidence_mtime=evidence_mtime,
|
|
3123
|
+
max_age_seconds=max_age_seconds,
|
|
3124
|
+
):
|
|
3125
|
+
invalid.append("forge_starter_proof:stale")
|
|
3126
|
+
blockers.append("stale_execution_primitive: forge_starter_proof")
|
|
3127
|
+
|
|
3128
|
+
# ── Resolve evidence-pack-embedded primitives ──────────────────────────
|
|
3129
|
+
# The evidence pack stores new execution primitives as nested dicts with
|
|
3130
|
+
# a "path" key. Resolve each one against output_root.
|
|
3131
|
+
_pack_embedded_primitives = (
|
|
3132
|
+
"exec_kernel_state",
|
|
3133
|
+
"worker_watchdog_replay",
|
|
3134
|
+
"merge_writer_provenance",
|
|
3135
|
+
"write_lease_provenance",
|
|
3136
|
+
"tool_fabric_ledger",
|
|
3137
|
+
"budget_envelope_state",
|
|
3138
|
+
"issue_report",
|
|
3139
|
+
"host_parity_report",
|
|
3140
|
+
"music_omr_testbed_evidence",
|
|
3141
|
+
)
|
|
3142
|
+
for token in _pack_embedded_primitives:
|
|
3143
|
+
entry = evidence_payload.get(token)
|
|
3144
|
+
if not isinstance(entry, dict):
|
|
3145
|
+
missing.append(token)
|
|
3146
|
+
blockers.append(f"missing_execution_primitive: {token}")
|
|
3147
|
+
continue
|
|
3148
|
+
entry_run_id = str(entry.get("run_id", "")).strip()
|
|
3149
|
+
if run_id and entry_run_id and entry_run_id != run_id:
|
|
3150
|
+
invalid.append(f"{token}:cross_run")
|
|
3151
|
+
blockers.append(f"cross_run_execution_primitive: {token}")
|
|
3152
|
+
rel_path = str(entry.get("path", "")).strip()
|
|
3153
|
+
if not rel_path:
|
|
3154
|
+
missing.append(token)
|
|
3155
|
+
blockers.append(f"missing_execution_primitive: {token}")
|
|
3156
|
+
continue
|
|
3157
|
+
resolved = _resolve_relative_path(output_root=output_root, rel_path=rel_path)
|
|
3158
|
+
if resolved is None:
|
|
3159
|
+
invalid.append(f"{token}:invalid_path")
|
|
3160
|
+
blockers.append(f"invalid_execution_primitive: {token}: invalid_path")
|
|
3161
|
+
continue
|
|
3162
|
+
if not resolved.exists() and token == "music_omr_testbed_evidence":
|
|
3163
|
+
tracked_candidates = sorted((output_root / "artifacts" / "release" / "evidence").glob("music-omr-*.json"))
|
|
3164
|
+
if tracked_candidates:
|
|
3165
|
+
resolved = tracked_candidates[-1]
|
|
3166
|
+
if not resolved.exists():
|
|
3167
|
+
missing.append(token)
|
|
3168
|
+
blockers.append(f"missing_execution_primitive: {token}")
|
|
3169
|
+
continue
|
|
3170
|
+
normalized_rel_path = str(resolved.relative_to(output_root)).replace("\\", "/")
|
|
3171
|
+
evidence_paths[token] = normalized_rel_path
|
|
3172
|
+
if token == "tool_fabric_ledger":
|
|
3173
|
+
if run_id and not _jsonl_has_run_id(resolved, run_id):
|
|
3174
|
+
invalid.append(f"{token}:run_id_missing")
|
|
3175
|
+
blockers.append(f"invalid_execution_primitive: {token}: run_id_missing")
|
|
3176
|
+
else:
|
|
3177
|
+
payload = _load_json_or_none(resolved)
|
|
3178
|
+
if isinstance(payload, dict):
|
|
3179
|
+
payload_run_id = str(payload.get("run_id", "")).strip()
|
|
3180
|
+
if run_id and payload_run_id and payload_run_id != run_id:
|
|
3181
|
+
invalid.append(f"{token}:cross_run")
|
|
3182
|
+
blockers.append(f"cross_run_execution_primitive: {token}")
|
|
3183
|
+
if token == "exec_kernel_state" and require_exec_kernel_evidence:
|
|
3184
|
+
if str(payload.get("schema", "")).strip() != "ExecKernelRunState":
|
|
3185
|
+
invalid.append("exec_kernel_state:schema_mismatch")
|
|
3186
|
+
blockers.append("invalid_execution_primitive: exec_kernel_state: schema_mismatch")
|
|
3187
|
+
if payload.get("kernel_enabled") is not True:
|
|
3188
|
+
invalid.append("exec_kernel_state:kernel_disabled")
|
|
3189
|
+
blockers.append("invalid_execution_primitive: exec_kernel_state: kernel_disabled")
|
|
3190
|
+
elif token == "exec_kernel_state" and require_exec_kernel_evidence:
|
|
3191
|
+
invalid.append("exec_kernel_state:unreadable")
|
|
3192
|
+
blockers.append("invalid_execution_primitive: exec_kernel_state: unreadable")
|
|
3193
|
+
if token == "tool_fabric_ledger" and require_governed_tool_evidence:
|
|
3194
|
+
try:
|
|
3195
|
+
ledger_size = resolved.stat().st_size
|
|
3196
|
+
except OSError:
|
|
3197
|
+
ledger_size = 0
|
|
3198
|
+
if ledger_size <= 0:
|
|
3199
|
+
invalid.append("tool_fabric_ledger:empty")
|
|
3200
|
+
blockers.append("invalid_execution_primitive: tool_fabric_ledger: empty")
|
|
3201
|
+
if _is_stale_execution_primitive(
|
|
3202
|
+
resolved,
|
|
3203
|
+
evidence_mtime=evidence_mtime,
|
|
3204
|
+
max_age_seconds=max_age_seconds,
|
|
3205
|
+
):
|
|
3206
|
+
invalid.append(f"{token}:stale")
|
|
3207
|
+
blockers.append(f"stale_execution_primitive: {token}")
|
|
3208
|
+
|
|
3209
|
+
if "music_omr_testbed_evidence" in evidence_paths and evidence_paths["music_omr_testbed_evidence"]:
|
|
3210
|
+
music_omr_path = _resolve_relative_path(
|
|
3211
|
+
output_root=output_root,
|
|
3212
|
+
rel_path=evidence_paths["music_omr_testbed_evidence"],
|
|
3213
|
+
)
|
|
3214
|
+
if music_omr_path is not None:
|
|
3215
|
+
music_omr_payload = _load_json_or_none(music_omr_path)
|
|
3216
|
+
if isinstance(music_omr_payload, dict):
|
|
3217
|
+
run_id_linkage = str(
|
|
3218
|
+
music_omr_payload.get("trace_metadata", {}).get("run_id_linkage", "")
|
|
3219
|
+
).strip()
|
|
3220
|
+
if run_id_linkage != run_id:
|
|
3221
|
+
invalid.append("music_omr_testbed_evidence:run_id_linkage_mismatch")
|
|
3222
|
+
blockers.append(
|
|
3223
|
+
"invalid_execution_primitive: music_omr_testbed_evidence: run_id_linkage_mismatch"
|
|
3224
|
+
)
|
|
3225
|
+
|
|
3226
|
+
freshness_payload = music_omr_payload.get("freshness")
|
|
3227
|
+
freshness_generated_at = ""
|
|
3228
|
+
if isinstance(freshness_payload, dict):
|
|
3229
|
+
freshness_generated_at = str(freshness_payload.get("generated_at", "")).strip()
|
|
3230
|
+
generated_at = None
|
|
3231
|
+
if freshness_generated_at:
|
|
3232
|
+
try:
|
|
3233
|
+
generated_at = datetime.fromisoformat(freshness_generated_at.replace("Z", "+00:00"))
|
|
3234
|
+
except ValueError:
|
|
3235
|
+
generated_at = None
|
|
3236
|
+
if generated_at is None:
|
|
3237
|
+
invalid.append("music_omr_testbed_evidence:payload_freshness_stale")
|
|
3238
|
+
blockers.append(
|
|
3239
|
+
"invalid_execution_primitive: music_omr_testbed_evidence: payload_freshness_stale"
|
|
3240
|
+
)
|
|
3241
|
+
else:
|
|
3242
|
+
if generated_at.tzinfo is None:
|
|
3243
|
+
generated_at = generated_at.replace(tzinfo=timezone.utc)
|
|
3244
|
+
max_age = timedelta(seconds=max(1, max_age_seconds))
|
|
3245
|
+
if datetime.now(timezone.utc) - generated_at > max_age:
|
|
3246
|
+
invalid.append("music_omr_testbed_evidence:payload_freshness_stale")
|
|
3247
|
+
blockers.append(
|
|
3248
|
+
"invalid_execution_primitive: music_omr_testbed_evidence: payload_freshness_stale"
|
|
3249
|
+
)
|
|
3250
|
+
if (
|
|
3251
|
+
is_release_orchestration_active(project_dir=str(output_root))
|
|
3252
|
+
and "coordinator_run_id" in music_omr_payload
|
|
3253
|
+
):
|
|
3254
|
+
coordinator_run_id = str(music_omr_payload.get("coordinator_run_id", "")).strip()
|
|
3255
|
+
active_coordinator_run_id = get_active_coordinator_run_id(str(output_root)) or ""
|
|
3256
|
+
if coordinator_run_id != active_coordinator_run_id:
|
|
3257
|
+
invalid.append("music_omr_testbed_evidence:coordinator_run_id_mismatch")
|
|
3258
|
+
blockers.append(
|
|
3259
|
+
"invalid_execution_primitive: music_omr_testbed_evidence: coordinator_run_id_mismatch"
|
|
3260
|
+
)
|
|
3261
|
+
|
|
3262
|
+
return {
|
|
3263
|
+
"status": "ok" if not blockers else "error",
|
|
3264
|
+
"run_id": run_id,
|
|
3265
|
+
"evidence_profile": resolved_profile,
|
|
3266
|
+
"required_evidence_requirements": list(required_evidence_requirements),
|
|
3267
|
+
"require_exec_kernel_evidence": require_exec_kernel_evidence,
|
|
3268
|
+
"require_governed_tool_evidence": require_governed_tool_evidence,
|
|
3269
|
+
"evidence_pack": str(evidence_path.relative_to(output_root)).replace("\\", "/"),
|
|
3270
|
+
"required": list(_REQUIRED_EXECUTION_PRIMITIVES),
|
|
3271
|
+
"missing": sorted(set(missing)),
|
|
3272
|
+
"invalid": sorted(set(invalid)),
|
|
3273
|
+
"evidence_paths": evidence_paths,
|
|
3274
|
+
"blockers": blockers,
|
|
3275
|
+
}
|
|
3276
|
+
|
|
3277
|
+
|
|
3278
|
+
def _find_state_for_run(
|
|
3279
|
+
*,
|
|
3280
|
+
output_root: Path,
|
|
3281
|
+
module: str,
|
|
3282
|
+
run_id: str,
|
|
3283
|
+
) -> tuple[Path | None, dict[str, Any] | None]:
|
|
3284
|
+
state_dir = output_root / ".omg" / "state" / module
|
|
3285
|
+
if not state_dir.exists():
|
|
3286
|
+
return None, None
|
|
3287
|
+
|
|
3288
|
+
preferred = state_dir / f"{run_id}.json"
|
|
3289
|
+
if run_id and preferred.exists():
|
|
3290
|
+
try:
|
|
3291
|
+
payload = _load_json(preferred)
|
|
3292
|
+
except Exception:
|
|
3293
|
+
payload = {}
|
|
3294
|
+
if isinstance(payload, dict):
|
|
3295
|
+
return preferred, payload
|
|
3296
|
+
|
|
3297
|
+
for path in sorted(state_dir.glob("*.json")):
|
|
3298
|
+
try:
|
|
3299
|
+
payload = _load_json(path)
|
|
3300
|
+
except Exception:
|
|
3301
|
+
continue
|
|
3302
|
+
if not isinstance(payload, dict):
|
|
3303
|
+
continue
|
|
3304
|
+
if run_id and str(payload.get("run_id", "")).strip() != run_id:
|
|
3305
|
+
continue
|
|
3306
|
+
return path, payload
|
|
3307
|
+
return None, None
|
|
3308
|
+
|
|
3309
|
+
|
|
3310
|
+
def _find_test_intent_lock(
|
|
3311
|
+
*,
|
|
3312
|
+
output_root: Path,
|
|
3313
|
+
run_id: str,
|
|
3314
|
+
evidence_payload: dict[str, Any],
|
|
3315
|
+
) -> tuple[Path | None, dict[str, Any] | None]:
|
|
3316
|
+
lock_dir = output_root / ".omg" / "state" / "test-intent-lock"
|
|
3317
|
+
if not lock_dir.exists():
|
|
3318
|
+
return None, None
|
|
3319
|
+
|
|
3320
|
+
lock_id = ""
|
|
3321
|
+
test_delta = evidence_payload.get("test_delta")
|
|
3322
|
+
if isinstance(test_delta, dict):
|
|
3323
|
+
lock_id = str(test_delta.get("lock_id", "")).strip()
|
|
3324
|
+
|
|
3325
|
+
for path in sorted(lock_dir.glob("*.json")):
|
|
3326
|
+
try:
|
|
3327
|
+
payload = _load_json(path)
|
|
3328
|
+
except Exception:
|
|
3329
|
+
continue
|
|
3330
|
+
if not isinstance(payload, dict):
|
|
3331
|
+
continue
|
|
3332
|
+
payload_lock_id = str(payload.get("lock_id", "")).strip()
|
|
3333
|
+
if lock_id and payload_lock_id == lock_id:
|
|
3334
|
+
return path, payload
|
|
3335
|
+
intent = payload.get("intent")
|
|
3336
|
+
if isinstance(intent, dict):
|
|
3337
|
+
intent_run = str(intent.get("run_id", "")).strip()
|
|
3338
|
+
if run_id and intent_run == run_id:
|
|
3339
|
+
return path, payload
|
|
3340
|
+
payload_run = str(payload.get("run_id", "")).strip()
|
|
3341
|
+
if run_id and payload_run == run_id:
|
|
3342
|
+
return path, payload
|
|
3343
|
+
return None, None
|
|
3344
|
+
|
|
3345
|
+
|
|
3346
|
+
def _find_forge_starter_proof(*, output_root: Path, run_id: str) -> tuple[Path | None, dict[str, Any] | None]:
|
|
3347
|
+
evidence_dir = output_root / ".omg" / "evidence"
|
|
3348
|
+
if not evidence_dir.exists():
|
|
3349
|
+
return None, None
|
|
3350
|
+
for path in sorted(evidence_dir.glob("forge-specialists-*.json")):
|
|
3351
|
+
try:
|
|
3352
|
+
payload = _load_json(path)
|
|
3353
|
+
except Exception:
|
|
3354
|
+
continue
|
|
3355
|
+
if not isinstance(payload, dict):
|
|
3356
|
+
continue
|
|
3357
|
+
payload_run = str(payload.get("run_id", "")).strip()
|
|
3358
|
+
if run_id and payload_run and payload_run != run_id:
|
|
3359
|
+
continue
|
|
3360
|
+
return path, payload
|
|
3361
|
+
return None, None
|
|
3362
|
+
|
|
3363
|
+
|
|
3364
|
+
def _sanitize_run_id(value: str) -> str:
|
|
3365
|
+
cleaned = "".join(ch if ch.isalnum() or ch in {"-", "_", "."} else "-" for ch in value.strip())
|
|
3366
|
+
return cleaned or "unknown"
|
|
3367
|
+
|
|
3368
|
+
|
|
3369
|
+
def _check_claim_judge_compliance(output_root: Path) -> dict[str, Any]:
|
|
3370
|
+
latest = _latest_evidence_pack(output_root)
|
|
3371
|
+
if latest is None:
|
|
3372
|
+
return {"status": "missing", "blockers": []}
|
|
3373
|
+
|
|
3374
|
+
_, evidence_payload = latest
|
|
3375
|
+
run_id = str(evidence_payload.get("run_id", "")).strip()
|
|
3376
|
+
claims_payload = evidence_payload.get("claims")
|
|
3377
|
+
claims = claims_payload if isinstance(claims_payload, list) else []
|
|
3378
|
+
if not run_id or not claims:
|
|
3379
|
+
return {
|
|
3380
|
+
"status": "missing",
|
|
3381
|
+
"run_id": run_id,
|
|
3382
|
+
"blockers": ["claim_judge_compliance_gate: missing release claims for compliance evaluation"],
|
|
3383
|
+
}
|
|
3384
|
+
|
|
3385
|
+
release_evidence: dict[str, object] = {"run_id": run_id, "claims": claims}
|
|
3386
|
+
artifact = evidence_payload.get("artifact")
|
|
3387
|
+
if isinstance(artifact, dict):
|
|
3388
|
+
release_evidence["artifact"] = artifact
|
|
3389
|
+
for _pt_key in ("test_delta", "test_intent_lock", "proof_chain"):
|
|
3390
|
+
_pt_val = evidence_payload.get(_pt_key)
|
|
3391
|
+
if isinstance(_pt_val, dict):
|
|
3392
|
+
release_evidence[_pt_key] = _pt_val
|
|
3393
|
+
decision = evaluate_release_compliance(
|
|
3394
|
+
project_dir=str(output_root),
|
|
3395
|
+
run_id=run_id,
|
|
3396
|
+
release_evidence=release_evidence,
|
|
3397
|
+
)
|
|
3398
|
+
decision_status = str(decision.get("status", "")).strip().lower()
|
|
3399
|
+
blockers: list[str] = []
|
|
3400
|
+
if decision_status == "blocked":
|
|
3401
|
+
reason = str(decision.get("reason", "compliance_gate_blocked")).strip() or "compliance_gate_blocked"
|
|
3402
|
+
blockers.append(f"claim_judge_compliance_gate: {reason}")
|
|
3403
|
+
return {
|
|
3404
|
+
"status": "ok" if not blockers else "error",
|
|
3405
|
+
"run_id": run_id,
|
|
3406
|
+
"decision": decision,
|
|
3407
|
+
"blockers": blockers,
|
|
3408
|
+
}
|
|
3409
|
+
|
|
3410
|
+
|
|
3411
|
+
def _check_test_intent_claims(payload: dict[str, Any]) -> list[str]:
|
|
3412
|
+
test_delta = payload.get("test_delta")
|
|
3413
|
+
claims = payload.get("claims", [])
|
|
3414
|
+
if not isinstance(claims, list):
|
|
3415
|
+
return []
|
|
3416
|
+
|
|
3417
|
+
from runtime.test_intent_lock import evaluate_test_delta
|
|
3418
|
+
|
|
3419
|
+
blockers: list[str] = []
|
|
3420
|
+
guarded_claims = {"tests passed", "tests_passed", "bug fixed", "bug_fixed"}
|
|
3421
|
+
for claim in claims:
|
|
3422
|
+
if not isinstance(claim, dict):
|
|
3423
|
+
continue
|
|
3424
|
+
claim_type = str(claim.get("claim_type", "")).strip().lower()
|
|
3425
|
+
if claim_type not in guarded_claims:
|
|
3426
|
+
continue
|
|
3427
|
+
delta = claim.get("test_delta")
|
|
3428
|
+
if not isinstance(delta, dict):
|
|
3429
|
+
delta = test_delta if isinstance(test_delta, dict) else None
|
|
3430
|
+
if not isinstance(delta, dict):
|
|
3431
|
+
blockers.append(f"test_intent_lock_missing_delta: claim '{claim_type}' requires test_delta evidence")
|
|
3432
|
+
continue
|
|
3433
|
+
result = evaluate_test_delta(delta)
|
|
3434
|
+
if result.get("verdict") != "pass":
|
|
3435
|
+
reasons = result.get("reasons", [])
|
|
3436
|
+
reason_text = "; ".join(str(item) for item in reasons if str(item).strip())
|
|
3437
|
+
suffix = f": {reason_text}" if reason_text else ""
|
|
3438
|
+
blockers.append(f"test_intent_lock_blocked: claim '{claim_type}'{suffix}")
|
|
3439
|
+
return blockers
|
|
3440
|
+
|
|
3441
|
+
|
|
3442
|
+
def _check_eval_gate(output_root: Path) -> dict[str, Any]:
|
|
3443
|
+
latest_path = output_root / ".omg" / "evals" / "latest.json"
|
|
3444
|
+
if not latest_path.exists():
|
|
3445
|
+
return {"status": "missing", "blockers": []}
|
|
3446
|
+
payload = _load_json(latest_path)
|
|
3447
|
+
blockers: list[str] = []
|
|
3448
|
+
if payload.get("status") != "ok" or bool(payload.get("summary", {}).get("regressed")):
|
|
3449
|
+
blockers.append("eval regression detected")
|
|
3450
|
+
return {
|
|
3451
|
+
"status": "ok" if not blockers else "error",
|
|
3452
|
+
"path": str(latest_path.relative_to(output_root)),
|
|
3453
|
+
"blockers": blockers,
|
|
3454
|
+
}
|
|
3455
|
+
|
|
3456
|
+
|
|
3457
|
+
def _check_proof_chain(output_root: Path) -> dict[str, Any]:
|
|
3458
|
+
chain_module = importlib.import_module("runtime.proof_chain")
|
|
3459
|
+
gate_module = importlib.import_module("runtime.proof_gate")
|
|
3460
|
+
|
|
3461
|
+
gate_input = chain_module.build_proof_gate_input(str(output_root))
|
|
3462
|
+
chain = gate_input.get("proof_chain", {}) if isinstance(gate_input, dict) else {}
|
|
3463
|
+
chain_status = str(chain.get("status", "error"))
|
|
3464
|
+
raw_blockers = chain.get("blockers", [])
|
|
3465
|
+
blockers = [f"proof_chain_linkage: {item}" for item in raw_blockers] if isinstance(raw_blockers, list) else ["proof_chain_linkage: invalid blockers"]
|
|
3466
|
+
if chain_status == "ok":
|
|
3467
|
+
blockers = []
|
|
3468
|
+
|
|
3469
|
+
proof_gate = gate_module.evaluate_proof_gate(gate_input if isinstance(gate_input, dict) else {})
|
|
3470
|
+
if str(proof_gate.get("verdict", "fail")) != "pass":
|
|
3471
|
+
gate_blockers = proof_gate.get("blockers", [])
|
|
3472
|
+
if isinstance(gate_blockers, list) and gate_blockers:
|
|
3473
|
+
blockers.extend(f"proof_gate_blocked: {item}" for item in gate_blockers)
|
|
3474
|
+
else:
|
|
3475
|
+
blockers.append("proof_gate_blocked: verdict_fail")
|
|
3476
|
+
|
|
3477
|
+
return {
|
|
3478
|
+
"status": "ok" if not blockers else "error",
|
|
3479
|
+
"proof_chain": chain,
|
|
3480
|
+
"proof_gate": proof_gate,
|
|
3481
|
+
"blockers": blockers,
|
|
3482
|
+
}
|
|
3483
|
+
|
|
3484
|
+
|
|
3485
|
+
def _check_bundle_promotion_parity(root: Path, output_root: Path) -> dict[str, Any]:
|
|
3486
|
+
missing_settings_required_bundles: list[str] = []
|
|
3487
|
+
missing_dist_public: list[str] = []
|
|
3488
|
+
missing_dist_enterprise: list[str] = []
|
|
3489
|
+
missing_pyproject_data_files: list[str] = []
|
|
3490
|
+
|
|
3491
|
+
settings_path = output_root / "settings.json"
|
|
3492
|
+
if settings_path.exists():
|
|
3493
|
+
settings = _load_json(settings_path)
|
|
3494
|
+
required_bundles = settings.get("_omg", {}).get("generated", {}).get("required_bundles", [])
|
|
3495
|
+
if not isinstance(required_bundles, list):
|
|
3496
|
+
required_bundles = []
|
|
3497
|
+
required_bundle_set = {str(item) for item in required_bundles}
|
|
3498
|
+
missing_settings_required_bundles = [
|
|
3499
|
+
bundle_id for bundle_id in TRUTH_COUNCIL_BUNDLES if bundle_id not in required_bundle_set
|
|
3500
|
+
]
|
|
3501
|
+
else:
|
|
3502
|
+
missing_settings_required_bundles = list(TRUTH_COUNCIL_BUNDLES)
|
|
3503
|
+
|
|
3504
|
+
for bundle_id in TRUTH_COUNCIL_BUNDLES:
|
|
3505
|
+
public_skill = output_root / "dist" / "public" / "bundle" / ".agents" / "skills" / "omg" / bundle_id / "SKILL.md"
|
|
3506
|
+
if not public_skill.exists():
|
|
3507
|
+
missing_dist_public.append(str(public_skill.relative_to(output_root)))
|
|
3508
|
+
|
|
3509
|
+
enterprise_skill = output_root / "dist" / "enterprise" / "bundle" / ".agents" / "skills" / "omg" / bundle_id / "SKILL.md"
|
|
3510
|
+
if not enterprise_skill.exists():
|
|
3511
|
+
missing_dist_enterprise.append(str(enterprise_skill.relative_to(output_root)))
|
|
3512
|
+
|
|
3513
|
+
pyproject_path = root / "pyproject.toml"
|
|
3514
|
+
if pyproject_path.exists():
|
|
3515
|
+
pyproject_content = pyproject_path.read_text(encoding="utf-8")
|
|
3516
|
+
for bundle_id in TRUTH_COUNCIL_BUNDLES:
|
|
3517
|
+
data_file_key = f'".agents/skills/omg/{bundle_id}" = '
|
|
3518
|
+
if data_file_key not in pyproject_content:
|
|
3519
|
+
missing_pyproject_data_files.append(bundle_id)
|
|
3520
|
+
else:
|
|
3521
|
+
missing_pyproject_data_files = list(TRUTH_COUNCIL_BUNDLES)
|
|
3522
|
+
|
|
3523
|
+
failed = any(
|
|
3524
|
+
(
|
|
3525
|
+
missing_settings_required_bundles,
|
|
3526
|
+
missing_dist_public,
|
|
3527
|
+
missing_dist_enterprise,
|
|
3528
|
+
missing_pyproject_data_files,
|
|
3529
|
+
)
|
|
3530
|
+
)
|
|
3531
|
+
return {
|
|
3532
|
+
"status": "ok" if not failed else "error",
|
|
3533
|
+
"blockers": ["bundle_promotion_parity"] if failed else [],
|
|
3534
|
+
"missing_settings_required_bundles": missing_settings_required_bundles,
|
|
3535
|
+
"missing_dist_public": missing_dist_public,
|
|
3536
|
+
"missing_dist_enterprise": missing_dist_enterprise,
|
|
3537
|
+
"missing_pyproject_data_files": missing_pyproject_data_files,
|
|
3538
|
+
}
|
|
3539
|
+
|
|
3540
|
+
|
|
3541
|
+
def check_package_parity(root_path: str | Path) -> dict[str, Any]:
|
|
3542
|
+
root = _resolve_root(root_path)
|
|
3543
|
+
required_surfaces = tuple(get_package_parity_surfaces())
|
|
3544
|
+
machine_blockers: list[dict[str, str]] = []
|
|
3545
|
+
blockers: list[str] = []
|
|
3546
|
+
|
|
3547
|
+
def _add_blocker(*, location: str, surface: str, path: str, reason: str = "missing_surface") -> None:
|
|
3548
|
+
blocker = {
|
|
3549
|
+
"kind": "package_parity_missing",
|
|
3550
|
+
"location": location,
|
|
3551
|
+
"surface": surface,
|
|
3552
|
+
"path": path,
|
|
3553
|
+
"reason": reason,
|
|
3554
|
+
}
|
|
3555
|
+
machine_blockers.append(blocker)
|
|
3556
|
+
blockers.append(
|
|
3557
|
+
f"package_parity_missing: location={location} surface={surface} path={path} reason={reason}"
|
|
3558
|
+
)
|
|
3559
|
+
|
|
3560
|
+
for surface in required_surfaces:
|
|
3561
|
+
source_path = root / ".agents" / "skills" / "omg" / surface / "SKILL.md"
|
|
3562
|
+
if not source_path.exists():
|
|
3563
|
+
_add_blocker(location="source", surface=surface, path=str(source_path.relative_to(root)))
|
|
3564
|
+
|
|
3565
|
+
def _check_bundle_surface_roots(*, location: str, roots: list[Path]) -> None:
|
|
3566
|
+
if not roots:
|
|
3567
|
+
for surface in required_surfaces:
|
|
3568
|
+
_add_blocker(
|
|
3569
|
+
location=location,
|
|
3570
|
+
surface=surface,
|
|
3571
|
+
path=f"{location}/bundle/.agents/skills/omg/{surface}/SKILL.md",
|
|
3572
|
+
reason="missing_output_location",
|
|
3573
|
+
)
|
|
3574
|
+
return
|
|
3575
|
+
for surface in required_surfaces:
|
|
3576
|
+
found = False
|
|
3577
|
+
for bundle_root in roots:
|
|
3578
|
+
candidate = bundle_root / ".agents" / "skills" / "omg" / surface / "SKILL.md"
|
|
3579
|
+
if candidate.exists():
|
|
3580
|
+
found = True
|
|
3581
|
+
break
|
|
3582
|
+
if not found:
|
|
3583
|
+
_add_blocker(
|
|
3584
|
+
location=location,
|
|
3585
|
+
surface=surface,
|
|
3586
|
+
path=f"{location}/bundle/.agents/skills/omg/{surface}/SKILL.md",
|
|
3587
|
+
)
|
|
3588
|
+
|
|
3589
|
+
dist_bundle_roots = [path for path in sorted((root / "dist").glob("*/bundle")) if path.is_dir()]
|
|
3590
|
+
_check_bundle_surface_roots(location="dist", roots=dist_bundle_roots)
|
|
3591
|
+
|
|
3592
|
+
release_bundle_roots = [
|
|
3593
|
+
path for path in sorted((root / "artifacts" / "release" / "dist").glob("*/bundle")) if path.is_dir()
|
|
3594
|
+
]
|
|
3595
|
+
_check_bundle_surface_roots(location="release", roots=release_bundle_roots)
|
|
3596
|
+
|
|
3597
|
+
wheel_files = sorted((root / "dist").glob("*.whl"))
|
|
3598
|
+
if not wheel_files:
|
|
3599
|
+
for surface in required_surfaces:
|
|
3600
|
+
_add_blocker(
|
|
3601
|
+
location="wheel",
|
|
3602
|
+
surface=surface,
|
|
3603
|
+
path=f"dist/*.whl::.agents/skills/omg/{surface}/SKILL.md",
|
|
3604
|
+
reason="missing_output_location",
|
|
3605
|
+
)
|
|
3606
|
+
else:
|
|
3607
|
+
wheel_path = wheel_files[-1]
|
|
3608
|
+
with zipfile.ZipFile(wheel_path) as archive:
|
|
3609
|
+
names = set(archive.namelist())
|
|
3610
|
+
for surface in required_surfaces:
|
|
3611
|
+
suffix = f".agents/skills/omg/{surface}/SKILL.md"
|
|
3612
|
+
if not any(name.endswith(suffix) for name in names):
|
|
3613
|
+
_add_blocker(
|
|
3614
|
+
location="wheel",
|
|
3615
|
+
surface=surface,
|
|
3616
|
+
path=f"{wheel_path.relative_to(root)}::{suffix}",
|
|
3617
|
+
)
|
|
3618
|
+
|
|
3619
|
+
return {
|
|
3620
|
+
"status": "ok" if not blockers else "error",
|
|
3621
|
+
"required_surfaces": list(required_surfaces),
|
|
3622
|
+
"machine_blockers": machine_blockers,
|
|
3623
|
+
"blockers": blockers,
|
|
3624
|
+
}
|
|
3625
|
+
|
|
3626
|
+
|
|
3627
|
+
def _check_packaged_install_smoke(root: Path) -> dict[str, Any]:
|
|
3628
|
+
blockers: list[str] = []
|
|
3629
|
+
with tempfile.TemporaryDirectory(prefix="omg-wheel-") as tmp_dir:
|
|
3630
|
+
proc = subprocess.run(
|
|
3631
|
+
[sys.executable, "-m", "pip", "wheel", ".", "--no-deps", "-w", tmp_dir],
|
|
3632
|
+
cwd=str(root),
|
|
3633
|
+
capture_output=True,
|
|
3634
|
+
text=True,
|
|
3635
|
+
check=False,
|
|
3636
|
+
timeout=120,
|
|
3637
|
+
)
|
|
3638
|
+
if proc.returncode != 0:
|
|
3639
|
+
return {
|
|
3640
|
+
"status": "error",
|
|
3641
|
+
"blockers": ["package smoke failed to build wheel"],
|
|
3642
|
+
"stdout": proc.stdout,
|
|
3643
|
+
"stderr": proc.stderr,
|
|
3644
|
+
}
|
|
3645
|
+
wheels = sorted(Path(tmp_dir).glob("*.whl"))
|
|
3646
|
+
if not wheels:
|
|
3647
|
+
return {"status": "error", "blockers": ["package smoke did not produce a wheel"]}
|
|
3648
|
+
with zipfile.ZipFile(wheels[-1]) as archive:
|
|
3649
|
+
names = set(archive.namelist())
|
|
3650
|
+
required_suffixes = (
|
|
3651
|
+
"control_plane/service.py",
|
|
3652
|
+
"registry/verify_artifact.py",
|
|
3653
|
+
"plugins/dephealth/cve_scanner.py",
|
|
3654
|
+
"OMG_COMPAT_CONTRACT.md",
|
|
3655
|
+
".agents/skills/omg/security-check/SKILL.md",
|
|
3656
|
+
".agents/skills/omg/plan-council/SKILL.md",
|
|
3657
|
+
".agents/skills/omg/claim-judge/SKILL.md",
|
|
3658
|
+
".agents/skills/omg/test-intent-lock/SKILL.md",
|
|
3659
|
+
".agents/skills/omg/proof-gate/SKILL.md",
|
|
3660
|
+
)
|
|
3661
|
+
for suffix in required_suffixes:
|
|
3662
|
+
if not any(name.endswith(suffix) for name in names):
|
|
3663
|
+
blockers.append(f"package parity missing {suffix}")
|
|
3664
|
+
return {"status": "ok" if not blockers else "error", "blockers": blockers}
|