@event4u/agent-config 16.0.0 → 16.2.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 +289 -140
- package/CONTRIBUTING.md +9 -0
- package/README.md +2 -2
- package/dist/agent-src/commands/analyze/inbox.md +26 -0
- package/dist/agent-src/commands/analyze/roadmap-repos.md +27 -0
- package/dist/agent-src/commands/challenge-me/closure.md +178 -0
- package/dist/agent-src/commands/challenge-me.md +9 -2
- package/dist/agent-src/commands/council/default.md +25 -5
- package/dist/agent-src/commands/feature/plan.md +28 -1
- package/dist/agent-src/commands/feature/roadmap.md +28 -1
- package/dist/agent-src/commands/implement-ticket.md +27 -0
- package/dist/agent-src/commands/jira-ticket.md +27 -0
- package/dist/agent-src/commands/pr/create.md +29 -0
- package/dist/agent-src/commands/pr/merge.md +60 -9
- package/dist/agent-src/commands/roadmap/ai-council.md +7 -1
- package/dist/agent-src/commands/roadmap/create.md +74 -27
- package/dist/agent-src/commands/roadmap/materialize.md +27 -0
- package/dist/agent-src/commands/roadmap/process-full.md +123 -18
- package/dist/agent-src/commands/team.md +22 -0
- package/dist/agent-src/commands/work.md +4 -0
- package/dist/agent-src/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +47 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +19 -23
- package/dist/agent-src/contexts/execution/decision-closure-execution.md +93 -0
- package/dist/agent-src/contexts/execution/fix-loop-ladder.md +108 -0
- package/dist/agent-src/contexts/execution/plan-confidence-gate.md +4 -2
- package/dist/agent-src/contexts/execution/roadmap-process-loop.md +75 -6
- package/dist/agent-src/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/dist/agent-src/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/dist/agent-src/guidelines/design-fidelity-routing.md +29 -0
- package/dist/agent-src/rules/autonomous-execution.md +15 -8
- package/dist/agent-src/rules/delegation-policy.md +23 -45
- package/dist/agent-src/rules/design-fidelity.md +32 -37
- package/dist/agent-src/rules/evaluator-independence.md +43 -49
- package/dist/agent-src/rules/test-first.md +41 -0
- package/dist/agent-src/rules/ui-audit-gate.md +1 -2
- package/dist/agent-src/skills/accessibility-auditor/data/aria-patterns.csv +1 -1
- package/dist/agent-src/skills/accessibility-auditor/data/manifest.json +3 -3
- package/dist/agent-src/skills/ai-council/SKILL.md +11 -3
- package/dist/agent-src/skills/api-design/data/manifest.json +3 -3
- package/dist/agent-src/skills/database/data/manifest.json +4 -4
- package/dist/agent-src/skills/database/data/query-tuning.csv +3 -3
- package/dist/agent-src/skills/design-review/SKILL.md +80 -4
- package/dist/agent-src/skills/design-review/references/verification-automation.md +32 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +30 -5
- package/dist/agent-src/skills/existing-ui-audit/references/component-taxonomy-detection.md +56 -0
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- package/dist/agent-src/skills/markitdown/SKILL.md +4 -0
- package/dist/agent-src/skills/playwright-testing/SKILL.md +30 -6
- package/dist/agent-src/skills/roadmap-writing/SKILL.md +1 -1
- package/dist/agent-src/skills/storybook-workshop/SKILL.md +42 -3
- package/dist/agent-src/skills/threat-modeling/SKILL.md +9 -4
- package/dist/agent-src/skills/threat-modeling/data/manifest.json +4 -4
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +23 -0
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +37 -3
- package/dist/agent-src/templates/agent-settings.md +1 -1
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +218 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/dist/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/dist/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/dist/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/dist/cli/python/knowledge_ingest.js +1 -1
- package/dist/cli/python/workspace_drive.js +1 -1
- package/dist/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- package/dist/cli-delegate/{chunk-S3VFXMTM.js → chunk-4XFP2Y3E.js} +1 -1
- package/dist/cli-delegate/chunk-5R7HOLAF.js +99 -0
- package/dist/cli-delegate/{chunk-IVBPRCQO.js → chunk-6KFEC3G7.js} +1 -1
- package/dist/cli-delegate/{chunk-2TAQ2K4V.js → chunk-6KM6ORRY.js} +55 -386
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-7XGBX6ZH.js} +973 -555
- package/dist/cli-delegate/{chunk-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-FP36SWJD.js} +587 -141
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-IGD6ERX6.js} +62 -7
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-JNMVLWLR.js} +99 -14
- package/dist/cli-delegate/{chunk-H32DGUJH.js → chunk-P6ZHP5W2.js} +1 -1
- package/dist/cli-delegate/{chunk-57PN4GYJ.js → chunk-RGNIWTEA.js} +1 -1
- package/dist/cli-delegate/chunk-ROYYTEZU.js +39 -0
- package/dist/cli-delegate/{chunk-2DK2VJWG.js → chunk-WGXTVKCQ.js} +20 -6
- package/dist/cli-delegate/chunk-YDPILCOL.js +152 -0
- package/dist/cli-delegate/{chunk-QJWAUMJW.js → chunk-YJ72UIAK.js} +730 -15
- 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 +985 -831
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +2 -2
- package/dist/cli-delegate/cmd_export.js +9 -7
- package/dist/cli-delegate/cmd_fleet.js +4 -2
- package/dist/cli-delegate/cmd_handoff.js +7 -7
- 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 +2 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- 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 +4 -4
- package/dist/cli-delegate/cmd_settings_get.js +28 -2
- package/dist/cli-delegate/cmd_settings_migrate.js +9 -7
- package/dist/cli-delegate/cmd_settings_set.js +1 -1
- package/dist/cli-delegate/cmd_sync.js +10 -8
- package/dist/cli-delegate/cmd_uninstall.js +16 -13
- 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 +12 -10
- package/dist/cli-delegate/cmd_versions.js +1 -1
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +90 -43
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +4 -4
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +10 -8
- package/dist/discovery/trust-report.md +3 -3
- package/dist/discovery/workspaces.json +8 -6
- package/dist/hooks/dispatch.js +7682 -2772
- package/dist/hooks/dispatch.sha256 +1 -0
- package/dist/install/claudeRuleRewrite.js +14 -3
- package/dist/install/conflict.js +60 -10
- package/dist/install/conflictTracker.js +218 -0
- package/dist/install/fsPrimitives.js +54 -0
- package/dist/install/install.mjs +2200 -1559
- package/dist/install/manifestFiles.js +259 -0
- package/dist/install/preflight.js +14 -2
- package/dist/install/preserve.js +157 -0
- package/dist/install/recordedOwnership.js +132 -0
- package/dist/install/txlog.js +11 -2
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +2 -2
- package/dist/mcp/server.mjs +3066 -1470
- package/dist/router.json +1 -1
- package/dist/scripts/_lib/agent_settings.js +3 -2
- package/dist/scripts/_lib/fs_atomic.js +167 -0
- package/dist/scripts/_lib/installed_tools.js +460 -0
- package/dist/scripts/_lib/lean_projection_mode.js +3 -2
- package/dist/scripts/_lib/settings_renamed_keys.js +78 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +40 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/index-Cwq4d0hX.js +23 -0
- package/dist/ui/assets/index-Cyjy8bWO.css +1 -0
- package/dist/ui/index.html +2 -2
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +28 -7
- package/docs/guidelines/agent-infra/active-remediation-mechanics.md +57 -0
- package/docs/guidelines/agent-infra/evaluator-independence-mechanics.md +102 -0
- package/docs/guidelines/design-fidelity-routing.md +29 -0
- package/package.json +4 -4
- package/src/agent-src/templates/scripts/work_engine/directives/backend/_self_fix.ts +62 -2
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +1 -0
- package/src/agent-src/templates/scripts/work_engine/directives/ui/app_spec.ts +10 -4
- package/src/agent-src/templates/scripts/work_engine/directives/ui/apply.ts +260 -21
- package/src/agent-src/templates/scripts/work_engine/directives/ui/audit.ts +22 -3
- package/src/agent-src/templates/scripts/work_engine/directives/ui/scaffold.ts +6 -1
- package/src/agent-src/templates/scripts/work_engine/state.ts +57 -7
- package/src/agent-src/templates/scripts/work_engine/taxonomy/detect.ts +500 -0
- package/src/agent-src/templates/scripts/work_engine/taxonomy/place.ts +183 -0
- package/src/config/agent-settings.template.yml +168 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +36 -5
- package/src/config/continuity-surface.json +23 -2
- package/src/config/gate-coverage.yml +377 -13
- package/src/config/gate-violation-baselines.json +10 -6
- package/src/config/hook-latency-budget.json +21 -0
- package/src/config/hook-token-budget.json +4 -2
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/metric-registry.yml +114 -0
- package/src/config/placeholder-drift-budget.json +10 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +33 -5
- package/src/config/rule-enforcement-baseline.json +0 -1
- package/src/install/claudeRuleRewrite.ts +16 -2
- package/src/install/conflict.ts +65 -9
- package/src/install/conflictTracker.ts +299 -0
- package/src/install/fsPrimitives.ts +54 -0
- package/src/install/manifestFiles.ts +312 -0
- package/src/install/preflight.ts +16 -2
- package/src/install/preserve.ts +205 -0
- package/src/install/recordedOwnership.ts +149 -0
- package/src/install/txlog.ts +11 -2
- package/src/install/types.ts +19 -4
- package/src/scripts/_cli/cmd_conformance.ts +88 -15
- package/src/scripts/_cli/cmd_doctor.ts +7 -20
- package/src/scripts/_cli/cmd_route_explain.ts +9 -2
- package/src/scripts/_cli/cmd_settings_get.ts +44 -0
- package/src/scripts/_cli/doctor_execution.ts +173 -0
- package/src/scripts/_cli/doctor_network_posture.ts +226 -0
- package/src/scripts/_lib/activation_payload.ts +247 -0
- package/src/scripts/_lib/agent_settings.ts +4 -2
- package/src/scripts/_lib/authority_path.ts +102 -0
- package/src/scripts/_lib/body_portable.ts +102 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/cc_transcript.ts +132 -1
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- package/src/scripts/_lib/concern_sla_window.ts +200 -0
- package/src/scripts/_lib/continuation_ladder.ts +149 -2
- package/src/scripts/_lib/council_transport.ts +129 -0
- package/src/scripts/_lib/delivery_ready.ts +112 -0
- package/src/scripts/_lib/forge_protection.ts +381 -0
- package/src/scripts/_lib/forge_reader.ts +558 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/host_env_write.ts +111 -0
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/llm_proposer_transport.ts +14 -4
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/mission_record.ts +190 -0
- package/src/scripts/_lib/obligation_frequency.ts +217 -0
- package/src/scripts/_lib/obligations.ts +513 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/probe_inputs.ts +271 -0
- package/src/scripts/_lib/read_surface_scan.ts +322 -0
- package/src/scripts/_lib/release_highlights.ts +201 -11
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/retrieval_sanitize.ts +77 -16
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/shell_write_shapes.ts +108 -0
- package/src/scripts/_lib/structural_hiding.ts +622 -0
- package/src/scripts/_lib/test_delta.ts +166 -0
- package/src/scripts/_lib/test_provenance.ts +171 -0
- package/src/scripts/_lib/turn_end_refusals.ts +525 -4
- package/src/scripts/_lib/turn_end_transcript.ts +216 -0
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -0
- package/src/scripts/_lib/verification_command.ts +324 -0
- package/src/scripts/_lib/verification_evidence.ts +647 -0
- package/src/scripts/_lib/verify_clause.ts +280 -0
- package/src/scripts/ai_council/config.ts +8 -11
- package/src/scripts/ai_council/fallback_config.ts +5 -3
- package/src/scripts/ai_council/ownership_classes.ts +118 -0
- package/src/scripts/ai_team/team_dispatch.ts +4 -0
- package/src/scripts/ask_block_census.ts +285 -15
- package/src/scripts/before_complete_hook.ts +340 -23
- package/src/scripts/bench_hook_latency.ts +412 -5
- package/src/scripts/cache_realization_report.ts +61 -1
- package/src/scripts/check_argument_hint.ts +287 -0
- package/src/scripts/check_claims.ts +125 -1
- package/src/scripts/check_enforcement_coverage.ts +38 -9
- package/src/scripts/check_enforcement_matrix.ts +866 -0
- package/src/scripts/check_finding_dispositions.ts +22 -3
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +1025 -0
- package/src/scripts/check_host_docs_digest.ts +786 -0
- package/src/scripts/check_host_format_column.ts +559 -0
- package/src/scripts/check_input_references.ts +228 -0
- package/src/scripts/check_inputs_parity.ts +235 -0
- package/src/scripts/check_kernel_edit_ratified.ts +47 -5
- package/src/scripts/check_kill_switch_table.ts +294 -0
- package/src/scripts/check_placeholder_drift.ts +221 -0
- package/src/scripts/check_preamble_payload_budget.ts +75 -2
- package/src/scripts/check_read_surface_coverage.ts +515 -0
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +568 -0
- package/src/scripts/check_routing_coverage.ts +378 -11
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_static_parity.ts +17 -0
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/ci_settle.ts +145 -19
- package/src/scripts/closure_scan.ts +447 -0
- package/src/scripts/compile_hook_manifest.ts +102 -18
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/external_sources_denylist.json +29 -4
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -11
- package/src/scripts/hooks/block_config_weakening.ts +229 -4
- package/src/scripts/hooks/block_kernel_rule_writes.ts +9 -78
- package/src/scripts/hooks/block_plumbing_writes.ts +314 -0
- package/src/scripts/hooks/bundle_integrity.ts +291 -0
- package/src/scripts/hooks/chain_nudge_hook.ts +1 -2
- package/src/scripts/hooks/code_graph_context_hook.ts +1 -2
- package/src/scripts/hooks/comment_discipline_hook.ts +1 -3
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/concern_timings.ts +94 -0
- package/src/scripts/hooks/delegation_nudge_hook.ts +1 -2
- package/src/scripts/hooks/design_pass_hook.ts +221 -5
- package/src/scripts/hooks/design_slop_hook.ts +1 -2
- package/src/scripts/hooks/dispatch_hook.ts +25 -19
- package/src/scripts/hooks/edit_shape_hook.ts +1 -2
- package/src/scripts/hooks/end_review_nudge_hook.ts +1 -1
- package/src/scripts/hooks/evidence_independence.ts +97 -2
- package/src/scripts/hooks/exit_codes.ts +128 -0
- package/src/scripts/hooks/host_lowering.json +1 -0
- package/src/scripts/hooks/host_lowering.ts +128 -3
- package/src/scripts/hooks/host_lowering.yaml +262 -46
- package/src/scripts/hooks/host_semantics.ts +30 -2
- package/src/scripts/hooks/injection_budget.ts +9 -2
- package/src/scripts/hooks/interruption_ledger_hook.ts +1 -2
- package/src/scripts/hooks/journal_record_hook.ts +1 -2
- package/src/scripts/hooks/memory_recall_hook.ts +539 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +328 -0
- package/src/scripts/hooks/one_question_per_ask_hook.ts +25 -4
- package/src/scripts/hooks/orchestration_record_hook.ts +1 -2
- package/src/scripts/hooks/permission_gate.ts +1 -2
- package/src/scripts/hooks/push_settle_hook.ts +1 -3
- package/src/scripts/hooks/replay_hook.ts +2 -1
- package/src/scripts/hooks/reread_guard_hook.ts +1 -2
- package/src/scripts/hooks/review_baseline_hook.ts +1 -2
- package/src/scripts/hooks/rtk_wrap_hook.ts +1 -2
- package/src/scripts/hooks/rule_inject_hook.ts +55 -4
- package/src/scripts/hooks/run_continuation_hook.ts +30 -35
- package/src/scripts/hooks/skill_route_hook.ts +1 -2
- package/src/scripts/hooks/source_first_gate_hook.ts +1 -2
- package/src/scripts/hooks/spawn_guard_shadow_hook.ts +1 -2
- package/src/scripts/hooks/subagent_ledger_hook.ts +1 -2
- package/src/scripts/hooks/suggestion_capture_hook.ts +1 -1
- package/src/scripts/hooks/table_fingerprint.ts +49 -0
- package/src/scripts/hooks/telemetry_flush_hook.ts +1 -2
- package/src/scripts/hooks/telemetry_usage_hook.ts +1 -2
- package/src/scripts/hooks/tool_result_bytes_hook.ts +93 -9
- package/src/scripts/hooks/turn_end_gate_hook.ts +337 -324
- package/src/scripts/hooks/ui_route_nudge_hook.ts +1 -2
- package/src/scripts/hooks_doctor.ts +85 -0
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +109 -210
- package/src/scripts/janitor.ts +7 -0
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_deny_text.ts +419 -0
- package/src/scripts/lint_exit_codes.ts +262 -0
- package/src/scripts/lint_hook_manifest.ts +66 -0
- package/src/scripts/lint_major_migration_sections.ts +450 -0
- package/src/scripts/lint_plan_risk_register.ts +47 -3
- package/src/scripts/lint_roadmap_blockers.ts +139 -12
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/lint_skill_descriptions.ts +1 -1
- package/src/scripts/lint_skill_scripts_readonly.ts +1 -1
- package/src/scripts/mcp_server/prompts.ts +142 -1
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_skill_ranker_baseline.ts +305 -51
- package/src/scripts/measure_turn_end_gate.ts +193 -0
- package/src/scripts/memory_lookup.ts +1 -1
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/prepack-check.mjs +53 -0
- package/src/scripts/release.ts +14 -98
- package/src/scripts/release_drill.ts +267 -1
- package/src/scripts/release_publication.ts +64 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_invocation_surface.ts +383 -0
- package/src/scripts/report_obligation_carriers.ts +699 -0
- package/src/scripts/report_obligation_writer_split.ts +236 -0
- package/src/scripts/report_skill_menu_census.ts +208 -0
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/roadmap_verify_share.ts +213 -0
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +48 -0
- package/src/scripts/schemas/review-findings.schema.json +4 -0
- package/src/scripts/schemas/skill.schema.json +44 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/self_review_gate.ts +359 -10
- package/src/scripts/semantic_parity_census.ts +314 -0
- package/src/scripts/session_eol_report.ts +54 -1
- package/src/scripts/sweep_dead_scan_roots.ts +1 -1
- package/src/scripts/trigger_eval_grandfather.json +0 -3
- package/src/scripts/trigger_eval_rotation.ts +122 -27
- package/src/scripts/ui_conformance_probe.ts +774 -0
- package/src/scripts/update_prices.ts +85 -3
- package/src/scripts/write_bundle_digest.mjs +74 -0
- package/src/server/schemas/settings.ts +62 -7
- package/src/shared/missionExecution.ts +126 -0
- package/src/templates/consumer-settings/ONBOARDING.md +1 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
- package/dist/ui/assets/index-D0jaCRPM.js +0 -29
- package/dist/ui/assets/index-jSfMW8_t.css +0 -1
- /package/dist/cli-delegate/{chunk-YUM5KURG.js → chunk-7RQP4UDH.js} +0 -0
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reading a Claude JSONL transcript tail, for the turn-end gate's detectors.
|
|
3
|
+
*
|
|
4
|
+
* Extracted from `hooks/turn_end_gate_hook.ts` for the reason its own detector
|
|
5
|
+
* E carries one line above its import: `_lib` is where this prose costs no
|
|
6
|
+
* ratchet debt. The gate reached 1,593 lines against a 1,500-line source
|
|
7
|
+
* budget when the shadow read landed, and `check_source_size_budget` only
|
|
8
|
+
* turns one way — so the choice was to extract or to delete comments that
|
|
9
|
+
* record why the reader is shaped as it is. This module is the extraction.
|
|
10
|
+
*
|
|
11
|
+
* Nothing about the behaviour moves: every function below is byte-identical to
|
|
12
|
+
* the version that lived in the gate, and the gate re-exports all three public
|
|
13
|
+
* names so no importer changes. The two guards this reader depends on stay
|
|
14
|
+
* where they are — `isSafeTranscriptPath` is the security-relevant
|
|
15
|
+
* home-confinement predicate and `isSyntheticPrompt` is the ordinal filter, and
|
|
16
|
+
* relocating either as a side effect of a size budget is exactly the drive-by
|
|
17
|
+
* this tree's diff discipline forbids.
|
|
18
|
+
*/
|
|
19
|
+
import * as fs from 'node:fs';
|
|
20
|
+
|
|
21
|
+
import { isSyntheticPrompt } from '../language_mirror_hook.js';
|
|
22
|
+
import { isSafeTranscriptPath } from '../hooks/end_review_nudge_hook.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* One tool call the assistant made, reduced to what a detector can reason
|
|
26
|
+
* about: the tool's name, the shell command when it is a shell call, and the
|
|
27
|
+
* target path when it is a file write.
|
|
28
|
+
*
|
|
29
|
+
* Nothing else is kept. A tool input can hold a whole file body, and this
|
|
30
|
+
* struct is what a refusal quotes back — so it is shaped to be INCAPABLE of
|
|
31
|
+
* carrying one, the same PII-exclusion-by-construction discipline
|
|
32
|
+
* `domain-safety-pii` § Surface 2 asks for in a log line.
|
|
33
|
+
*/
|
|
34
|
+
export interface ToolCall {
|
|
35
|
+
name: string;
|
|
36
|
+
/** `Bash` only — the command line, so "did anything verify" is answerable. */
|
|
37
|
+
command?: string;
|
|
38
|
+
/** Edit/Write only — the file the turn changed, used as the evidence span. */
|
|
39
|
+
path?: string;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface TranscriptTail {
|
|
43
|
+
lastAssistant: string;
|
|
44
|
+
/**
|
|
45
|
+
* Every assistant text of the CURRENT turn, in order — reset at each genuine
|
|
46
|
+
* user prompt exactly like `toolCalls`, and for the same reason: a question
|
|
47
|
+
* asked three turns ago was answered, a question asked earlier in THIS turn
|
|
48
|
+
* was not. Detector E reads it; the other four read `lastAssistant`, which is
|
|
49
|
+
* always the last element when this is non-empty.
|
|
50
|
+
*
|
|
51
|
+
* A tool-only assistant entry contributes NOTHING here. `_messageText`
|
|
52
|
+
* returns null for it and the collector skips it, so the ordinary shape of a
|
|
53
|
+
* working turn — prose, tool call, prose — produces two elements and not
|
|
54
|
+
* three. That is the whole false-positive surface of detector E, and it is
|
|
55
|
+
* closed here rather than in the detector.
|
|
56
|
+
*/
|
|
57
|
+
assistantTurnTexts: string[];
|
|
58
|
+
/**
|
|
59
|
+
* How many GENUINE user prompts the transcript carries — harness-injected
|
|
60
|
+
* user-role entries excluded via `isSyntheticPrompt`. This is the turn's
|
|
61
|
+
* identity; see `alreadyRefusedTurn` for why the prompt's text is not.
|
|
62
|
+
*/
|
|
63
|
+
turnOrdinal: number;
|
|
64
|
+
/**
|
|
65
|
+
* The tool calls of the CURRENT turn, in order — reset at every genuine user
|
|
66
|
+
* prompt, so a verification from three turns ago cannot vouch for an edit
|
|
67
|
+
* made now. `_messageText` keeps only `type === 'text'` blocks, which is why
|
|
68
|
+
* this needed its own extraction rather than a reading of `lastAssistant`:
|
|
69
|
+
* tool activity is not in the prose at all.
|
|
70
|
+
*/
|
|
71
|
+
toolCalls: ToolCall[];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* The last assistant text, plus the turn ordinal, from a Claude JSONL
|
|
76
|
+
* transcript. Mirrors `chat_history._extract_claude_transcript_response` for
|
|
77
|
+
* the assistant half.
|
|
78
|
+
*
|
|
79
|
+
* `isSyntheticPrompt` is applied to every user-role entry — the same filter
|
|
80
|
+
* `language_mirror_hook` uses, and for the same reason it was added there
|
|
81
|
+
* (round-5 § 6.5): a background-task notification and a `<system-reminder>`
|
|
82
|
+
* both occupy the user role without being chat messages. Counting them would
|
|
83
|
+
* move the turn ordinal mid-turn, which is R2 finding 3.
|
|
84
|
+
*/
|
|
85
|
+
export function readTranscriptTail(
|
|
86
|
+
transcriptPath: string,
|
|
87
|
+
opts: { homeDir?: string; maxBytes?: number } = {},
|
|
88
|
+
): TranscriptTail {
|
|
89
|
+
const empty: TranscriptTail = {
|
|
90
|
+
lastAssistant: '',
|
|
91
|
+
turnOrdinal: 0,
|
|
92
|
+
toolCalls: [],
|
|
93
|
+
assistantTurnTexts: [],
|
|
94
|
+
};
|
|
95
|
+
if (!transcriptPath || !isSafeTranscriptPath(transcriptPath, opts)) return empty;
|
|
96
|
+
let lines: string[];
|
|
97
|
+
try {
|
|
98
|
+
// R2 finding 12: `maxBytes` was accepted in the options type and never
|
|
99
|
+
// used, so the declared cap was decoration. It is enforced here as well
|
|
100
|
+
// as inside `isSafeTranscriptPath`, because this is the read it bounds.
|
|
101
|
+
// The whole file still has to be walked — the turn ordinal is a count
|
|
102
|
+
// over all entries, not something a tail can answer — so the cap is the
|
|
103
|
+
// guard, not an optimisation.
|
|
104
|
+
if (opts.maxBytes !== undefined && fs.statSync(transcriptPath).size > opts.maxBytes) {
|
|
105
|
+
return empty;
|
|
106
|
+
}
|
|
107
|
+
lines = fs.readFileSync(transcriptPath, 'utf-8').split('\n');
|
|
108
|
+
} catch {
|
|
109
|
+
return empty;
|
|
110
|
+
}
|
|
111
|
+
let lastAssistant = '';
|
|
112
|
+
let turnOrdinal = 0;
|
|
113
|
+
let toolCalls: ToolCall[] = [];
|
|
114
|
+
let assistantTurnTexts: string[] = [];
|
|
115
|
+
for (const rawLine of lines) {
|
|
116
|
+
const line = rawLine.trim();
|
|
117
|
+
if (!line) continue;
|
|
118
|
+
let obj: unknown;
|
|
119
|
+
try {
|
|
120
|
+
obj = JSON.parse(line);
|
|
121
|
+
} catch {
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
if (typeof obj !== 'object' || obj === null || Array.isArray(obj)) continue;
|
|
125
|
+
const entry = obj as Record<string, unknown>;
|
|
126
|
+
// Sidechain entries are a SUBAGENT's conversation recorded in the same
|
|
127
|
+
// JSONL. A subagent prompt is a genuine-looking user-role text entry
|
|
128
|
+
// appended mid-turn, so counting it moves the ordinal within the turn —
|
|
129
|
+
// finding 3's failure class in a new shape (R2 round 2, finding 3).
|
|
130
|
+
if (entry['isSidechain'] === true) continue;
|
|
131
|
+
const role = entry['type'];
|
|
132
|
+
if (role !== 'assistant' && role !== 'user') continue;
|
|
133
|
+
const msg = entry['message'];
|
|
134
|
+
if (typeof msg !== 'object' || msg === null || Array.isArray(msg)) continue;
|
|
135
|
+
const content = (msg as Record<string, unknown>)['content'];
|
|
136
|
+
// Tool calls are read BEFORE the text guard: an assistant entry that is
|
|
137
|
+
// only a tool_use block has no text at all, so `continue`-ing on a null
|
|
138
|
+
// text would drop exactly the entries this detector exists to see.
|
|
139
|
+
if (role === 'assistant') {
|
|
140
|
+
toolCalls.push(...extractToolCalls(content));
|
|
141
|
+
}
|
|
142
|
+
const text = _messageText(content);
|
|
143
|
+
if (text === null) continue;
|
|
144
|
+
if (role === 'assistant') {
|
|
145
|
+
lastAssistant = text;
|
|
146
|
+
assistantTurnTexts.push(text);
|
|
147
|
+
} else if (!isSyntheticPrompt(text)) {
|
|
148
|
+
turnOrdinal += 1;
|
|
149
|
+
// A genuine user prompt starts a new turn, so the previous turn's
|
|
150
|
+
// tool activity stops counting. Without this reset, a verification
|
|
151
|
+
// run three turns ago would vouch for an edit made now — the
|
|
152
|
+
// "fresh" in edit-without-FRESH-verification is this line.
|
|
153
|
+
toolCalls = [];
|
|
154
|
+
// Same boundary, same reason, for detector E: an options block the
|
|
155
|
+
// user has since replied to is answered, not dropped. Resetting on
|
|
156
|
+
// the identical line is what makes "in the SAME user turn" true of
|
|
157
|
+
// the array rather than merely intended.
|
|
158
|
+
assistantTurnTexts = [];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
return {
|
|
162
|
+
lastAssistant: lastAssistant.trim(),
|
|
163
|
+
turnOrdinal,
|
|
164
|
+
toolCalls,
|
|
165
|
+
assistantTurnTexts,
|
|
166
|
+
};
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
/**
|
|
170
|
+
* Extract this entry's tool calls, keeping only name, shell command and target
|
|
171
|
+
* path. A tool input can hold a whole file body; nothing but those three fields
|
|
172
|
+
* is carried forward.
|
|
173
|
+
*
|
|
174
|
+
* Exported because `measure_turn_end_gate` scores detectors C and E over a real
|
|
175
|
+
* transcript corpus and therefore has to rebuild the same `ToolCall[]` the gate
|
|
176
|
+
* sees. A second extractor there would be a second dialect of "what the turn
|
|
177
|
+
* did", and the measurement would then be of that dialect rather than of the
|
|
178
|
+
* shipped gate — the exact population-parity defect that script's own header
|
|
179
|
+
* documents twice.
|
|
180
|
+
*/
|
|
181
|
+
export function extractToolCalls(content: unknown): ToolCall[] {
|
|
182
|
+
if (!Array.isArray(content)) return [];
|
|
183
|
+
const out: ToolCall[] = [];
|
|
184
|
+
for (const blk of content) {
|
|
185
|
+
if (typeof blk !== 'object' || blk === null || Array.isArray(blk)) continue;
|
|
186
|
+
const b = blk as Record<string, unknown>;
|
|
187
|
+
if (b['type'] !== 'tool_use') continue;
|
|
188
|
+
const name = b['name'];
|
|
189
|
+
if (typeof name !== 'string') continue;
|
|
190
|
+
const input = b['input'];
|
|
191
|
+
const call: ToolCall = { name };
|
|
192
|
+
if (typeof input === 'object' && input !== null && !Array.isArray(input)) {
|
|
193
|
+
const inp = input as Record<string, unknown>;
|
|
194
|
+
const cmd = inp['command'];
|
|
195
|
+
if (typeof cmd === 'string') call.command = cmd;
|
|
196
|
+
const p = inp['file_path'] ?? inp['path'] ?? inp['notebook_path'];
|
|
197
|
+
if (typeof p === 'string') call.path = p;
|
|
198
|
+
}
|
|
199
|
+
out.push(call);
|
|
200
|
+
}
|
|
201
|
+
return out;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
function _messageText(content: unknown): string | null {
|
|
205
|
+
if (typeof content === 'string') return content;
|
|
206
|
+
if (!Array.isArray(content)) return null;
|
|
207
|
+
const parts: string[] = [];
|
|
208
|
+
for (const blk of content) {
|
|
209
|
+
if (typeof blk !== 'object' || blk === null || Array.isArray(blk)) continue;
|
|
210
|
+
const b = blk as Record<string, unknown>;
|
|
211
|
+
if (b['type'] !== 'text') continue;
|
|
212
|
+
const t = b['text'];
|
|
213
|
+
if (typeof t === 'string') parts.push(t);
|
|
214
|
+
}
|
|
215
|
+
return parts.length > 0 ? parts.join('\n') : null;
|
|
216
|
+
}
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Who may authorise a typed op — `road-to-adversarial-verification-and-long-runs`
|
|
3
|
+
* 7.3.
|
|
4
|
+
*
|
|
5
|
+
* Under a mission the sequence is: a council check that the op belongs to the
|
|
6
|
+
* mission → a native ask naming the exact object → execute. The council sits
|
|
7
|
+
* BEFORE the ask and can only subtract.
|
|
8
|
+
*
|
|
9
|
+
* ```
|
|
10
|
+
* THE COUNCIL MAY VETO A TYPED OP. IT MAY NEVER GRANT ONE.
|
|
11
|
+
* A COUNCIL VERDICT ALONE NEVER PRODUCES THE GRANT, HOWEVER UNANIMOUS.
|
|
12
|
+
* THE GRANT COMES FROM THE ASK, AND THE ASK NAMES THE EXACT OBJECT.
|
|
13
|
+
* THE ASK ALSO NAMES THE VERB AND THE TURN. A CONFIRMATION THAT CANNOT SAY
|
|
14
|
+
* WHICH TURN IT CAME FROM IS NOT A THIS-TURN CONFIRMATION.
|
|
15
|
+
* ```
|
|
16
|
+
*
|
|
17
|
+
* **Why the asymmetry is the whole design.** ADR-257: an unpaid route may
|
|
18
|
+
* propose and score, never decide. A council that could grant would be a second
|
|
19
|
+
* authorisation path around `non-destructive-by-default`'s this-turn
|
|
20
|
+
* confirmation — and it would be the cheaper one, so it would become the only
|
|
21
|
+
* one. Letting it VETO adds a refusal without adding an authority, which is the
|
|
22
|
+
* only direction that is safe to add for free.
|
|
23
|
+
*
|
|
24
|
+
* **An UNAVAILABLE council does not block the op**, and that is deliberate
|
|
25
|
+
* rather than lax: the council is a filter, so its absence removes a filter and
|
|
26
|
+
* leaves the ask exactly where it was. Treating unavailability as a veto would
|
|
27
|
+
* make an unconfigured council a silent kill switch on every typed op — the
|
|
28
|
+
* same-shaped wrong guess `council-availability` exists over.
|
|
29
|
+
*
|
|
30
|
+
* **What the exactness check is, and why it is two tiers.** It used to be a
|
|
31
|
+
* shape-free heuristic — eight characters, one of them not a letter — which
|
|
32
|
+
* accepted `!!!!!!!!` and `all-branches` alike, so the "exact object" guarantee
|
|
33
|
+
* the Hard Floor rests on was not real. It is now a universal floor that every
|
|
34
|
+
* op passes through, plus a per-op shape where one is declared. The floor is
|
|
35
|
+
* deliberately the weakest claim that cannot be wrong (an object with no
|
|
36
|
+
* alphanumeric run names nothing); the shape is where a category is actually
|
|
37
|
+
* refused. An op with NO declared shape keeps the old heuristic above the floor
|
|
38
|
+
* rather than being refused by default, because a Hard-Floor ask that can never
|
|
39
|
+
* be answered is worse than a loose one. The residual that leaves is real and
|
|
40
|
+
* bounded: a category still grants for an op OUTSIDE the declared set, and the
|
|
41
|
+
* declared set is drawn from the Hard Floor's own list of gated operations.
|
|
42
|
+
*
|
|
43
|
+
* Pure.
|
|
44
|
+
*/
|
|
45
|
+
|
|
46
|
+
/** What the council said about this op. `null` when it was not consulted. */
|
|
47
|
+
export type CouncilVerdict = 'in-mission' | 'out-of-mission' | 'unavailable' | null;
|
|
48
|
+
|
|
49
|
+
/** The ask, as `non-destructive-by-default` requires it: an exact object. */
|
|
50
|
+
export interface ExactObjectAsk {
|
|
51
|
+
/** e.g. `force-push`. Blank is not a verb, and a blank verb never grants. */
|
|
52
|
+
readonly op: string;
|
|
53
|
+
/**
|
|
54
|
+
* The object, named concretely — a branch and SHA, a recipient and subject,
|
|
55
|
+
* an amount and card-last4. A CATEGORY is not an object.
|
|
56
|
+
*/
|
|
57
|
+
readonly object: string;
|
|
58
|
+
/** True once a human answered this ask. */
|
|
59
|
+
readonly confirmed: boolean;
|
|
60
|
+
/**
|
|
61
|
+
* The turn the human answered in, compared against the caller's current
|
|
62
|
+
* turn. A bare boolean cannot express the Hard Floor's THIS-TURN wording:
|
|
63
|
+
* an ask persisted into a mission record and read back after a restart is
|
|
64
|
+
* otherwise indistinguishable from a confirmation given a second ago. That
|
|
65
|
+
* replay-across-restart case is the concrete threat, and the sibling module
|
|
66
|
+
* `mission_record` is what makes it concrete.
|
|
67
|
+
*/
|
|
68
|
+
readonly confirmed_turn: string;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export type GrantState = 'vetoed' | 'ask-required' | 'granted';
|
|
72
|
+
|
|
73
|
+
export interface GrantDecision {
|
|
74
|
+
readonly state: GrantState;
|
|
75
|
+
readonly reason: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Whitespace-separated parts of an object, empties dropped. */
|
|
79
|
+
function parts(object: string): string[] {
|
|
80
|
+
return object.trim().split(/\s+/).filter((p) => p.length > 0);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** A part with its surrounding punctuation removed, so `@abc123,` reads as `abc123`. */
|
|
84
|
+
function bare(part: string): string {
|
|
85
|
+
return part.replace(/^[^0-9a-z]+/i, '').replace(/[^0-9a-z]+$/i, '');
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/** Does this text name anything at all? The universal floor, and the weakest claim made here. */
|
|
89
|
+
function namesSomething(object: string): boolean {
|
|
90
|
+
return /[0-9a-z]{2,}/i.test(object);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
/** A 7-to-40 character hex run — the token that pins a push to one commit. */
|
|
94
|
+
function isSha(part: string): boolean {
|
|
95
|
+
return /^[0-9a-f]{7,40}$/i.test(bare(part));
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** An address-shaped part — the token that pins a send to one recipient. */
|
|
99
|
+
function isRecipient(part: string): boolean {
|
|
100
|
+
return /^[^\s@]+@[^\s@]+\.[a-z]{2,}$/i.test(bare(part));
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/** A money-shaped part — a decimal amount, or digits carrying a currency marker. */
|
|
104
|
+
function isAmount(part: string): boolean {
|
|
105
|
+
return /\d[.,]\d{2}\b/.test(part) || (/\d/.test(part) && /[€$£]|\b(?:EUR|USD|GBP)\b/i.test(part));
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** A four-digit run — the card suffix that pins a purchase to one instrument. */
|
|
109
|
+
function isCardSuffix(part: string): boolean {
|
|
110
|
+
return /(?:^|\D)\d{4}$/.test(bare(part)) && !isAmount(part);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Is there a part that carries a name, other than the one already accounted for? */
|
|
114
|
+
function hasQualifier(ps: string[], accountedFor: (p: string) => boolean): boolean {
|
|
115
|
+
return ps.some((p) => !accountedFor(p) && /[0-9a-z]{2,}/i.test(bare(p)));
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* The declared per-op object shapes.
|
|
120
|
+
*
|
|
121
|
+
* Keyed by the typed-op vocabulary, looked up case-insensitively on a trimmed
|
|
122
|
+
* op. Only the ops whose exact object this repository has actually seen or that
|
|
123
|
+
* the roadmap specifies are declared — inventing a shape for an op with no
|
|
124
|
+
* example risks refusing the first real one, which is the Risk-1 direction.
|
|
125
|
+
*
|
|
126
|
+
* `tag-or-release` is deliberately absent: a release object may legitimately be
|
|
127
|
+
* a bare version, so a SHA requirement would refuse a real one.
|
|
128
|
+
*/
|
|
129
|
+
const OBJECT_SHAPES: ReadonlyMap<string, (object: string) => boolean> = new Map([
|
|
130
|
+
// A push names WHERE and WHICH COMMIT. The remote is optional — the shipped
|
|
131
|
+
// object in the unit fixture names a ref and a SHA and no remote, so
|
|
132
|
+
// requiring one would refuse an object this repository already ships.
|
|
133
|
+
...(['push', 'force-push', 'merge-to-trunk', 'history-rewrite'] as const).map(
|
|
134
|
+
(op) =>
|
|
135
|
+
[
|
|
136
|
+
op,
|
|
137
|
+
(object: string): boolean => {
|
|
138
|
+
const ps = parts(object);
|
|
139
|
+
return ps.some(isSha) && hasQualifier(ps, isSha);
|
|
140
|
+
},
|
|
141
|
+
] as const,
|
|
142
|
+
),
|
|
143
|
+
[
|
|
144
|
+
'send',
|
|
145
|
+
(object: string): boolean => {
|
|
146
|
+
const ps = parts(object);
|
|
147
|
+
return ps.some(isRecipient) && hasQualifier(ps, isRecipient);
|
|
148
|
+
},
|
|
149
|
+
] as const,
|
|
150
|
+
[
|
|
151
|
+
'purchase',
|
|
152
|
+
(object: string): boolean => {
|
|
153
|
+
const ps = parts(object);
|
|
154
|
+
return ps.some(isAmount) && ps.some(isCardSuffix);
|
|
155
|
+
},
|
|
156
|
+
] as const,
|
|
157
|
+
]);
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* A named object must be specific enough to be checkable.
|
|
161
|
+
*
|
|
162
|
+
* Exported so the two tiers are testable separately: a caller can ask whether an
|
|
163
|
+
* object would survive the floor without also asking which op it belongs to.
|
|
164
|
+
*/
|
|
165
|
+
export function objectIsExact(op: string, object: string): boolean {
|
|
166
|
+
if (!namesSomething(object)) return false;
|
|
167
|
+
const shape = OBJECT_SHAPES.get(op.trim().toLowerCase());
|
|
168
|
+
if (shape !== undefined) return shape(object);
|
|
169
|
+
// No declared shape: the pre-existing heuristic, above the floor.
|
|
170
|
+
return object.trim().length >= 8 && /[^a-z ]/i.test(object);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/** An op names what is about to happen. Blank names nothing. */
|
|
174
|
+
function opIsNamed(op: string): boolean {
|
|
175
|
+
return op.trim().length > 0;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* Decide whether a typed op may proceed.
|
|
180
|
+
*
|
|
181
|
+
* The order is fixed: veto first, then the verb, then the confirmation, then
|
|
182
|
+
* the turn, then the object. A confirmed ask after a veto is still a veto —
|
|
183
|
+
* otherwise the veto would be advisory, and an advisory veto is not one. The
|
|
184
|
+
* verb is read before the object because it decides which object shape applies.
|
|
185
|
+
*
|
|
186
|
+
* `currentTurn` is REQUIRED rather than optional. An optional turn would make
|
|
187
|
+
* the this-turn check something a caller opts into, and a security check that
|
|
188
|
+
* is off by default is not one.
|
|
189
|
+
*/
|
|
190
|
+
export function grantFor(
|
|
191
|
+
verdict: CouncilVerdict,
|
|
192
|
+
ask: ExactObjectAsk,
|
|
193
|
+
currentTurn: string,
|
|
194
|
+
): GrantDecision {
|
|
195
|
+
if (verdict === 'out-of-mission') {
|
|
196
|
+
return {
|
|
197
|
+
state: 'vetoed',
|
|
198
|
+
reason: `the council found ${ask.op} outside this mission; a veto is not overridden by a later confirmation`,
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
if (!opIsNamed(ask.op)) {
|
|
202
|
+
return {
|
|
203
|
+
state: 'ask-required',
|
|
204
|
+
reason: 'the ask names no operation — the verb that says what is about to happen is blank',
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
if (!ask.confirmed) {
|
|
208
|
+
return {
|
|
209
|
+
state: 'ask-required',
|
|
210
|
+
reason:
|
|
211
|
+
verdict === 'in-mission'
|
|
212
|
+
? `the council cleared ${ask.op}, which is not a grant — the ask still has to happen`
|
|
213
|
+
: `${ask.op} needs a this-turn confirmation naming the exact object`,
|
|
214
|
+
};
|
|
215
|
+
}
|
|
216
|
+
if (currentTurn.trim().length === 0 || ask.confirmed_turn.trim().length === 0) {
|
|
217
|
+
return {
|
|
218
|
+
state: 'ask-required',
|
|
219
|
+
reason: `the confirmation for ${ask.op} carries no turn, so it cannot be shown to be this turn`,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
if (ask.confirmed_turn !== currentTurn) {
|
|
223
|
+
return {
|
|
224
|
+
state: 'ask-required',
|
|
225
|
+
reason: `the confirmation for ${ask.op} was given in turn ${ask.confirmed_turn}, not this turn (${currentTurn})`,
|
|
226
|
+
};
|
|
227
|
+
}
|
|
228
|
+
if (!objectIsExact(ask.op, ask.object)) {
|
|
229
|
+
return {
|
|
230
|
+
state: 'ask-required',
|
|
231
|
+
reason: `the confirmation named "${ask.object}", which is a category rather than an object — an approval must name the exact thing`,
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
return { state: 'granted', reason: `confirmed this turn for ${ask.op} on ${ask.object}` };
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Could this verdict, on its own, produce a grant?
|
|
239
|
+
*
|
|
240
|
+
* Exists as its own function so the Iron Law is CHECKABLE rather than merely
|
|
241
|
+
* stated: it returns false for every verdict, including a unanimous one, and a
|
|
242
|
+
* test asserts that over the whole domain.
|
|
243
|
+
*/
|
|
244
|
+
export function verdictAloneGrants(_verdict: CouncilVerdict): boolean {
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The guardrail watch over typed ops — `road-to-adversarial-verification-and-long-runs`
|
|
3
|
+
* 7.1, observation-only floor.
|
|
4
|
+
*
|
|
5
|
+
* The eleven typed ops are the Hard Floor's own list: the irreversible and
|
|
6
|
+
* externally-visible actions `non-destructive-by-default` gates. The layering is
|
|
7
|
+
* forge protection → the host hook where one is bound → this watch → model
|
|
8
|
+
* policy, and the watch exists because 7.2 measured the second layer and found
|
|
9
|
+
* SEVEN of eight hosts carry no refusing `pre_tool_use` binding at all.
|
|
10
|
+
*
|
|
11
|
+
* **This mode observes and takes no action. That is a design decision with a
|
|
12
|
+
* measured precondition, not caution.** K5 of the roadmap kills daemon
|
|
13
|
+
* enforcement before the observation floor, and the roadmap's own Risk 3 states
|
|
14
|
+
* the reason: a watchdog reading the reflog and shell history for typed ops can
|
|
15
|
+
* misclassify, and its enforcing action is stopping the host process — one false
|
|
16
|
+
* stop costs a twelve-hour run. So enforcement waits behind a measured
|
|
17
|
+
* false-positive rate under 1 % on the 30-session corpus, and
|
|
18
|
+
* {@link enforcementAllowed} refuses to unlock without that artefact.
|
|
19
|
+
*
|
|
20
|
+
* **What it can and cannot see, said plainly.** It reads lines — reflog entries,
|
|
21
|
+
* exposed shell history, forge event payloads. A line is not an intention: an op
|
|
22
|
+
* typed into a comment, a heredoc, or a string literal looks identical to one
|
|
23
|
+
* about to run. That is precisely why the first mode only writes down what it
|
|
24
|
+
* saw, and why the false-positive rate is the gate on doing anything more.
|
|
25
|
+
*
|
|
26
|
+
* Pure. The I/O — tailing a reflog, reading history, polling an event stream —
|
|
27
|
+
* belongs to the caller, so every classification is testable offline.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
/** The typed-op classes this watch recognises. */
|
|
31
|
+
export const TYPED_OPS = [
|
|
32
|
+
'push',
|
|
33
|
+
'force-push',
|
|
34
|
+
'merge-to-trunk',
|
|
35
|
+
'tag-or-release',
|
|
36
|
+
'deploy',
|
|
37
|
+
'bulk-delete',
|
|
38
|
+
'history-rewrite',
|
|
39
|
+
'send',
|
|
40
|
+
'publish',
|
|
41
|
+
'purchase',
|
|
42
|
+
'submit',
|
|
43
|
+
] as const;
|
|
44
|
+
export type TypedOp = (typeof TYPED_OPS)[number];
|
|
45
|
+
|
|
46
|
+
/** Where a line came from. Kept because the sources have different reliability. */
|
|
47
|
+
export type WatchSource = 'reflog' | 'shell-history' | 'forge-event';
|
|
48
|
+
|
|
49
|
+
export interface Observation {
|
|
50
|
+
readonly op: TypedOp;
|
|
51
|
+
readonly source: WatchSource;
|
|
52
|
+
/** The line, truncated. Evidence, never re-executed. */
|
|
53
|
+
readonly evidence: string;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* The recognisers, most specific first.
|
|
58
|
+
*
|
|
59
|
+
* ORDER MATTERS and is asserted by a test: `git push --force` is a force-push,
|
|
60
|
+
* not a push, and a table walked in declaration order gives the specific class
|
|
61
|
+
* only if it is declared before the general one. A misordered table would report
|
|
62
|
+
* the milder op for the more dangerous line, which is the wrong direction for
|
|
63
|
+
* every downstream reader.
|
|
64
|
+
*/
|
|
65
|
+
const RECOGNISERS: readonly (readonly [TypedOp, RegExp])[] = [
|
|
66
|
+
['force-push', /\bgit\b[^\n]*\bpush\b[^\n]*(--force\b|--force-with-lease\b|\s-f\b)/],
|
|
67
|
+
['history-rewrite', /\bgit\b[^\n]*\b(rebase|filter-repo|filter-branch)\b|\bgit\b[^\n]*reset\s+--hard\b/],
|
|
68
|
+
['merge-to-trunk', /\bgit\b[^\n]*\bmerge\b[^\n]*\b(main|master|trunk|production)\b|\bgh\b[^\n]*\bpr\b[^\n]*\bmerge\b/],
|
|
69
|
+
['tag-or-release', /\bgit\b[^\n]*\bpush\b[^\n]*\b--tags?\b|\bgh\b[^\n]*\brelease\b[^\n]*\bcreate\b/],
|
|
70
|
+
['deploy', /\b(terraform\s+apply|kubectl\s+apply|helm\s+(install|upgrade)|serverless\s+deploy)\b/],
|
|
71
|
+
['bulk-delete', /\brm\s+-[a-z]*r[a-z]*f\b|\bgit\b[^\n]*\brm\s+-r\b|\b(DROP\s+TABLE|TRUNCATE)\b/i],
|
|
72
|
+
['push', /\bgit\b[^\n]*\bpush\b/],
|
|
73
|
+
['purchase', /\b(stripe|checkout|charge|payment)\b[^\n]*\b(create|confirm|capture)\b/i],
|
|
74
|
+
['publish', /\bnpm\s+publish\b|\bgh\b[^\n]*\brelease\b[^\n]*\bupload\b/],
|
|
75
|
+
['send', /\b(sendmail|mail\s+-s|curl\b[^\n]*\/messages\b)/],
|
|
76
|
+
['submit', /\bcurl\b[^\n]*\s-X\s*POST\b[^\n]*\b(submit|apply|order)\b/i],
|
|
77
|
+
];
|
|
78
|
+
|
|
79
|
+
const EVIDENCE_MAX = 200;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Classify one line, or `null` when nothing matched.
|
|
83
|
+
*
|
|
84
|
+
* Deliberately returns at most ONE op. A line is one action, and reporting two
|
|
85
|
+
* classes for it would double-count in the false-positive denominator the
|
|
86
|
+
* enforcement gate depends on.
|
|
87
|
+
*/
|
|
88
|
+
export function classifyLine(line: string, source: WatchSource): Observation | null {
|
|
89
|
+
for (const [op, re] of RECOGNISERS) {
|
|
90
|
+
if (re.test(line)) {
|
|
91
|
+
return { op, source, evidence: line.trim().slice(0, EVIDENCE_MAX) };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return null;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
/** Classify a batch, preserving order and dropping non-matches. */
|
|
98
|
+
export function observe(
|
|
99
|
+
lines: readonly string[],
|
|
100
|
+
source: WatchSource,
|
|
101
|
+
): Observation[] {
|
|
102
|
+
return lines
|
|
103
|
+
.map((l) => classifyLine(l, source))
|
|
104
|
+
.filter((o): o is Observation => o !== null);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/** The two modes. There is no third, and `enforce` is gated. */
|
|
108
|
+
export type WatchMode = 'observe' | 'enforce';
|
|
109
|
+
|
|
110
|
+
/** The false-positive measurement the enforcing mode waits behind. */
|
|
111
|
+
export interface FalsePositiveMeasurement {
|
|
112
|
+
/** Sessions the corpus covered. The roadmap names 30. */
|
|
113
|
+
readonly sessions: number;
|
|
114
|
+
readonly observations: number;
|
|
115
|
+
readonly falsePositives: number;
|
|
116
|
+
/** Where the artefact lives, so the claim is checkable. */
|
|
117
|
+
readonly artefact: string;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** The corpus size the roadmap fixed before any measurement was taken. */
|
|
121
|
+
export const REQUIRED_SESSIONS = 30;
|
|
122
|
+
/** The rate enforcement waits behind. */
|
|
123
|
+
export const MAX_FALSE_POSITIVE_RATE = 0.01;
|
|
124
|
+
|
|
125
|
+
export interface EnforcementVerdict {
|
|
126
|
+
readonly allowed: boolean;
|
|
127
|
+
readonly reason: string;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* May the enforcing mode be enabled?
|
|
132
|
+
*
|
|
133
|
+
* `null` — no measurement at all — is a refusal with its own reason, never a
|
|
134
|
+
* pass. A gate that unlocks when it cannot measure is the shape this whole
|
|
135
|
+
* roadmap is written against, and it is the one an absent-artefact check gets
|
|
136
|
+
* wrong by default.
|
|
137
|
+
*/
|
|
138
|
+
export function enforcementAllowed(
|
|
139
|
+
m: FalsePositiveMeasurement | null,
|
|
140
|
+
): EnforcementVerdict {
|
|
141
|
+
if (m === null) {
|
|
142
|
+
return {
|
|
143
|
+
allowed: false,
|
|
144
|
+
reason:
|
|
145
|
+
'no false-positive measurement exists. Enforcement stops the host process; a ' +
|
|
146
|
+
'rate nobody measured is not a control (K5).',
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
if (m.sessions < REQUIRED_SESSIONS) {
|
|
150
|
+
return {
|
|
151
|
+
allowed: false,
|
|
152
|
+
reason: `measured over ${String(m.sessions)} session(s), below the ${String(REQUIRED_SESSIONS)} the corpus was fixed at`,
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
if (m.observations === 0) {
|
|
156
|
+
return {
|
|
157
|
+
allowed: false,
|
|
158
|
+
reason:
|
|
159
|
+
'zero observations: a rate over an empty denominator is not a measurement, ' +
|
|
160
|
+
'and it is the reading a broken recogniser produces',
|
|
161
|
+
};
|
|
162
|
+
}
|
|
163
|
+
const rate = m.falsePositives / m.observations;
|
|
164
|
+
if (rate >= MAX_FALSE_POSITIVE_RATE) {
|
|
165
|
+
return {
|
|
166
|
+
allowed: false,
|
|
167
|
+
reason: `false-positive rate ${rate.toFixed(4)} is not below ${String(MAX_FALSE_POSITIVE_RATE)}`,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
return { allowed: true, reason: `rate ${rate.toFixed(4)} over ${String(m.observations)} observations in ${m.artefact}` };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* What the watch does with an observation, per mode.
|
|
175
|
+
*
|
|
176
|
+
* In `observe` the action is always `record` — there is no branch that acts, so
|
|
177
|
+
* no configuration flips this mode into one that does.
|
|
178
|
+
*/
|
|
179
|
+
export function actionFor(
|
|
180
|
+
mode: WatchMode,
|
|
181
|
+
measurement: FalsePositiveMeasurement | null,
|
|
182
|
+
): 'record' | 'record-and-ask' {
|
|
183
|
+
if (mode === 'observe') return 'record';
|
|
184
|
+
return enforcementAllowed(measurement).allowed ? 'record-and-ask' : 'record';
|
|
185
|
+
}
|