@event4u/agent-config 14.23.0 → 16.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +144 -152
- package/README.md +1 -1
- package/dist/agent-src/commands/agent-handoff.md +8 -7
- package/dist/agent-src/commands/agents/init.md +1 -1
- package/dist/agent-src/commands/agents/user/accept.md +4 -4
- package/dist/agent-src/commands/agents/user/delete.md +2 -2
- package/dist/agent-src/commands/agents/user/init.md +3 -3
- package/dist/agent-src/commands/agents/user/review.md +3 -3
- package/dist/agent-src/commands/agents/user/show.md +3 -3
- package/dist/agent-src/commands/agents/user/update.md +2 -2
- package/dist/agent-src/commands/agents/user.md +6 -6
- package/dist/agent-src/commands/analytics/prune.md +1 -1
- package/dist/agent-src/commands/analytics/show.md +2 -2
- package/dist/agent-src/commands/analyze/decision.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +5 -5
- package/dist/agent-src/commands/analyze/incident.md +3 -3
- package/dist/agent-src/commands/analyze/near-miss.md +3 -3
- package/dist/agent-src/commands/analyze/postmortem.md +4 -4
- package/dist/agent-src/commands/analyze/premortem.md +2 -2
- package/dist/agent-src/commands/analyze.md +2 -2
- package/dist/agent-src/commands/bug/fix.md +1 -1
- package/dist/agent-src/commands/bug/investigate.md +2 -2
- package/dist/agent-src/commands/cost/profile.md +3 -3
- package/dist/agent-src/commands/design.md +1 -1
- package/dist/agent-src/commands/feature/explore.md +1 -1
- package/dist/agent-src/commands/feature/plan.md +2 -2
- package/dist/agent-src/commands/feature/roadmap.md +2 -2
- package/dist/agent-src/commands/ghostwriter/fetch.md +4 -4
- package/dist/agent-src/commands/ghostwriter/list.md +1 -1
- package/dist/agent-src/commands/ghostwriter/show.md +1 -1
- package/dist/agent-src/commands/ghostwriter/write.md +9 -9
- package/dist/agent-src/commands/humanize.md +6 -6
- package/dist/agent-src/commands/jira-ticket.md +1 -1
- package/dist/agent-src/commands/judge/solo.md +1 -1
- package/dist/agent-src/commands/judge.md +1 -1
- package/dist/agent-src/commands/knowledge/cross-repo.md +1 -1
- package/dist/agent-src/commands/knowledge/forget.md +1 -1
- package/dist/agent-src/commands/knowledge/ingest.md +2 -2
- package/dist/agent-src/commands/knowledge/list.md +1 -1
- package/dist/agent-src/commands/memory/add.md +3 -3
- package/dist/agent-src/commands/memory/load.md +4 -4
- package/dist/agent-src/commands/memory/mine-session.md +1 -1
- package/dist/agent-src/commands/memory/promote.md +4 -4
- package/dist/agent-src/commands/memory/propose.md +2 -2
- package/dist/agent-src/commands/mode.md +5 -5
- package/dist/agent-src/commands/post-as/ghostwriter.md +3 -3
- package/dist/agent-src/commands/post-as/me.md +7 -7
- package/dist/agent-src/commands/pr/create.md +1 -1
- package/dist/agent-src/commands/pr/merge.md +1 -1
- package/dist/agent-src/commands/profile/activate.md +1 -1
- package/dist/agent-src/commands/profile/show.md +1 -1
- package/dist/agent-src/commands/research/deep.md +2 -2
- package/dist/agent-src/commands/research/report.md +2 -2
- package/dist/agent-src/commands/review/changes.md +1 -1
- package/dist/agent-src/commands/review/routing.md +1 -1
- package/dist/agent-src/commands/roadmap/create.md +4 -3
- package/dist/agent-src/commands/roadmap/materialize.md +2 -2
- package/dist/agent-src/commands/roadmap/next.md +1 -1
- package/dist/agent-src/commands/roadmap/process-full.md +2 -2
- package/dist/agent-src/commands/security-audit-config.md +3 -3
- package/dist/agent-src/commands/skill/preview.md +1 -1
- package/dist/agent-src/commands/skills/discover.md +1 -1
- package/dist/agent-src/commands/sync/agent-settings.md +2 -2
- package/dist/agent-src/commands/sync/gitignore/fix.md +1 -1
- package/dist/agent-src/commands/tests/create.md +1 -1
- package/dist/agent-src/commands/tests/e2e-heal.md +1 -1
- package/dist/agent-src/commands/tests/e2e-plan.md +1 -1
- package/dist/agent-src/contexts/authority/kernel-rule-edits.md +6 -6
- package/dist/agent-src/contexts/communication/rules-auto/slash-command-routing-policy-mechanics.md +1 -2
- package/dist/agent-src/contexts/communication/rules-auto/think-before-action-mechanics.md +2 -2
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +88 -1
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +51 -1
- package/dist/agent-src/contexts/contracts/frugality-charter.md +1 -1
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +1 -1
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +1 -1
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +1 -1
- package/dist/agent-src/contexts/execution/cheap-question-mechanics.md +1 -1
- package/dist/agent-src/contexts/execution/contract-decision-sheet.md +3 -3
- package/dist/agent-src/contexts/execution/mandated-lines.md +1 -1
- package/dist/agent-src/contexts/execution/non-interactive-contract.md +1 -1
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -1
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +5 -5
- package/dist/agent-src/contexts/execution/roadmap-execution-contract.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-modes-detail.md +1 -1
- package/dist/agent-src/contexts/execution/subagent-routing.md +2 -2
- package/dist/agent-src/contexts/execution/subagent-spawn-contract.md +1 -1
- package/dist/agent-src/contexts/execution/user-memory-channels.md +3 -3
- package/dist/agent-src/contexts/execution/verification-mechanics.md +1 -1
- package/dist/agent-src/contexts/judges/persona-voice-rubric.md +2 -2
- package/dist/agent-src/guidelines/abstraction-thresholds.md +88 -0
- package/dist/agent-src/guidelines/agent-infra/5w2h-analysis.md +260 -0
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +99 -0
- package/dist/agent-src/guidelines/agent-infra/agent-interaction-and-decision-quality.md +325 -0
- package/dist/agent-src/guidelines/agent-infra/api-cost-levers.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +222 -0
- package/dist/agent-src/guidelines/agent-infra/ask-when-uncertain-demos.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/asking-and-brevity-examples.md +198 -0
- package/dist/agent-src/guidelines/agent-infra/break-glass-usage.md +113 -0
- package/dist/agent-src/guidelines/agent-infra/carve-out-predicates.md +17 -0
- package/dist/agent-src/guidelines/agent-infra/comparison-matrix.md +179 -0
- package/dist/agent-src/guidelines/agent-infra/context-hygiene-mechanics.md +165 -0
- package/dist/agent-src/guidelines/agent-infra/corpus-grounding-authoring.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/critical-thinking.md +156 -0
- package/dist/agent-src/guidelines/agent-infra/cross-source-consistency-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/developer-judgment.md +82 -0
- package/dist/agent-src/guidelines/agent-infra/direct-answers-demos.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/domain-adoption-gates.md +112 -0
- package/dist/agent-src/guidelines/agent-infra/domain-eval-anti-pattern.md +53 -0
- package/dist/agent-src/guidelines/agent-infra/domain-pack-architecture.md +56 -0
- package/dist/agent-src/guidelines/agent-infra/downstream-changes-mechanics.md +91 -0
- package/dist/agent-src/guidelines/agent-infra/emphasis-budget.md +45 -0
- package/dist/agent-src/guidelines/agent-infra/engineering-memory-data-format.md +210 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +92 -0
- package/dist/agent-src/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/failure-signatures.md +70 -0
- package/dist/agent-src/guidelines/agent-infra/false-green.md +233 -0
- package/dist/agent-src/guidelines/agent-infra/first-principles.md +192 -0
- package/dist/agent-src/guidelines/agent-infra/framework-neutrality-patterns.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/frontier-reasoning-operating-profile.md +169 -0
- package/dist/agent-src/guidelines/agent-infra/gate-authoring.md +177 -0
- package/dist/agent-src/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/dist/agent-src/guidelines/agent-infra/installed-tools-manifest.md +142 -0
- package/dist/agent-src/guidelines/agent-infra/inversion-thinking.md +388 -0
- package/dist/agent-src/guidelines/agent-infra/ios-simulator-guide.md +378 -0
- package/dist/agent-src/guidelines/agent-infra/language-and-tone-examples.md +141 -0
- package/dist/agent-src/guidelines/agent-infra/layered-settings.md +317 -0
- package/dist/agent-src/guidelines/agent-infra/linked-projects-onboarding-gate.md +80 -0
- package/dist/agent-src/guidelines/agent-infra/mcp-request-signing.md +197 -0
- package/dist/agent-src/guidelines/agent-infra/memory-access.md +149 -0
- package/dist/agent-src/guidelines/agent-infra/mental-models.md +314 -0
- package/dist/agent-src/guidelines/agent-infra/minimal-safe-diff-mechanics.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/missing-tool-handling.md +66 -0
- package/dist/agent-src/guidelines/agent-infra/model-recommendation.md +101 -0
- package/dist/agent-src/guidelines/agent-infra/naming.md +69 -0
- package/dist/agent-src/guidelines/agent-infra/notes-horizon-mechanics.md +90 -0
- package/dist/agent-src/guidelines/agent-infra/output-patterns.md +117 -0
- package/dist/agent-src/guidelines/agent-infra/recurring-criticism-mechanics.md +96 -0
- package/dist/agent-src/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/dist/agent-src/guidelines/agent-infra/review-routing-data-format.md +143 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-deferred-resolution-provenance.md +54 -0
- package/dist/agent-src/guidelines/agent-infra/roadmap-progress-mechanics.md +274 -0
- package/dist/agent-src/guidelines/agent-infra/role-contracts.md +209 -0
- package/dist/agent-src/guidelines/agent-infra/role-mode-router.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/rule-body-migration-inventory.md +241 -0
- package/dist/agent-src/guidelines/agent-infra/rule-type-governance.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/runtime-layer.md +89 -0
- package/dist/agent-src/guidelines/agent-infra/scqa-framework.md +526 -0
- package/dist/agent-src/guidelines/agent-infra/security-lint-containment.md +121 -0
- package/dist/agent-src/guidelines/agent-infra/self-improvement-pipeline.md +134 -0
- package/dist/agent-src/guidelines/agent-infra/simplicity-and-goal-demos.md +211 -0
- package/dist/agent-src/guidelines/agent-infra/six-hats.md +353 -0
- package/dist/agent-src/guidelines/agent-infra/size-and-scope.md +229 -0
- package/dist/agent-src/guidelines/agent-infra/skill-quality-checklist.md +157 -0
- package/dist/agent-src/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/dist/agent-src/guidelines/agent-infra/symptom-driven-harvest-loop.md +104 -0
- package/dist/agent-src/guidelines/agent-infra/systems-thinking.md +220 -0
- package/dist/agent-src/guidelines/agent-infra/tool-description-as-policy.md +35 -0
- package/dist/agent-src/guidelines/agent-infra/tool-integration.md +73 -0
- package/dist/agent-src/guidelines/agent-infra/traceability-field-mechanics.md +50 -0
- package/dist/agent-src/guidelines/agent-infra/untrusted-input-spotlighting.md +166 -0
- package/dist/agent-src/guidelines/agent-infra/verify-before-complete-demos.md +128 -0
- package/dist/agent-src/guidelines/augment-portability-patterns.md +70 -0
- package/dist/agent-src/guidelines/code-clarity.md +350 -0
- package/dist/agent-src/guidelines/component-oriented-and-oop-development.md +189 -0
- package/dist/agent-src/guidelines/cross-role-handoff.md +127 -0
- package/dist/agent-src/guidelines/design-antipatterns-triggers.json +18 -0
- package/dist/agent-src/guidelines/design-antipatterns.md +327 -0
- package/dist/agent-src/guidelines/design-asset-discipline.md +38 -0
- package/dist/agent-src/guidelines/design-canon.md +63 -0
- package/dist/agent-src/guidelines/design-fidelity-mechanics.md +293 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +78 -0
- package/dist/agent-src/guidelines/design-handover-extraction.md +117 -0
- package/dist/agent-src/guidelines/design-modes.md +143 -0
- package/dist/agent-src/guidelines/design-reconciliation.md +172 -0
- package/dist/agent-src/guidelines/docs/readme-size-and-splitting.md +209 -0
- package/dist/agent-src/guidelines/e2e/playwright.md +363 -0
- package/dist/agent-src/guidelines/gtm-handoff.md +114 -0
- package/dist/agent-src/guidelines/monorepo-antipatterns.md +141 -0
- package/dist/agent-src/guidelines/php/api-design.md +115 -0
- package/dist/agent-src/guidelines/php/artisan-commands.md +81 -0
- package/dist/agent-src/guidelines/php/blade-ui.md +78 -0
- package/dist/agent-src/guidelines/php/controllers.md +90 -0
- package/dist/agent-src/guidelines/php/database.md +194 -0
- package/dist/agent-src/guidelines/php/eloquent.md +208 -0
- package/dist/agent-src/guidelines/php/flux.md +80 -0
- package/dist/agent-src/guidelines/php/general.md +207 -0
- package/dist/agent-src/guidelines/php/git.md +164 -0
- package/dist/agent-src/guidelines/php/jobs.md +111 -0
- package/dist/agent-src/guidelines/php/livewire.md +71 -0
- package/dist/agent-src/guidelines/php/logging.md +79 -0
- package/dist/agent-src/guidelines/php/naming.md +89 -0
- package/dist/agent-src/guidelines/php/patterns/dependency-injection.md +57 -0
- package/dist/agent-src/guidelines/php/patterns/dtos.md +199 -0
- package/dist/agent-src/guidelines/php/patterns/events.md +67 -0
- package/dist/agent-src/guidelines/php/patterns/factory.md +53 -0
- package/dist/agent-src/guidelines/php/patterns/pipelines.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/policies.md +66 -0
- package/dist/agent-src/guidelines/php/patterns/repositories.md +122 -0
- package/dist/agent-src/guidelines/php/patterns/service-layer.md +64 -0
- package/dist/agent-src/guidelines/php/patterns/strategy.md +247 -0
- package/dist/agent-src/guidelines/php/patterns.md +28 -0
- package/dist/agent-src/guidelines/php/performance.md +92 -0
- package/dist/agent-src/guidelines/php/php-coding-patterns.md +80 -0
- package/dist/agent-src/guidelines/php/resources.md +100 -0
- package/dist/agent-src/guidelines/php/security.md +110 -0
- package/dist/agent-src/guidelines/php/sql.md +97 -0
- package/dist/agent-src/guidelines/php/validations.md +119 -0
- package/dist/agent-src/guidelines/php/websocket.md +100 -0
- package/dist/agent-src/guidelines/prompt-templates.md +189 -0
- package/dist/agent-src/guidelines/redundancy-taxonomy.md +208 -0
- package/dist/agent-src/guidelines/wing4-handoff.md +127 -0
- package/dist/agent-src/personas/README.md +1 -1
- package/dist/agent-src/personas/_template-specialist/persona.md +1 -1
- package/dist/agent-src/personas/product-owner.md +1 -1
- package/dist/agent-src/rules/active-remediation.md +1 -1
- package/dist/agent-src/rules/architecture.md +2 -2
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/ask-when-uncertain.md +2 -2
- package/dist/agent-src/rules/brand-source-of-truth.md +9 -7
- package/dist/agent-src/rules/code-comment-discipline.md +2 -2
- package/dist/agent-src/rules/code-provenance.md +1 -1
- package/dist/agent-src/rules/content-quoting-floor.md +1 -1
- package/dist/agent-src/rules/context-hygiene.md +5 -5
- package/dist/agent-src/rules/cross-source-consistency.md +1 -1
- package/dist/agent-src/rules/design-fidelity.md +36 -46
- package/dist/agent-src/rules/direct-answers.md +2 -2
- package/dist/agent-src/rules/domain-adoption-policy.md +1 -1
- package/dist/agent-src/rules/evaluator-independence.md +1 -1
- package/dist/agent-src/rules/framework-neutrality-in-generic-skills.md +1 -1
- package/dist/agent-src/rules/icon-consistency.md +5 -1
- package/dist/agent-src/rules/improve-before-implement.md +1 -1
- package/dist/agent-src/rules/language-and-tone.md +3 -3
- package/dist/agent-src/rules/minimal-safe-diff.md +1 -1
- package/dist/agent-src/rules/notes-first-reasoning.md +2 -2
- package/dist/agent-src/rules/recurring-criticism.md +1 -1
- package/dist/agent-src/rules/roadmap-progress-sync.md +1 -1
- package/dist/agent-src/rules/role-mode-adherence.md +2 -2
- package/dist/agent-src/rules/security-sensitive-stop.md +1 -1
- package/dist/agent-src/rules/size-enforcement.md +1 -1
- package/dist/agent-src/rules/source-confidentiality.md +2 -2
- package/dist/agent-src/rules/think-before-action.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +7 -5
- package/dist/agent-src/rules/ui-audit-gate.md +3 -2
- package/dist/agent-src/rules/untrusted-input-defense.md +3 -3
- package/dist/agent-src/rules/user-interaction.md +31 -30
- package/dist/agent-src/skills/activation-design/SKILL.md +2 -2
- package/dist/agent-src/skills/adr-create/SKILL.md +5 -5
- package/dist/agent-src/skills/ai-council/SKILL.md +1 -1
- package/dist/agent-src/skills/ai-council/references/advanced-modes.md +1 -1
- package/dist/agent-src/skills/blade-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/blast-radius-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/brand-to-tokens/SKILL.md +24 -3
- package/dist/agent-src/skills/bug-analyzer/SKILL.md +1 -1
- package/dist/agent-src/skills/build-buy-partner/SKILL.md +1 -1
- package/dist/agent-src/skills/churn-prevention/SKILL.md +2 -2
- package/dist/agent-src/skills/code-refactoring/SKILL.md +3 -3
- package/dist/agent-src/skills/code-review/SKILL.md +3 -3
- package/dist/agent-src/skills/command-writing/SKILL.md +3 -3
- package/dist/agent-src/skills/comp-banding/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-moat-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/competitive-positioning/SKILL.md +2 -2
- package/dist/agent-src/skills/complexity-first-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/condense-memory/SKILL.md +1 -1
- package/dist/agent-src/skills/content-funnel-design/SKILL.md +2 -2
- package/dist/agent-src/skills/contracts-cognition/SKILL.md +1 -1
- package/dist/agent-src/skills/copilot-agents-optimization/SKILL.md +2 -2
- package/dist/agent-src/skills/corpus-grounding/SKILL.md +2 -2
- package/dist/agent-src/skills/customer-research/SKILL.md +2 -2
- package/dist/agent-src/skills/dashboard-design/SKILL.md +1 -1
- package/dist/agent-src/skills/data-handling-judgment/SKILL.md +1 -1
- package/dist/agent-src/skills/deal-qualification-meddic/SKILL.md +2 -2
- package/dist/agent-src/skills/decision-record/SKILL.md +3 -3
- package/dist/agent-src/skills/decision-review/SKILL.md +8 -8
- package/dist/agent-src/skills/deep-reading-analyst/SKILL.md +22 -22
- package/dist/agent-src/skills/design-intelligence/SKILL.md +4 -4
- package/dist/agent-src/skills/design-intelligence/references/context-and-registers.md +2 -2
- package/dist/agent-src/skills/design-review/SKILL.md +4 -4
- package/dist/agent-src/skills/design-system-capture/references/design-system-json.md +20 -1
- package/dist/agent-src/skills/design-tokens/SKILL.md +17 -2
- package/dist/agent-src/skills/design-variations/SKILL.md +2 -2
- package/dist/agent-src/skills/developer-like-execution/SKILL.md +2 -2
- package/dist/agent-src/skills/discovery-interview/SKILL.md +2 -2
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +3 -3
- package/dist/agent-src/skills/editorial-calendar/SKILL.md +2 -2
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +44 -1
- package/dist/agent-src/skills/existing-ui-audit/references/anti-slop-cross-reference.md +2 -2
- package/dist/agent-src/skills/expansion-playbook/SKILL.md +2 -2
- package/dist/agent-src/skills/experiment-loop/SKILL.md +1 -1
- package/dist/agent-src/skills/experiment-loop/references/protocol.md +1 -1
- package/dist/agent-src/skills/fe-design/SKILL.md +3 -3
- package/dist/agent-src/skills/fe-design/references/design-patterns.md +1 -1
- package/dist/agent-src/skills/fe-design/references/design-read-and-memory.md +3 -3
- package/dist/agent-src/skills/fe-design/references/source-led-port.md +1 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +1 -1
- package/dist/agent-src/skills/flux/SKILL.md +1 -1
- package/dist/agent-src/skills/forecast-accuracy/SKILL.md +2 -2
- package/dist/agent-src/skills/forecasting/SKILL.md +2 -2
- package/dist/agent-src/skills/fundraising-narrative/SKILL.md +2 -2
- package/dist/agent-src/skills/funnel-analysis/SKILL.md +2 -2
- package/dist/agent-src/skills/gtm-launch/SKILL.md +2 -2
- package/dist/agent-src/skills/hiring-loop-design/SKILL.md +1 -1
- package/dist/agent-src/skills/html-deck/SKILL.md +5 -5
- package/dist/agent-src/skills/humanizer/SKILL.md +1 -1
- package/dist/agent-src/skills/humanizer/data/patterns.md +1 -1
- package/dist/agent-src/skills/iconography/SKILL.md +3 -3
- package/dist/agent-src/skills/judge-synthesis/SKILL.md +1 -1
- package/dist/agent-src/skills/laravel-api-endpoint/SKILL.md +2 -2
- package/dist/agent-src/skills/launch-readiness/SKILL.md +3 -3
- package/dist/agent-src/skills/learning-to-rule-or-skill/SKILL.md +6 -6
- package/dist/agent-src/skills/livewire/SKILL.md +1 -1
- package/dist/agent-src/skills/market-entry-analysis/SKILL.md +1 -1
- package/dist/agent-src/skills/mcp-builder/SKILL.md +1 -1
- package/dist/agent-src/skills/memory-consolidation/SKILL.md +3 -3
- package/dist/agent-src/skills/messaging-architecture/SKILL.md +2 -2
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +1 -1
- package/dist/agent-src/skills/onboarding-design/SKILL.md +2 -2
- package/dist/agent-src/skills/onboarding-program/SKILL.md +1 -1
- package/dist/agent-src/skills/one-on-one-cadence/SKILL.md +1 -1
- package/dist/agent-src/skills/org-design/SKILL.md +1 -1
- package/dist/agent-src/skills/overbuild-review-lens/SKILL.md +3 -3
- package/dist/agent-src/skills/override-management/SKILL.md +1 -1
- package/dist/agent-src/skills/perf-feedback-craft/SKILL.md +1 -1
- package/dist/agent-src/skills/persona-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/php-coder/SKILL.md +1 -1
- package/dist/agent-src/skills/pipeline-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/playwright-testing/SKILL.md +3 -3
- package/dist/agent-src/skills/po-discovery/SKILL.md +1 -1
- package/dist/agent-src/skills/positioning-strategy/SKILL.md +2 -2
- package/dist/agent-src/skills/privacy-review/SKILL.md +1 -1
- package/dist/agent-src/skills/prompt-optimizer/SKILL.md +3 -3
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/reasoning-orchestrator/SKILL.md +2 -2
- package/dist/agent-src/skills/receiving-code-review/SKILL.md +1 -1
- package/dist/agent-src/skills/refine-ticket/SKILL.md +2 -2
- package/dist/agent-src/skills/release-comms/SKILL.md +2 -2
- package/dist/agent-src/skills/retention-loops/SKILL.md +2 -2
- package/dist/agent-src/skills/review-routing/SKILL.md +2 -2
- package/dist/agent-src/skills/roadmap-management/SKILL.md +1 -1
- package/dist/agent-src/skills/roadmap-management/references/archival.md +1 -1
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +5 -5
- package/dist/agent-src/skills/rule-writing/SKILL.md +6 -6
- package/dist/agent-src/skills/runway-cognition/SKILL.md +2 -2
- package/dist/agent-src/skills/scenario-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/SKILL.md +2 -2
- package/dist/agent-src/skills/skill-writing/references/procedure.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/section-patterns.md +3 -3
- package/dist/agent-src/skills/spreadsheet-authoring/SKILL.md +1 -1
- package/dist/agent-src/skills/stakeholder-tradeoff/SKILL.md +3 -3
- package/dist/agent-src/skills/subagent-orchestration/SKILL.md +1 -1
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +1 -1
- package/dist/agent-src/skills/systematic-debugging/SKILL.md +2 -2
- package/dist/agent-src/skills/tailwind-engineer/SKILL.md +11 -5
- package/dist/agent-src/skills/throughput-vs-morale-tradeoff/SKILL.md +1 -1
- package/dist/agent-src/skills/token-optimizer/SKILL.md +1 -1
- package/dist/agent-src/skills/typography-system/SKILL.md +2 -2
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +28 -8
- package/dist/agent-src/skills/unit-economics-modeling/SKILL.md +2 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +1 -1
- package/dist/agent-src/skills/validate-feature-fit/SKILL.md +2 -2
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +2 -2
- package/dist/agent-src/skills/vision-articulation/SKILL.md +1 -1
- package/dist/agent-src/skills/voc-extract/SKILL.md +2 -2
- package/dist/agent-src/skills/voice-and-tone-design/SKILL.md +2 -2
- package/dist/agent-src/skills/wireframe/SKILL.md +1 -1
- package/dist/agent-src/skills/workspace-link/SKILL.md +1 -1
- package/dist/agent-src/templates/agent-settings.md +6 -6
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/contexts/knowledge-pages.md +2 -3
- package/dist/agent-src/templates/copilot-instructions.md +1 -1
- package/dist/agent-src/templates/roadmaps.md +1 -1
- package/dist/agent-src/templates/scripts/README.md +2 -2
- package/dist/agent-src/user-types/_template/user-type.md +1 -1
- package/dist/cli-delegate/check_update_banner.js +12 -1
- package/dist/cli-delegate/{chunk-NLP54U3J.js → chunk-2XY7ILBT.js} +13 -1
- package/dist/cli-delegate/{chunk-IAI3XL43.js → chunk-3FKR64R5.js} +22 -6
- package/dist/cli-delegate/{chunk-J3RFNKG4.js → chunk-55ACLDUE.js} +2 -2
- package/dist/cli-delegate/{chunk-PKOBQADP.js → chunk-57PN4GYJ.js} +1 -1
- package/dist/cli-delegate/{chunk-KCNTUSPI.js → chunk-AK6HODNQ.js} +4 -4
- package/dist/cli-delegate/{chunk-2FTMENJL.js → chunk-GYC3VRYN.js} +1 -1
- package/dist/cli-delegate/{chunk-LWTYTX6F.js → chunk-H32DGUJH.js} +15 -84
- package/dist/cli-delegate/{chunk-6RJA2F5A.js → chunk-IVBPRCQO.js} +1 -1
- package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- package/dist/cli-delegate/{chunk-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- package/dist/cli-delegate/{chunk-I6RHFQEG.js → chunk-YUM5KURG.js} +17 -1
- package/dist/cli-delegate/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +834 -275
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_explain.js +5 -3
- package/dist/cli-delegate/cmd_export.js +4 -4
- package/dist/cli-delegate/cmd_handoff.js +6 -6
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_memory_get.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -2
- package/dist/cli-delegate/cmd_prune.js +4 -4
- package/dist/cli-delegate/cmd_route_audit.js +3 -3
- package/dist/cli-delegate/cmd_route_explain.js +1 -1
- package/dist/cli-delegate/cmd_session_recycle.js +3 -3
- package/dist/cli-delegate/cmd_settings_get.js +15 -9
- package/dist/cli-delegate/cmd_settings_migrate.js +4 -4
- package/dist/cli-delegate/cmd_settings_set.js +54 -11
- package/dist/cli-delegate/cmd_sync.js +4 -4
- package/dist/cli-delegate/cmd_uninstall.js +4 -4
- package/dist/cli-delegate/cmd_update.js +1 -1
- package/dist/cli-delegate/cmd_upgrade.js +1 -1
- package/dist/cli-delegate/cmd_validate.js +4 -4
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +25 -78
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +3 -3
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +5 -7
- package/dist/discovery/trust-report.md +2 -2
- package/dist/discovery/workspaces.json +4 -6
- package/dist/hooks/dispatch.js +9684 -9304
- package/dist/install/install.mjs +36 -8
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +2147 -1470
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +73 -0
- package/dist/scripts/_lib/agent_src.js +20 -0
- package/dist/scripts/_lib/guidelines_lane.js +158 -0
- package/dist/scripts/_lib/installed_lock.js +1 -1
- package/dist/scripts/condense.js +2 -5
- package/dist/server/io/yamlIO.js +175 -17
- package/dist/server/schemas/settings.js +18 -4
- package/dist/shared/settingsSurface.js +6 -1
- package/docs/contracts/settings-classes.md +18 -9
- package/docs/guidelines/abstraction-thresholds.md +35 -0
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +3 -3
- package/docs/guidelines/agent-infra/false-green.md +2 -2
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +1 -1
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +1 -1
- package/docs/guidelines/design-fidelity-mechanics.md +4 -0
- package/docs/guidelines/design-fidelity-routing.md +78 -0
- package/docs/guidelines/design-reconciliation.md +172 -0
- package/docs/guidelines/gtm-handoff.md +2 -2
- package/docs/guidelines/wing4-handoff.md +2 -2
- package/package.json +7 -3
- package/src/config/agent-settings.template.yml +55 -8
- package/src/config/ci-local-parity.yml +34 -0
- package/src/config/continuity-surface.json +11 -32
- package/src/config/gate-coverage.yml +31 -8
- package/src/config/gate-reachability-exemptions.json +1 -0
- package/src/config/gate-violation-baselines.json +3 -3
- package/src/config/hook-token-budget.json +1 -1
- package/src/config/host-injection-effect.json +2 -2
- package/src/config/pack-size-budget.json +55 -4
- package/src/config/platform-anchor.json +47 -0
- package/src/config/preamble-payload-budget.json +12 -8
- package/src/config/preamble-payload-exceptions.json +28 -0
- package/src/config/publish-surface.json +4 -2
- package/src/config/ratification-policy.json +7 -0
- package/src/config/rule-stub-ceilings.json +16 -9
- package/src/scripts/_cli/cmd_settings_get.ts +22 -7
- package/src/scripts/_dispatch.bash +3 -1
- package/src/scripts/_lib/agent_settings.ts +85 -0
- package/src/scripts/_lib/agent_src.ts +21 -0
- package/src/scripts/_lib/artifact_maturity.ts +242 -0
- package/src/scripts/_lib/base_ref_payload.ts +315 -0
- package/src/scripts/_lib/conformance_report.ts +403 -0
- package/src/scripts/_lib/continuity_writer.ts +72 -13
- package/src/scripts/_lib/design_system_import.ts +20 -0
- package/src/scripts/_lib/design_tolerance.ts +281 -0
- package/src/scripts/_lib/dropped_decision.ts +142 -0
- package/src/scripts/_lib/guidelines_lane.ts +164 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- package/src/scripts/_lib/measured_payload_ceiling.ts +424 -0
- package/src/scripts/_lib/obligation_frequency.ts +2 -3
- package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/platform_anchor.ts +1053 -0
- package/src/scripts/_lib/ratification_artifact.ts +278 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/security_lint.ts +23 -2
- package/src/scripts/_lib/self_repair.ts +21 -0
- package/src/scripts/_lib/session_index_trust.ts +42 -1
- package/src/scripts/_lib/standing_bound_ratchet.ts +267 -88
- package/src/scripts/_lib/tolerance_shadow.ts +177 -0
- package/src/scripts/_lib/turn_end_refusals.ts +51 -4
- package/src/scripts/ai_council/redact_low_impact_entry.ts +8 -0
- package/src/scripts/autonomy_friction_traffic.ts +379 -0
- package/src/scripts/check_beta_review_markers.ts +35 -4
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_kernel_edit_ratified.ts +462 -0
- package/src/scripts/check_loss_class_declared.ts +793 -46
- package/src/scripts/check_platform_anchor.ts +390 -0
- package/src/scripts/check_preamble_payload_budget.ts +672 -107
- package/src/scripts/check_projected_rule_routes.ts +395 -0
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- package/src/scripts/code_graph/cli.ts +6 -0
- package/src/scripts/condense.ts +2 -6
- package/src/scripts/generate_host_cost_table.ts +354 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +74 -30
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/category_a.ts +1 -1
- package/src/scripts/hooks/chain_nudge_hook.ts +311 -25
- package/src/scripts/hooks/concern_registry.ts +2 -0
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- package/src/scripts/hooks/review_baseline_hook.ts +130 -0
- package/src/scripts/hooks/session_eol_hook.ts +68 -154
- package/src/scripts/hooks/source_first_gate_hook.ts +12 -0
- package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks/ui_route_nudge_hook.ts +52 -15
- package/src/scripts/hooks_doctor.ts +4 -1
- package/src/scripts/hot_context_hook.ts +41 -401
- package/src/scripts/install.ts +1 -1
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +8 -1
- package/src/scripts/lint_evidence_artifacts.ts +1 -0
- package/src/scripts/lint_knowledge_scale.ts +0 -26
- package/src/scripts/measure_turn_end_gate.ts +302 -5
- package/src/scripts/memory_lookup.ts +9 -9
- package/src/scripts/release.ts +52 -64
- package/src/scripts/release_drill.ts +193 -14
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +1 -1
- package/src/scripts/report_held_object_arrivals.ts +456 -0
- package/src/scripts/report_standing_payload_by_host.ts +7 -7
- package/src/scripts/session_memory_index.ts +5 -0
- package/src/scripts/sync_agent_settings.ts +394 -12
- package/src/server/io/yamlIO.ts +175 -17
- package/src/server/schemas/settings.ts +25 -5
- package/src/shared/settingsSurface.ts +13 -2
- package/src/templates/compact-instructions.md +8 -3
- package/dist/agent-src/commands/chat-history/import.md +0 -170
- package/dist/agent-src/commands/chat-history.md +0 -63
|
@@ -0,0 +1,1053 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The platform anchor: does the forge actually enforce what the ratification
|
|
3
|
+
* mechanism's trust argument says it enforces?
|
|
4
|
+
*
|
|
5
|
+
* `check_kernel_edit_ratified` proves that a diff touching a kernel rule, a
|
|
6
|
+
* governance hook or the ratification mechanism itself carries a reviewed
|
|
7
|
+
* artifact. What no diff can contain is what the forge itself requires, and
|
|
8
|
+
* that is what this module reads.
|
|
9
|
+
*
|
|
10
|
+
* It does NOT establish that the artifact was reviewed by anyone the repository
|
|
11
|
+
* insisted on: by owner ruling of 2026-09-10 this repository requires no
|
|
12
|
+
* approving review, because one maintainer is active and a mandatory approval
|
|
13
|
+
* would be a stop rather than a control. So what this module checks is branch
|
|
14
|
+
* integrity plus the presence of the required context that carries the gate —
|
|
15
|
+
* see `docs/contracts/ratification-artifact.md` for what a green anchor is and
|
|
16
|
+
* is not evidence of, which is the shorter list than it once was.
|
|
17
|
+
*
|
|
18
|
+
* It is deliberately PURE: it evaluates ruleset payloads a caller fetched, so
|
|
19
|
+
* both polarities are testable offline and no test needs a token. The impure
|
|
20
|
+
* half — the `gh api` calls and the fail-closed exit codes — lives in
|
|
21
|
+
* `src/scripts/check_platform_anchor.ts`.
|
|
22
|
+
*
|
|
23
|
+
* Five constraints below are structural, and the code would read as arbitrary
|
|
24
|
+
* without them:
|
|
25
|
+
*
|
|
26
|
+
* 1. **Effective combination, never a ruleset by id.** A repository may carry
|
|
27
|
+
* several active branch rulesets and an administrator may split one at any
|
|
28
|
+
* time. Assuming ruleset `17749383` — which is what exists today — or its
|
|
29
|
+
* name would make this check wrong on the first split. So every applicable
|
|
30
|
+
* active ruleset is unioned: booleans OR together, the approval count takes
|
|
31
|
+
* the MAXIMUM, contexts union, and bypass actors accumulate.
|
|
32
|
+
* 2. **A non-negotiable floor in the code, in two tiers.** The expectation lives
|
|
33
|
+
* in `src/config/platform-anchor.json` so that changing it is a reviewable
|
|
34
|
+
* governance diff. That alone is not enough: a policy-only edit could lower a
|
|
35
|
+
* threshold and make itself green, so `NON_NEGOTIABLE_FLOOR` below is what
|
|
36
|
+
* such an edit cannot cross. `NEVER_WAIVABLE` is the harder tier inside it —
|
|
37
|
+
* the dimensions no waiver may reach at all.
|
|
38
|
+
* 3. **Two distinct negative outcomes, and one qualified positive.**
|
|
39
|
+
* `noncompliant` means the configuration was read and violates the policy;
|
|
40
|
+
* `unverifiable` means it could not be established, and both are failures
|
|
41
|
+
* because a control that passes when it cannot measure is advisory. They stay
|
|
42
|
+
* separate statuses because they are different repairs.
|
|
43
|
+
* `compliant-with-accepted-risk` is the third: every hard dimension present,
|
|
44
|
+
* and at least one baseline expectation covered by a complete, unexpired,
|
|
45
|
+
* owner-authorised waiver. It is neither a pass nor a failure, and the caller
|
|
46
|
+
* must not print it as a plain pass.
|
|
47
|
+
* 4. **Configured bypass actors, not the caller's own capability.** GitHub
|
|
48
|
+
* reports `current_user_can_bypass` for the acting identity, which varies by
|
|
49
|
+
* token and says nothing durable about the repository. The durable fact is
|
|
50
|
+
* the configured `bypass_actors` list, so that is what is judged.
|
|
51
|
+
* 5. **A malformed waiver is worse than no waiver.** An incomplete, expired or
|
|
52
|
+
* impermissible waiver is refused, and the dimension it named is then judged
|
|
53
|
+
* normally. The opposite direction — a badly written waiver skipping a check
|
|
54
|
+
* — is how a waiver mechanism becomes an exemption registry.
|
|
55
|
+
*
|
|
56
|
+
* A required status check pins a JOB's reported name, never the steps inside it.
|
|
57
|
+
* `check_kernel_edit_ratified` is a step inside `Sync + Generate Tools
|
|
58
|
+
* Consistency` (`.github/workflows/consistency.yml:811-825`), and that job IS
|
|
59
|
+
* the ruleset's one required context — so the gate is platform-required
|
|
60
|
+
* transitively. It is necessary and not sufficient: a candidate branch may
|
|
61
|
+
* delete the step and the job still reports the same context, green. That is the
|
|
62
|
+
* defect the round-2 ratification review refused the deny-retirement over, and
|
|
63
|
+
* this module does not close it. It asserts the necessary half and says so.
|
|
64
|
+
*/
|
|
65
|
+
|
|
66
|
+
/** A rule inside a ruleset. `parameters` is untyped until the type is known. */
|
|
67
|
+
export interface RulesetRule {
|
|
68
|
+
type?: string | undefined;
|
|
69
|
+
parameters?: unknown;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** One entry of a ruleset's bypass list. */
|
|
73
|
+
export interface BypassActor {
|
|
74
|
+
actor_type?: string | undefined;
|
|
75
|
+
actor_id?: number | undefined;
|
|
76
|
+
bypass_mode?: string | undefined;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/** The ruleset shape this module reads. Extra fields are ignored. */
|
|
80
|
+
export interface RulesetDetail {
|
|
81
|
+
id?: number | undefined;
|
|
82
|
+
name?: string | undefined;
|
|
83
|
+
target?: string | undefined;
|
|
84
|
+
enforcement?: string | undefined;
|
|
85
|
+
conditions?:
|
|
86
|
+
| {
|
|
87
|
+
ref_name?:
|
|
88
|
+
| {
|
|
89
|
+
include?: string[] | undefined;
|
|
90
|
+
exclude?: string[] | undefined;
|
|
91
|
+
}
|
|
92
|
+
| undefined;
|
|
93
|
+
}
|
|
94
|
+
| undefined;
|
|
95
|
+
rules?: RulesetRule[] | undefined;
|
|
96
|
+
bypass_actors?: BypassActor[] | undefined;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** The committed expectation, as `src/config/platform-anchor.json` carries it. */
|
|
100
|
+
export interface AnchorPolicy {
|
|
101
|
+
enforcement: string;
|
|
102
|
+
target: string;
|
|
103
|
+
covers_default_branch: boolean;
|
|
104
|
+
required_review_thread_resolution: boolean;
|
|
105
|
+
block_deletion: boolean;
|
|
106
|
+
block_non_fast_forward: boolean;
|
|
107
|
+
strict_required_status_checks: boolean;
|
|
108
|
+
required_status_check_contexts: string[];
|
|
109
|
+
allow_unconditional_bypass: boolean;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* The floor a policy edit may not cross.
|
|
114
|
+
*
|
|
115
|
+
* openai/codex-default's condition, quoted: *"Reject unknown fields, missing
|
|
116
|
+
* requirements, and policy values weaker than a small non-negotiable floor
|
|
117
|
+
* embedded in the verifier. That last safeguard prevents a policy-only edit from
|
|
118
|
+
* redefining `minimumApprovals` to zero and making itself green."*
|
|
119
|
+
*/
|
|
120
|
+
export const NON_NEGOTIABLE_FLOOR = {
|
|
121
|
+
// `minimum_approving_reviews` and `require_last_push_approval` are NOT here,
|
|
122
|
+
// and their absence is a recorded trust-model decision rather than an
|
|
123
|
+
// oversight or a convenience. This repository has one active maintainer;
|
|
124
|
+
// the others hold write access and are rarely available. A mandatory
|
|
125
|
+
// approving review is therefore not a control here, it is a stop — and a
|
|
126
|
+
// floor nothing can satisfy makes red mean "the repository chose a
|
|
127
|
+
// different model" instead of "the platform violates its policy", which is
|
|
128
|
+
// the reading that gets a gate deleted or wired `continue-on-error`.
|
|
129
|
+
//
|
|
130
|
+
// The boundary this does NOT cross, stated because the mechanism cannot
|
|
131
|
+
// check it: a floor reduction is legitimate only where the operating model
|
|
132
|
+
// CANNOT satisfy the floor — no second human exists — never where waiting
|
|
133
|
+
// is merely inconvenient.
|
|
134
|
+
//
|
|
135
|
+
// `required_review_thread_resolution` IS here, and it was briefly not.
|
|
136
|
+
// Removing it was the implementer's own judgement — approval-adjacent,
|
|
137
|
+
// near-vacuous once no review is required — and a blind review caught that
|
|
138
|
+
// the same change's record says only the owner may authorize a floor
|
|
139
|
+
// reduction while the ruling names two dimensions and not this one. It also
|
|
140
|
+
// left the check policy-lowerable: one boolean flip in the expectation
|
|
141
|
+
// deleted it while the anchor still read compliant. Restored, and the forge
|
|
142
|
+
// has it true, so restoring costs nothing and removes an unauthorized
|
|
143
|
+
// reduction rather than adding a requirement.
|
|
144
|
+
required_review_thread_resolution: true,
|
|
145
|
+
block_deletion: true,
|
|
146
|
+
block_non_fast_forward: true,
|
|
147
|
+
allow_unconditional_bypass: false,
|
|
148
|
+
minimum_required_contexts: 1,
|
|
149
|
+
// `strict_required_status_checks` is NOT in the floor, and that is the
|
|
150
|
+
// second half of the same correction. It carries an active waiver, and a
|
|
151
|
+
// floored dimension is unwaivable by construction — holding it in both
|
|
152
|
+
// places was the contradiction a blind review surfaced from the other side.
|
|
153
|
+
// It stays a baseline expectation in `platform-anchor.json`, which is the
|
|
154
|
+
// tier a waiver may trade against.
|
|
155
|
+
//
|
|
156
|
+
// The SELECTOR half. The values above are thresholds a ruleset is
|
|
157
|
+
// measured against; these three decide WHICH rulesets are measured at all,
|
|
158
|
+
// and leaving them unfloored left the whole check reachable through a door
|
|
159
|
+
// the floor did not watch. A blind review probed it against this evaluator:
|
|
160
|
+
// `enforcement: "evaluate"` credited a non-enforcing dry-run ruleset as
|
|
161
|
+
// full protection and returned `compliant` with zero findings, and
|
|
162
|
+
// `covers_default_branch: false` credited a `refs/heads/release`-only
|
|
163
|
+
// ruleset to `main` while `main` carried no ruleset. Neither touched a
|
|
164
|
+
// threshold, so neither was caught.
|
|
165
|
+
enforcement: 'active',
|
|
166
|
+
target: 'branch',
|
|
167
|
+
covers_default_branch: true,
|
|
168
|
+
} as const;
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* A dimension the owner has deliberately left unsatisfied, on record.
|
|
172
|
+
*
|
|
173
|
+
* The third state exists because the first two were both wrong for a real case.
|
|
174
|
+
* Deleting a dimension the repository still considers the safer configuration
|
|
175
|
+
* erases the difference between "never expected" and "expected and knowingly
|
|
176
|
+
* waived"; leaving it enforced-and-red makes the gate say the configuration
|
|
177
|
+
* violates its policy when the policy is what chose it. openai: *"If
|
|
178
|
+
* noncompliance does not fail the gate, that dimension is not enforced for the
|
|
179
|
+
* duration of the waiver. It remains a baseline expectation subject to an
|
|
180
|
+
* active exception."* This type is that exception, made checkable.
|
|
181
|
+
*
|
|
182
|
+
* Every field is required, and a waiver missing any of them is not honoured —
|
|
183
|
+
* an incomplete waiver is an undocumented deviation wearing a record's clothes.
|
|
184
|
+
* `expires` is the load-bearing one: without it a waiver outlives the cost
|
|
185
|
+
* premise that justified it, and the premise here is a CI duration, which is
|
|
186
|
+
* not a constant.
|
|
187
|
+
*/
|
|
188
|
+
export interface AcceptedRiskWaiver {
|
|
189
|
+
dimension: string;
|
|
190
|
+
baseline: boolean;
|
|
191
|
+
accepted: boolean;
|
|
192
|
+
/** What can go wrong, in concrete terms rather than as a category. */
|
|
193
|
+
failure_mode: string;
|
|
194
|
+
/** What the waiver buys, with the measurement it rests on. */
|
|
195
|
+
cost_avoided: string;
|
|
196
|
+
/** Explicitly an assumption unless incident data supports it. */
|
|
197
|
+
frequency_assumption: string;
|
|
198
|
+
/** How a breakage is noticed and by whom, and how it gets repaired. */
|
|
199
|
+
detection_and_repair: string;
|
|
200
|
+
/** What still protects the surface, and what that protection does NOT cover. */
|
|
201
|
+
residual_protection: string;
|
|
202
|
+
authority: string;
|
|
203
|
+
decided: string;
|
|
204
|
+
/** ISO date. A waiver past it is not honoured, renewal is a fresh decision. */
|
|
205
|
+
expires: string;
|
|
206
|
+
review_triggers: string[];
|
|
207
|
+
id: string;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
/**
|
|
211
|
+
* Dimensions no waiver may reach, whatever it documents.
|
|
212
|
+
*
|
|
213
|
+
* DERIVED FROM THE FLOOR, not hand-listed beside it. A blind review found the
|
|
214
|
+
* hand-written version had drifted from it in the worst possible direction:
|
|
215
|
+
* `required_review_thread_resolution` sat in the floor and was NOT on the
|
|
216
|
+
* never-waivable list, so the same change that restored it to the floor "to
|
|
217
|
+
* remove an unauthorized reduction" opened a second, unauthenticated route to
|
|
218
|
+
* exactly that reduction. Deriving the set makes the two impossible to
|
|
219
|
+
* disagree.
|
|
220
|
+
*
|
|
221
|
+
* The invariant this expresses: the FLOOR is the hard tier and is unwaivable by
|
|
222
|
+
* construction; the expectation's remaining dimensions are baseline
|
|
223
|
+
* expectations a complete, owner-authorised, unexpired waiver may trade. A
|
|
224
|
+
* dimension cannot be both floored and waivable, which is what the drift had
|
|
225
|
+
* made possible.
|
|
226
|
+
*
|
|
227
|
+
* `minimum_required_contexts` is excluded because it is a COUNT rather than a
|
|
228
|
+
* dimension name — it has no matching key in the expectation for a waiver to
|
|
229
|
+
* name, so listing it would bound nothing.
|
|
230
|
+
*/
|
|
231
|
+
export const NEVER_WAIVABLE: readonly string[] = Object.keys(NON_NEGOTIABLE_FLOOR).filter(
|
|
232
|
+
(k) => k !== 'minimum_required_contexts',
|
|
233
|
+
);
|
|
234
|
+
|
|
235
|
+
export type AnchorStatus =
|
|
236
|
+
| 'compliant'
|
|
237
|
+
| 'compliant-with-accepted-risk'
|
|
238
|
+
| 'noncompliant'
|
|
239
|
+
| 'unverifiable';
|
|
240
|
+
|
|
241
|
+
/** Stable codes, so a test asserts the reason rather than the wording. */
|
|
242
|
+
export type AnchorCode =
|
|
243
|
+
| 'policy-missing'
|
|
244
|
+
| 'policy-unparseable'
|
|
245
|
+
| 'policy-missing-field'
|
|
246
|
+
| 'policy-unknown-field'
|
|
247
|
+
| 'policy-repository-mismatch'
|
|
248
|
+
| 'waiver-incomplete'
|
|
249
|
+
| 'waiver-expired'
|
|
250
|
+
| 'waiver-not-permitted'
|
|
251
|
+
| 'waiver-unused'
|
|
252
|
+
| 'policy-below-floor'
|
|
253
|
+
| 'rulesets-unreadable'
|
|
254
|
+
| 'default-branch-unknown'
|
|
255
|
+
| 'no-applicable-ruleset'
|
|
256
|
+
| 'thread-resolution-missing'
|
|
257
|
+
| 'deletion-not-blocked'
|
|
258
|
+
| 'non-fast-forward-not-blocked'
|
|
259
|
+
| 'status-checks-not-strict'
|
|
260
|
+
| 'required-context-missing'
|
|
261
|
+
| 'unconditional-bypass';
|
|
262
|
+
|
|
263
|
+
export interface AnchorFinding {
|
|
264
|
+
code: AnchorCode;
|
|
265
|
+
message: string;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
export interface AnchorReading {
|
|
269
|
+
status: AnchorStatus;
|
|
270
|
+
findings: AnchorFinding[];
|
|
271
|
+
/** Human-readable facts the verdict rests on, for the gate's output. */
|
|
272
|
+
evidence: string[];
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** The effective configuration, unioned across every applicable ruleset. */
|
|
276
|
+
export interface EffectiveProtection {
|
|
277
|
+
rulesetIds: number[];
|
|
278
|
+
approvingReviews: number;
|
|
279
|
+
requireLastPushApproval: boolean;
|
|
280
|
+
requireThreadResolution: boolean;
|
|
281
|
+
blocksDeletion: boolean;
|
|
282
|
+
blocksNonFastForward: boolean;
|
|
283
|
+
strictStatusChecks: boolean;
|
|
284
|
+
requiredContexts: string[];
|
|
285
|
+
unconditionalBypassActors: BypassActor[];
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const POLICY_FIELDS: readonly (keyof AnchorPolicy)[] = [
|
|
289
|
+
'enforcement',
|
|
290
|
+
'target',
|
|
291
|
+
'covers_default_branch',
|
|
292
|
+
'required_review_thread_resolution',
|
|
293
|
+
'block_deletion',
|
|
294
|
+
'block_non_fast_forward',
|
|
295
|
+
'strict_required_status_checks',
|
|
296
|
+
'required_status_check_contexts',
|
|
297
|
+
'allow_unconditional_bypass',
|
|
298
|
+
];
|
|
299
|
+
|
|
300
|
+
function isObject(v: unknown): v is Record<string, unknown> {
|
|
301
|
+
return v !== null && typeof v === 'object' && !Array.isArray(v);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
/**
|
|
305
|
+
* Read and validate the committed policy.
|
|
306
|
+
*
|
|
307
|
+
* Returns `null` for the policy on any problem, so a caller cannot accidentally
|
|
308
|
+
* evaluate against a half-read expectation. Missing and unparseable are distinct
|
|
309
|
+
* codes because they are distinct repairs.
|
|
310
|
+
*/
|
|
311
|
+
export function readAnchorPolicy(text: string | null): {
|
|
312
|
+
policy: AnchorPolicy | null;
|
|
313
|
+
findings: AnchorFinding[];
|
|
314
|
+
} {
|
|
315
|
+
if (text === null) {
|
|
316
|
+
return {
|
|
317
|
+
policy: null,
|
|
318
|
+
findings: [
|
|
319
|
+
{
|
|
320
|
+
code: 'policy-missing',
|
|
321
|
+
message:
|
|
322
|
+
'src/config/platform-anchor.json is absent, so the expected platform ' +
|
|
323
|
+
'configuration is unknown. This check fails closed rather than assuming one.',
|
|
324
|
+
},
|
|
325
|
+
],
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
let parsed: unknown;
|
|
329
|
+
try {
|
|
330
|
+
parsed = JSON.parse(text);
|
|
331
|
+
} catch (e) {
|
|
332
|
+
return {
|
|
333
|
+
policy: null,
|
|
334
|
+
findings: [
|
|
335
|
+
{
|
|
336
|
+
code: 'policy-unparseable',
|
|
337
|
+
message: `src/config/platform-anchor.json is not valid JSON: ${String(e)}`,
|
|
338
|
+
},
|
|
339
|
+
],
|
|
340
|
+
};
|
|
341
|
+
}
|
|
342
|
+
if (!isObject(parsed) || !isObject(parsed['required'])) {
|
|
343
|
+
return {
|
|
344
|
+
policy: null,
|
|
345
|
+
findings: [
|
|
346
|
+
{
|
|
347
|
+
code: 'policy-missing-field',
|
|
348
|
+
message: 'src/config/platform-anchor.json carries no `required` object.',
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
const req = parsed['required'];
|
|
354
|
+
const findings: AnchorFinding[] = [];
|
|
355
|
+
for (const field of POLICY_FIELDS) {
|
|
356
|
+
if (!(field in req)) {
|
|
357
|
+
findings.push({
|
|
358
|
+
code: 'policy-missing-field',
|
|
359
|
+
message: `src/config/platform-anchor.json \`required\` is missing \`${field}\`.`,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
// Unknown keys are refused, not ignored. A misspelled or stale key would
|
|
364
|
+
// otherwise sit in the expectation looking authoritative while the field it
|
|
365
|
+
// was meant to set falls back to a default nobody chose — and the council
|
|
366
|
+
// condition quoted above this file's floor says "reject unknown fields" in
|
|
367
|
+
// as many words, so ignoring them documented the code as doing the
|
|
368
|
+
// opposite of what it did.
|
|
369
|
+
for (const key of Object.keys(req)) {
|
|
370
|
+
if (!(POLICY_FIELDS as readonly string[]).includes(key)) {
|
|
371
|
+
findings.push({
|
|
372
|
+
code: 'policy-unknown-field',
|
|
373
|
+
message:
|
|
374
|
+
`src/config/platform-anchor.json \`required\` carries an unknown key ` +
|
|
375
|
+
`\`${key}\`. Every key must be one of: ${POLICY_FIELDS.join(', ')}. ` +
|
|
376
|
+
'Explanatory prose belongs in a sibling `*_note` key outside `required`.',
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
if (findings.length > 0) {
|
|
381
|
+
return { policy: null, findings };
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
const contexts = req['required_status_check_contexts'];
|
|
385
|
+
if (!Array.isArray(contexts) || contexts.some((c) => typeof c !== 'string')) {
|
|
386
|
+
return {
|
|
387
|
+
policy: null,
|
|
388
|
+
findings: [
|
|
389
|
+
{
|
|
390
|
+
code: 'policy-missing-field',
|
|
391
|
+
message:
|
|
392
|
+
'`required_status_check_contexts` must be an array of strings naming the ' +
|
|
393
|
+
'CI contexts that must be required checks.',
|
|
394
|
+
},
|
|
395
|
+
],
|
|
396
|
+
};
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const policy: AnchorPolicy = {
|
|
400
|
+
enforcement: String(req['enforcement']),
|
|
401
|
+
target: String(req['target']),
|
|
402
|
+
covers_default_branch: req['covers_default_branch'] === true,
|
|
403
|
+
required_review_thread_resolution: req['required_review_thread_resolution'] === true,
|
|
404
|
+
block_deletion: req['block_deletion'] === true,
|
|
405
|
+
block_non_fast_forward: req['block_non_fast_forward'] === true,
|
|
406
|
+
strict_required_status_checks: req['strict_required_status_checks'] === true,
|
|
407
|
+
required_status_check_contexts: contexts as string[],
|
|
408
|
+
allow_unconditional_bypass: req['allow_unconditional_bypass'] === true,
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const floor = enforceFloor(policy);
|
|
412
|
+
if (floor.length > 0) {
|
|
413
|
+
return { policy: null, findings: floor };
|
|
414
|
+
}
|
|
415
|
+
return { policy, findings: [] };
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
/** The expectation's supported schema version. Bump only with its reader. */
|
|
419
|
+
export const SUPPORTED_ANCHOR_SCHEMA = 2;
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* The two top-level fields that say WHICH expectation this is.
|
|
423
|
+
*
|
|
424
|
+
* Both were declared and read by nothing, which a blind review named: the gate
|
|
425
|
+
* could report `compliant` for a fork, or for whatever `--repo` was handed it,
|
|
426
|
+
* while quoting an expectation that names another repository. A verdict that
|
|
427
|
+
* does not know whose settings it measured is not a verdict.
|
|
428
|
+
*/
|
|
429
|
+
export function checkAnchorIdentity(text: string | null, repo: string): AnchorFinding[] {
|
|
430
|
+
if (text === null) {
|
|
431
|
+
return [];
|
|
432
|
+
}
|
|
433
|
+
let parsed: unknown;
|
|
434
|
+
try {
|
|
435
|
+
parsed = JSON.parse(text);
|
|
436
|
+
} catch {
|
|
437
|
+
return [];
|
|
438
|
+
}
|
|
439
|
+
if (!isObject(parsed)) {
|
|
440
|
+
return [];
|
|
441
|
+
}
|
|
442
|
+
const f: AnchorFinding[] = [];
|
|
443
|
+
const version = parsed['schema_version'];
|
|
444
|
+
if (version !== SUPPORTED_ANCHOR_SCHEMA) {
|
|
445
|
+
f.push({
|
|
446
|
+
code: 'policy-missing-field',
|
|
447
|
+
message:
|
|
448
|
+
`src/config/platform-anchor.json declares \`schema_version\` ` +
|
|
449
|
+
`${JSON.stringify(version)}; this reader supports ${SUPPORTED_ANCHOR_SCHEMA}. ` +
|
|
450
|
+
'A schema bump must land with the reader that understands it.',
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
const declared = parsed['repository'];
|
|
454
|
+
if (typeof declared === 'string' && declared !== repo) {
|
|
455
|
+
f.push({
|
|
456
|
+
code: 'policy-repository-mismatch',
|
|
457
|
+
message:
|
|
458
|
+
`src/config/platform-anchor.json states the expectation for \`${declared}\`, ` +
|
|
459
|
+
`but this run measured \`${repo}\`. A pass here would credit one repository's ` +
|
|
460
|
+
"settings to another's expectation — most likely a fork, or a --repo argument " +
|
|
461
|
+
'that does not match the checkout.',
|
|
462
|
+
});
|
|
463
|
+
}
|
|
464
|
+
return f;
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/**
|
|
468
|
+
* Reject a policy weaker than the floor.
|
|
469
|
+
*
|
|
470
|
+
* This is what stops the expectation file from being the weak link: the file is
|
|
471
|
+
* on the gated-surface list so an edit to it needs its own ratification, and
|
|
472
|
+
* even a ratified edit cannot go below these values without changing this code —
|
|
473
|
+
* which is itself a gated surface.
|
|
474
|
+
*/
|
|
475
|
+
export function enforceFloor(policy: AnchorPolicy): AnchorFinding[] {
|
|
476
|
+
const f: AnchorFinding[] = [];
|
|
477
|
+
const under = (what: string, got: unknown, floorValue: unknown): void => {
|
|
478
|
+
f.push({
|
|
479
|
+
code: 'policy-below-floor',
|
|
480
|
+
message:
|
|
481
|
+
`src/config/platform-anchor.json sets \`${what}\` to ${JSON.stringify(got)}, ` +
|
|
482
|
+
`below the non-negotiable floor ${JSON.stringify(floorValue)} in ` +
|
|
483
|
+
'src/scripts/_lib/platform_anchor.ts. A policy edit may not lower the floor.',
|
|
484
|
+
});
|
|
485
|
+
};
|
|
486
|
+
|
|
487
|
+
// The selectors first, because a weakened selector makes every threshold
|
|
488
|
+
// below it moot — it changes which rulesets are read rather than how they
|
|
489
|
+
// are judged, so a policy can neutralise the check without touching a
|
|
490
|
+
// single floored threshold.
|
|
491
|
+
if (policy.enforcement !== NON_NEGOTIABLE_FLOOR.enforcement) {
|
|
492
|
+
under('enforcement', policy.enforcement, NON_NEGOTIABLE_FLOOR.enforcement);
|
|
493
|
+
}
|
|
494
|
+
if (policy.target !== NON_NEGOTIABLE_FLOOR.target) {
|
|
495
|
+
under('target', policy.target, NON_NEGOTIABLE_FLOOR.target);
|
|
496
|
+
}
|
|
497
|
+
if (!policy.covers_default_branch) {
|
|
498
|
+
under('covers_default_branch', policy.covers_default_branch, true);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (!policy.required_review_thread_resolution) {
|
|
502
|
+
under('required_review_thread_resolution', false, true);
|
|
503
|
+
}
|
|
504
|
+
if (!policy.block_deletion) {
|
|
505
|
+
under('block_deletion', false, true);
|
|
506
|
+
}
|
|
507
|
+
if (!policy.block_non_fast_forward) {
|
|
508
|
+
under('block_non_fast_forward', false, true);
|
|
509
|
+
}
|
|
510
|
+
if (policy.allow_unconditional_bypass) {
|
|
511
|
+
under('allow_unconditional_bypass', true, false);
|
|
512
|
+
}
|
|
513
|
+
if (
|
|
514
|
+
policy.required_status_check_contexts.length < NON_NEGOTIABLE_FLOOR.minimum_required_contexts
|
|
515
|
+
) {
|
|
516
|
+
under(
|
|
517
|
+
'required_status_check_contexts.length',
|
|
518
|
+
policy.required_status_check_contexts.length,
|
|
519
|
+
NON_NEGOTIABLE_FLOOR.minimum_required_contexts,
|
|
520
|
+
);
|
|
521
|
+
}
|
|
522
|
+
return f;
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Match one ruleset ref pattern against a concrete ref.
|
|
527
|
+
*
|
|
528
|
+
* The forge treats these as fnmatch patterns, not literals, and comparing them
|
|
529
|
+
* with `===` was the one fail-OPEN direction in this evaluator: a blind review
|
|
530
|
+
* probed `exclude: ["refs/heads/m*"]` under `include: ["~ALL"]` and the equality
|
|
531
|
+
* form returned "covered" for `main`, crediting a ruleset that in fact excludes
|
|
532
|
+
* it. The mirror error also existed — `include: ["refs/heads/**"]` read as no
|
|
533
|
+
* match at all.
|
|
534
|
+
*
|
|
535
|
+
* `**` crosses `/`; `*` and `?` stay inside one segment. Everything else is
|
|
536
|
+
* escaped, so a pattern with regex metacharacters cannot widen itself.
|
|
537
|
+
*/
|
|
538
|
+
export function refPatternMatches(pattern: string, ref: string): boolean {
|
|
539
|
+
let re = '';
|
|
540
|
+
for (let i = 0; i < pattern.length; i += 1) {
|
|
541
|
+
const c = pattern[i] ?? '';
|
|
542
|
+
if (c === '*') {
|
|
543
|
+
if (pattern[i + 1] === '*') {
|
|
544
|
+
re += '.*';
|
|
545
|
+
i += 1;
|
|
546
|
+
} else {
|
|
547
|
+
re += '[^/]*';
|
|
548
|
+
}
|
|
549
|
+
} else if (c === '?') {
|
|
550
|
+
re += '[^/]';
|
|
551
|
+
} else {
|
|
552
|
+
re += c.replace(/[.+^${}()|[\]\\]/g, '\\$&');
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
try {
|
|
556
|
+
return new RegExp(`^${re}$`).test(ref);
|
|
557
|
+
} catch {
|
|
558
|
+
return false;
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
/**
|
|
563
|
+
* True when a ruleset's ref conditions reach the default branch.
|
|
564
|
+
*
|
|
565
|
+
* An empty `include` is NOT a match: a ruleset that names no ref covers no ref,
|
|
566
|
+
* and reading absence as "everything" would be the fail-open reading again.
|
|
567
|
+
*/
|
|
568
|
+
export function coversDefaultBranch(rs: RulesetDetail, defaultBranch: string): boolean {
|
|
569
|
+
const include = rs.conditions?.ref_name?.include ?? [];
|
|
570
|
+
const exclude = rs.conditions?.ref_name?.exclude ?? [];
|
|
571
|
+
const full = `refs/heads/${defaultBranch}`;
|
|
572
|
+
const hits = (p: string): boolean =>
|
|
573
|
+
p === '~DEFAULT_BRANCH' ||
|
|
574
|
+
p === '~ALL' ||
|
|
575
|
+
refPatternMatches(p, full) ||
|
|
576
|
+
refPatternMatches(p, defaultBranch);
|
|
577
|
+
if (exclude.some(hits)) {
|
|
578
|
+
return false;
|
|
579
|
+
}
|
|
580
|
+
return include.some(hits);
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
function ruleParams(rs: RulesetDetail, type: string): Record<string, unknown> | null {
|
|
584
|
+
for (const r of rs.rules ?? []) {
|
|
585
|
+
if (r.type === type) {
|
|
586
|
+
return isObject(r.parameters) ? r.parameters : {};
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
return null;
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
/**
|
|
593
|
+
* Union every applicable active ruleset into one effective configuration.
|
|
594
|
+
*
|
|
595
|
+
* Booleans OR, the approval count takes the maximum, contexts union: a second
|
|
596
|
+
* ruleset can only ever ADD protection, which is how GitHub evaluates them and
|
|
597
|
+
* is why reading one ruleset by id would understate the real protection as
|
|
598
|
+
* easily as it overstates it.
|
|
599
|
+
*/
|
|
600
|
+
export function effectiveProtection(
|
|
601
|
+
rulesets: readonly RulesetDetail[],
|
|
602
|
+
defaultBranch: string,
|
|
603
|
+
policy: AnchorPolicy,
|
|
604
|
+
): EffectiveProtection {
|
|
605
|
+
const eff: EffectiveProtection = {
|
|
606
|
+
rulesetIds: [],
|
|
607
|
+
approvingReviews: 0,
|
|
608
|
+
requireLastPushApproval: false,
|
|
609
|
+
requireThreadResolution: false,
|
|
610
|
+
blocksDeletion: false,
|
|
611
|
+
blocksNonFastForward: false,
|
|
612
|
+
strictStatusChecks: false,
|
|
613
|
+
requiredContexts: [],
|
|
614
|
+
unconditionalBypassActors: [],
|
|
615
|
+
};
|
|
616
|
+
for (const rs of rulesets) {
|
|
617
|
+
if (rs.enforcement !== policy.enforcement) {
|
|
618
|
+
continue;
|
|
619
|
+
}
|
|
620
|
+
if (rs.target !== policy.target) {
|
|
621
|
+
continue;
|
|
622
|
+
}
|
|
623
|
+
if (policy.covers_default_branch && !coversDefaultBranch(rs, defaultBranch)) {
|
|
624
|
+
continue;
|
|
625
|
+
}
|
|
626
|
+
eff.rulesetIds.push(rs.id ?? -1);
|
|
627
|
+
|
|
628
|
+
const pr = ruleParams(rs, 'pull_request');
|
|
629
|
+
if (pr !== null) {
|
|
630
|
+
const n = Number(pr['required_approving_review_count'] ?? 0);
|
|
631
|
+
if (Number.isFinite(n) && n > eff.approvingReviews) {
|
|
632
|
+
eff.approvingReviews = n;
|
|
633
|
+
}
|
|
634
|
+
eff.requireLastPushApproval ||= pr['require_last_push_approval'] === true;
|
|
635
|
+
eff.requireThreadResolution ||= pr['required_review_thread_resolution'] === true;
|
|
636
|
+
}
|
|
637
|
+
eff.blocksDeletion ||= ruleParams(rs, 'deletion') !== null;
|
|
638
|
+
eff.blocksNonFastForward ||= ruleParams(rs, 'non_fast_forward') !== null;
|
|
639
|
+
|
|
640
|
+
const sc = ruleParams(rs, 'required_status_checks');
|
|
641
|
+
if (sc !== null) {
|
|
642
|
+
eff.strictStatusChecks ||= sc['strict_required_status_checks_policy'] === true;
|
|
643
|
+
const list = sc['required_status_checks'];
|
|
644
|
+
if (Array.isArray(list)) {
|
|
645
|
+
for (const entry of list) {
|
|
646
|
+
if (isObject(entry) && typeof entry['context'] === 'string') {
|
|
647
|
+
if (!eff.requiredContexts.includes(entry['context'])) {
|
|
648
|
+
eff.requiredContexts.push(entry['context']);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
for (const a of rs.bypass_actors ?? []) {
|
|
655
|
+
if (a.bypass_mode === 'always') {
|
|
656
|
+
eff.unconditionalBypassActors.push(a);
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
return eff;
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
const WAIVER_FIELDS: readonly (keyof AcceptedRiskWaiver)[] = [
|
|
664
|
+
'dimension',
|
|
665
|
+
'baseline',
|
|
666
|
+
'accepted',
|
|
667
|
+
'failure_mode',
|
|
668
|
+
'cost_avoided',
|
|
669
|
+
'frequency_assumption',
|
|
670
|
+
'detection_and_repair',
|
|
671
|
+
'residual_protection',
|
|
672
|
+
'authority',
|
|
673
|
+
'decided',
|
|
674
|
+
'expires',
|
|
675
|
+
'review_triggers',
|
|
676
|
+
'id',
|
|
677
|
+
];
|
|
678
|
+
|
|
679
|
+
export interface WaiverReading {
|
|
680
|
+
/** Keyed by dimension. Only complete, permitted, unexpired waivers appear. */
|
|
681
|
+
honoured: Map<string, AcceptedRiskWaiver>;
|
|
682
|
+
findings: AnchorFinding[];
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
/** The only values `authority` may carry. A floor trade is the owner's alone. */
|
|
686
|
+
export const WAIVER_AUTHORITIES: readonly string[] = ['owner'];
|
|
687
|
+
|
|
688
|
+
/**
|
|
689
|
+
* Read the waivers, refusing every one that is not fully in order.
|
|
690
|
+
*
|
|
691
|
+
* A refused waiver is absent from `honoured`, so the dimension it meant to
|
|
692
|
+
* cover is judged normally — AND its refusal is a finding the caller must merge
|
|
693
|
+
* into the verdict. Both halves are required, and the second was missing:
|
|
694
|
+
* a blind review reproduced the inversion end to end. The refusals were printed
|
|
695
|
+
* and never reached the exit code, so an EXPIRED waiver over a dimension the
|
|
696
|
+
* forge happened to satisfy left the gate green, while a WELL-FORMED waiver for
|
|
697
|
+
* the same satisfied dimension reded via `waiver-unused`. Malformed was strictly
|
|
698
|
+
* better than correct — the exact thing this function's contract forbids, and
|
|
699
|
+
* reachable by an ordinary lapsed expiry rather than only by malice.
|
|
700
|
+
*
|
|
701
|
+
* `evaluateAnchor` now takes the whole reading rather than just the map, so the
|
|
702
|
+
* findings cannot be dropped at a call site again.
|
|
703
|
+
*/
|
|
704
|
+
export function readWaivers(text: string | null, now: Date): WaiverReading {
|
|
705
|
+
const honoured = new Map<string, AcceptedRiskWaiver>();
|
|
706
|
+
const findings: AnchorFinding[] = [];
|
|
707
|
+
if (text === null) {
|
|
708
|
+
return { honoured, findings };
|
|
709
|
+
}
|
|
710
|
+
let parsed: unknown;
|
|
711
|
+
try {
|
|
712
|
+
parsed = JSON.parse(text);
|
|
713
|
+
} catch {
|
|
714
|
+
return { honoured, findings };
|
|
715
|
+
}
|
|
716
|
+
if (!isObject(parsed) || parsed['accepted_risk_reductions'] === undefined) {
|
|
717
|
+
return { honoured, findings };
|
|
718
|
+
}
|
|
719
|
+
const list = parsed['accepted_risk_reductions'];
|
|
720
|
+
if (!Array.isArray(list)) {
|
|
721
|
+
findings.push({
|
|
722
|
+
code: 'waiver-incomplete',
|
|
723
|
+
message: '`accepted_risk_reductions` must be an array of waiver objects.',
|
|
724
|
+
});
|
|
725
|
+
return { honoured, findings };
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
for (const entry of list) {
|
|
729
|
+
if (!isObject(entry)) {
|
|
730
|
+
findings.push({
|
|
731
|
+
code: 'waiver-incomplete',
|
|
732
|
+
message: 'a waiver entry is not an object.',
|
|
733
|
+
});
|
|
734
|
+
continue;
|
|
735
|
+
}
|
|
736
|
+
const id = typeof entry['id'] === 'string' ? entry['id'] : '<no id>';
|
|
737
|
+
|
|
738
|
+
// Presence AND substance. Presence-only validation honoured an empty
|
|
739
|
+
// string and a null in every prose field, which is an undocumented
|
|
740
|
+
// deviation wearing thirteen keys — the blind review filed it as its
|
|
741
|
+
// own finding and it is the same defect class as the refusals not
|
|
742
|
+
// reaching the verdict: a check that reads a field without judging it.
|
|
743
|
+
const missing = WAIVER_FIELDS.filter((f) => {
|
|
744
|
+
if (!(f in entry)) {
|
|
745
|
+
return true;
|
|
746
|
+
}
|
|
747
|
+
const v = entry[f];
|
|
748
|
+
if (f === 'review_triggers') {
|
|
749
|
+
return !Array.isArray(v) || v.length === 0 || v.some((x) => String(x).trim() === '');
|
|
750
|
+
}
|
|
751
|
+
if (f === 'baseline' || f === 'accepted') {
|
|
752
|
+
return typeof v !== 'boolean';
|
|
753
|
+
}
|
|
754
|
+
return typeof v !== 'string' || v.trim() === '';
|
|
755
|
+
});
|
|
756
|
+
if (missing.length > 0) {
|
|
757
|
+
findings.push({
|
|
758
|
+
code: 'waiver-incomplete',
|
|
759
|
+
message:
|
|
760
|
+
`waiver \`${id}\` is missing or empty at ${missing.join(', ')}. An incomplete ` +
|
|
761
|
+
'waiver is an undocumented deviation, so it is refused rather than honoured ' +
|
|
762
|
+
'and the dimension it names is judged normally.',
|
|
763
|
+
});
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
if (!WAIVER_AUTHORITIES.includes(String(entry['authority']))) {
|
|
767
|
+
findings.push({
|
|
768
|
+
code: 'waiver-not-permitted',
|
|
769
|
+
message:
|
|
770
|
+
`waiver \`${id}\` claims authority \`${String(entry['authority'])}\`, which is ` +
|
|
771
|
+
`not one of ${WAIVER_AUTHORITIES.join(', ')}. Trading a baseline control is ` +
|
|
772
|
+
'the owner\'s decision; a waiver that names anything else — an agent, a ' +
|
|
773
|
+
'council, a session — is refused. The field was presence-checked and never ' +
|
|
774
|
+
'value-checked, so `agent-self-service` was honoured while four prose ' +
|
|
775
|
+
'surfaces rested on "only the owner may authorize a floor reduction".',
|
|
776
|
+
});
|
|
777
|
+
continue;
|
|
778
|
+
}
|
|
779
|
+
if (entry['baseline'] === entry['accepted']) {
|
|
780
|
+
findings.push({
|
|
781
|
+
code: 'waiver-incomplete',
|
|
782
|
+
message:
|
|
783
|
+
`waiver \`${id}\` records the same value for \`baseline\` and \`accepted\`, so ` +
|
|
784
|
+
'it waives nothing. Both were printed as record and neither was checked.',
|
|
785
|
+
});
|
|
786
|
+
continue;
|
|
787
|
+
}
|
|
788
|
+
const dimension = String(entry['dimension']);
|
|
789
|
+
if (!(POLICY_FIELDS as readonly string[]).includes(dimension)) {
|
|
790
|
+
findings.push({
|
|
791
|
+
code: 'waiver-not-permitted',
|
|
792
|
+
message:
|
|
793
|
+
`waiver \`${id}\` names \`${dimension}\`, which is not a dimension of the ` +
|
|
794
|
+
'expectation. A typo was previously honoured and then mis-diagnosed as an ' +
|
|
795
|
+
'unused waiver, which sends a reader looking for the wrong problem.',
|
|
796
|
+
});
|
|
797
|
+
continue;
|
|
798
|
+
}
|
|
799
|
+
if (honoured.has(dimension)) {
|
|
800
|
+
findings.push({
|
|
801
|
+
code: 'waiver-not-permitted',
|
|
802
|
+
message:
|
|
803
|
+
`waiver \`${id}\` is a second entry for \`${dimension}\`. Duplicates used to ` +
|
|
804
|
+
'resolve last-wins, so which record governed depended on file order.',
|
|
805
|
+
});
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
if (Number.isNaN(new Date(String(entry['decided'])).getTime())) {
|
|
809
|
+
findings.push({
|
|
810
|
+
code: 'waiver-incomplete',
|
|
811
|
+
message: `waiver \`${id}\` has an unparseable \`decided\`; it must be an ISO date.`,
|
|
812
|
+
});
|
|
813
|
+
continue;
|
|
814
|
+
}
|
|
815
|
+
if (NEVER_WAIVABLE.includes(dimension)) {
|
|
816
|
+
findings.push({
|
|
817
|
+
code: 'waiver-not-permitted',
|
|
818
|
+
message:
|
|
819
|
+
`waiver \`${id}\` names \`${dimension}\`, which no waiver may reach. A ruleset ` +
|
|
820
|
+
'that is not active, does not cover the branch, or lets an actor bypass it ' +
|
|
821
|
+
'unconditionally makes every other assertion here meaningless, and that is ' +
|
|
822
|
+
'not tradeable against an operational saving.',
|
|
823
|
+
});
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
const expires = new Date(String(entry['expires']));
|
|
827
|
+
if (Number.isNaN(expires.getTime())) {
|
|
828
|
+
findings.push({
|
|
829
|
+
code: 'waiver-incomplete',
|
|
830
|
+
message: `waiver \`${id}\` has an unparseable \`expires\`; it must be an ISO date.`,
|
|
831
|
+
});
|
|
832
|
+
continue;
|
|
833
|
+
}
|
|
834
|
+
if (expires.getTime() <= now.getTime()) {
|
|
835
|
+
findings.push({
|
|
836
|
+
code: 'waiver-expired',
|
|
837
|
+
message:
|
|
838
|
+
`waiver \`${id}\` expired on ${String(entry['expires'])}. Renewal is a fresh ` +
|
|
839
|
+
'decision with refreshed measurements rather than an extension — the premise ' +
|
|
840
|
+
'it rests on is a cost, and a cost is not a constant.',
|
|
841
|
+
});
|
|
842
|
+
continue;
|
|
843
|
+
}
|
|
844
|
+
honoured.set(dimension, entry as unknown as AcceptedRiskWaiver);
|
|
845
|
+
}
|
|
846
|
+
return { honoured, findings };
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
/**
|
|
850
|
+
* The verdict.
|
|
851
|
+
*
|
|
852
|
+
* `rulesets === null` means the caller could not read them — network, token
|
|
853
|
+
* scope, offline — and yields `unverifiable`, never `compliant`. That is the
|
|
854
|
+
* fail-closed direction both council seats required, and its blast radius is
|
|
855
|
+
* only ever a diff on the gated surface, because the caller runs this check
|
|
856
|
+
* exclusively when `requiresRatification` is true.
|
|
857
|
+
*
|
|
858
|
+
* A dimension covered by an honoured waiver does not become a finding; it is
|
|
859
|
+
* reported as a known accepted risk and the status becomes
|
|
860
|
+
* `compliant-with-accepted-risk`, which is neither a pass nor a failure. An
|
|
861
|
+
* unused waiver is itself reported: a waiver for a dimension the forge now
|
|
862
|
+
* satisfies is stale record, and stale records are how a reader learns to
|
|
863
|
+
* distrust the whole file.
|
|
864
|
+
*/
|
|
865
|
+
export function evaluateAnchor(
|
|
866
|
+
policy: AnchorPolicy,
|
|
867
|
+
rulesets: readonly RulesetDetail[] | null,
|
|
868
|
+
defaultBranch: string | null,
|
|
869
|
+
waiverReading: WaiverReading = { honoured: new Map(), findings: [] },
|
|
870
|
+
): AnchorReading {
|
|
871
|
+
// Seeded, not appended later. The refusals ARE part of the verdict, and a
|
|
872
|
+
// caller that received only the map could drop them — which is the shape
|
|
873
|
+
// that produced the inversion this parameter now prevents.
|
|
874
|
+
const findings: AnchorFinding[] = [...waiverReading.findings];
|
|
875
|
+
const waivers = waiverReading.honoured;
|
|
876
|
+
const evidence: string[] = [];
|
|
877
|
+
const accepted: string[] = [];
|
|
878
|
+
const used = new Set<string>();
|
|
879
|
+
|
|
880
|
+
/**
|
|
881
|
+
* Record a violated dimension as a finding, or as an accepted risk.
|
|
882
|
+
*
|
|
883
|
+
* The waiver check happens HERE rather than at the end, so a waived
|
|
884
|
+
* dimension never enters the findings list at all — filtering findings
|
|
885
|
+
* afterwards would leave the two states one refactor apart.
|
|
886
|
+
*/
|
|
887
|
+
const violated = (dimension: string, finding: AnchorFinding): void => {
|
|
888
|
+
const w = waivers.get(dimension);
|
|
889
|
+
if (w === undefined) {
|
|
890
|
+
findings.push(finding);
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
used.add(dimension);
|
|
894
|
+
accepted.push(
|
|
895
|
+
`${dimension}: baseline ${String(w.baseline)}, accepted ${String(w.accepted)} — ` +
|
|
896
|
+
`${w.id}, ${w.authority} ${w.decided}, expires ${w.expires}. ` +
|
|
897
|
+
`Residual: ${w.residual_protection}`,
|
|
898
|
+
);
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
if (rulesets === null) {
|
|
902
|
+
return {
|
|
903
|
+
status: 'unverifiable',
|
|
904
|
+
findings: [
|
|
905
|
+
...findings,
|
|
906
|
+
{
|
|
907
|
+
code: 'rulesets-unreadable',
|
|
908
|
+
message:
|
|
909
|
+
'the repository rulesets could not be read (network, credentials, token ' +
|
|
910
|
+
'scope, or an offline run). The platform anchor is UNVERIFIABLE, which is ' +
|
|
911
|
+
'a failure and not a pass: a control that succeeds when it cannot measure ' +
|
|
912
|
+
'is advisory.',
|
|
913
|
+
},
|
|
914
|
+
],
|
|
915
|
+
evidence,
|
|
916
|
+
};
|
|
917
|
+
}
|
|
918
|
+
if (defaultBranch === null) {
|
|
919
|
+
return {
|
|
920
|
+
status: 'unverifiable',
|
|
921
|
+
findings: [
|
|
922
|
+
...findings,
|
|
923
|
+
{
|
|
924
|
+
code: 'default-branch-unknown',
|
|
925
|
+
message:
|
|
926
|
+
'the default branch could not be resolved, so no ruleset can be matched ' +
|
|
927
|
+
'against it.',
|
|
928
|
+
},
|
|
929
|
+
],
|
|
930
|
+
evidence,
|
|
931
|
+
};
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
const eff = effectiveProtection(rulesets, defaultBranch, policy);
|
|
935
|
+
evidence.push(
|
|
936
|
+
`default branch: ${defaultBranch} · applicable active ${policy.target} ruleset(s): ` +
|
|
937
|
+
(eff.rulesetIds.length > 0 ? eff.rulesetIds.join(', ') : 'none'),
|
|
938
|
+
);
|
|
939
|
+
|
|
940
|
+
if (eff.rulesetIds.length === 0) {
|
|
941
|
+
findings.push({
|
|
942
|
+
code: 'no-applicable-ruleset',
|
|
943
|
+
message:
|
|
944
|
+
`no ruleset with target \`${policy.target}\` and enforcement ` +
|
|
945
|
+
`\`${policy.enforcement}\` covers \`${defaultBranch}\`. Note that classic branch ` +
|
|
946
|
+
'protection is a different API surface and is not read here; if this repository ' +
|
|
947
|
+
'moved back to it, src/config/platform-anchor.json needs correcting rather than ' +
|
|
948
|
+
'this result overriding.',
|
|
949
|
+
});
|
|
950
|
+
return { status: 'noncompliant', findings, evidence };
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
// Observed and reported, never judged. The two approval dimensions left the
|
|
954
|
+
// enforced set with the trust-model decision above, and deleting the
|
|
955
|
+
// MEASUREMENT with the requirement would have been the wrong half to drop:
|
|
956
|
+
// a reader still wants to see what the forge actually says, and a number
|
|
957
|
+
// that is reported without being a threshold cannot quietly become one.
|
|
958
|
+
evidence.push(
|
|
959
|
+
`approving reviews required: ${eff.approvingReviews} · last-push approval: ` +
|
|
960
|
+
`${eff.requireLastPushApproval} — observed, not required here (see NON_NEGOTIABLE_FLOOR)`,
|
|
961
|
+
);
|
|
962
|
+
if (policy.required_review_thread_resolution && !eff.requireThreadResolution) {
|
|
963
|
+
violated('required_review_thread_resolution', {
|
|
964
|
+
code: 'thread-resolution-missing',
|
|
965
|
+
message: '`required_review_thread_resolution` is off, so an open objection cannot block a merge.',
|
|
966
|
+
});
|
|
967
|
+
}
|
|
968
|
+
if (policy.block_deletion && !eff.blocksDeletion) {
|
|
969
|
+
violated('block_deletion', {
|
|
970
|
+
code: 'deletion-not-blocked',
|
|
971
|
+
message: 'the default branch is not protected against deletion.',
|
|
972
|
+
});
|
|
973
|
+
}
|
|
974
|
+
if (policy.block_non_fast_forward && !eff.blocksNonFastForward) {
|
|
975
|
+
violated('block_non_fast_forward', {
|
|
976
|
+
code: 'non-fast-forward-not-blocked',
|
|
977
|
+
message: 'the default branch is not protected against force-pushes (non-fast-forward).',
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
if (policy.strict_required_status_checks && !eff.strictStatusChecks) {
|
|
981
|
+
violated('strict_required_status_checks', {
|
|
982
|
+
code: 'status-checks-not-strict',
|
|
983
|
+
message:
|
|
984
|
+
'`strict_required_status_checks_policy` is off, so a branch may merge on checks ' +
|
|
985
|
+
'that passed against a base it has not caught up with. The staleness is not ' +
|
|
986
|
+
'bounded to one commit: any number of merges may have landed since, so two ' +
|
|
987
|
+
'independently green branches can compose into a broken base.',
|
|
988
|
+
});
|
|
989
|
+
}
|
|
990
|
+
evidence.push(
|
|
991
|
+
`required status check contexts: ` +
|
|
992
|
+
(eff.requiredContexts.length > 0 ? eff.requiredContexts.join(' · ') : 'none'),
|
|
993
|
+
);
|
|
994
|
+
for (const want of policy.required_status_check_contexts) {
|
|
995
|
+
if (!eff.requiredContexts.includes(want)) {
|
|
996
|
+
findings.push({
|
|
997
|
+
code: 'required-context-missing',
|
|
998
|
+
message:
|
|
999
|
+
`\`${want}\` is not a required status check. That context is the job which ` +
|
|
1000
|
+
'carries the ratification gate, so without it a branch can merge without the ' +
|
|
1001
|
+
'gate having run at all.',
|
|
1002
|
+
});
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
if (!policy.allow_unconditional_bypass && eff.unconditionalBypassActors.length > 0) {
|
|
1006
|
+
const who = eff.unconditionalBypassActors
|
|
1007
|
+
.map((a) => `${a.actor_type ?? '?'}#${a.actor_id ?? '?'}`)
|
|
1008
|
+
.join(', ');
|
|
1009
|
+
evidence.push(`unconditional bypass actors: ${who}`);
|
|
1010
|
+
findings.push({
|
|
1011
|
+
code: 'unconditional-bypass',
|
|
1012
|
+
message:
|
|
1013
|
+
`${who} may bypass every rule above unconditionally (\`bypass_mode: always\`), so ` +
|
|
1014
|
+
'each rule is advisory for that actor. The owner ruled on 2026-09-10 that ' +
|
|
1015
|
+
'administrators are the root of trust here, and that ruling REMOVED the approval ' +
|
|
1016
|
+
'dimensions rather than this one: an unconditional bypass actor is still a ' +
|
|
1017
|
+
'failure and no waiver may reach it. See `owner_ruling_2026_09_10` in ' +
|
|
1018
|
+
'src/config/platform-anchor.json.',
|
|
1019
|
+
});
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
// A waiver nobody needed is stale record, and stale records teach a reader
|
|
1023
|
+
// to distrust the file they sit in. It IS a finding and therefore reds —
|
|
1024
|
+
// the comment here used to say "deliberately NOT a failure" while the code
|
|
1025
|
+
// returned noncompliant, which a blind review filed as its own
|
|
1026
|
+
// contradiction. Kept as a failure and the comment corrected to match: the
|
|
1027
|
+
// repair is deleting one stale entry, and leaving it green would let the
|
|
1028
|
+
// file drift out of step with the forge unnoticed.
|
|
1029
|
+
for (const [dimension, w] of waivers) {
|
|
1030
|
+
if (!used.has(dimension)) {
|
|
1031
|
+
findings.push({
|
|
1032
|
+
code: 'waiver-unused',
|
|
1033
|
+
message:
|
|
1034
|
+
`waiver \`${w.id}\` covers \`${dimension}\`, which the forge now satisfies. ` +
|
|
1035
|
+
'The waiver is stale and should be removed — the risk it accepted is not ' +
|
|
1036
|
+
'being taken.',
|
|
1037
|
+
});
|
|
1038
|
+
}
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
for (const a of accepted) {
|
|
1042
|
+
evidence.push(`ACCEPTED RISK — ${a}`);
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
if (findings.length > 0) {
|
|
1046
|
+
return { status: 'noncompliant', findings, evidence };
|
|
1047
|
+
}
|
|
1048
|
+
return {
|
|
1049
|
+
status: accepted.length > 0 ? 'compliant-with-accepted-risk' : 'compliant',
|
|
1050
|
+
findings,
|
|
1051
|
+
evidence,
|
|
1052
|
+
};
|
|
1053
|
+
}
|