@event4u/agent-config 14.12.0 → 14.13.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 +234 -0
- package/CONTRIBUTING.md +38 -0
- package/CREDITS.md +135 -0
- package/NOTICE +13 -0
- package/README.md +8 -6
- package/dist/agent-src/commands/analyze/inbox.md +109 -2
- package/dist/agent-src/commands/feature/dev.md +7 -2
- package/dist/agent-src/commands/ghostwriter/write.md +5 -1
- package/dist/agent-src/commands/implement-ticket.md +1 -1
- package/dist/agent-src/commands/mission/upgrade.md +1 -1
- package/dist/agent-src/commands/orchestrate.md +2 -2
- package/dist/agent-src/commands/post-as/me.md +5 -1
- package/dist/agent-src/commands/pr/merge.md +19 -6
- package/dist/agent-src/commands/roadmap/process-full.md +19 -5
- package/dist/agent-src/commands/tdd/red.md +12 -2
- package/dist/agent-src/commands/work.md +1 -1
- package/dist/agent-src/contexts/communication/rules-auto/token-efficiency-mechanics.md +40 -0
- package/dist/agent-src/contexts/execution/auto-dispatch-classification.md +5 -2
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +21 -0
- package/dist/agent-src/contexts/execution/orchestration-telemetry.md +1 -0
- package/dist/agent-src/contexts/execution/subagent-response-contract.md +40 -0
- package/dist/agent-src/contexts/execution/subagent-steering.md +13 -1
- package/dist/agent-src/contexts/execution/terminal-states.md +96 -0
- package/dist/agent-src/rules/artifact-drafting-protocol.md +1 -1
- package/dist/agent-src/rules/broken-access-control.md +4 -0
- package/dist/agent-src/rules/code-provenance.md +4 -0
- package/dist/agent-src/rules/design-fidelity.md +1 -1
- package/dist/agent-src/rules/domain-safety-pii.md +4 -0
- package/dist/agent-src/rules/domain-safety-retention.md +4 -0
- package/dist/agent-src/rules/engineering-safety-floor.md +2 -0
- package/dist/agent-src/rules/external-code-graph-interop.md +14 -12
- package/dist/agent-src/rules/improve-before-implement.md +6 -8
- package/dist/agent-src/rules/laravel-routing.md +1 -1
- package/dist/agent-src/rules/lethal-trifecta-guard.md +4 -0
- package/dist/agent-src/rules/secret-vcs-guard.md +2 -0
- package/dist/agent-src/rules/security-sensitive-stop.md +2 -0
- package/dist/agent-src/rules/source-confidentiality.md +12 -34
- package/dist/agent-src/rules/symfony-routing.md +1 -1
- package/dist/agent-src/rules/token-efficiency.md +14 -0
- package/dist/agent-src/rules/tool-safety.md +4 -0
- package/dist/agent-src/rules/untrusted-input-defense.md +4 -0
- package/dist/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
- package/dist/agent-src/scripts/guarded_baseline.ts +328 -0
- package/dist/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/dist/agent-src/scripts/stub_queue.ts +82 -0
- package/dist/agent-src/scripts/stubs_due.ts +400 -0
- package/dist/agent-src/scripts/ticket_bundles.ts +84 -0
- package/dist/agent-src/scripts/update_roadmap_progress.ts +8 -4
- package/dist/agent-src/skills/brand-asset-generation/SKILL.md +17 -4
- package/dist/agent-src/skills/brand-audit/SKILL.md +33 -0
- package/dist/agent-src/skills/code-intelligence/SKILL.md +57 -0
- package/dist/agent-src/skills/database/SKILL.md +8 -0
- package/dist/agent-src/skills/dependency-upgrade/SKILL.md +27 -0
- package/dist/agent-src/skills/design-review/SKILL.md +1 -1
- package/dist/agent-src/skills/doc-coauthoring/SKILL.md +1 -1
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +17 -1
- package/dist/agent-src/skills/feature-planning/SKILL.md +9 -2
- package/dist/agent-src/skills/frontend-render-security/SKILL.md +44 -0
- package/dist/agent-src/skills/git-workflow/SKILL.md +1 -1
- package/dist/agent-src/skills/js-library-packaging/scripts/check_package_surface.ts +38 -4
- package/dist/agent-src/skills/judge-artifact-completeness/rubrics/ticket-quality-score.json +2 -2
- package/dist/agent-src/skills/laravel/SKILL.md +35 -0
- package/dist/agent-src/skills/laravel-migration/SKILL.md +57 -3
- package/dist/agent-src/skills/monorepo-workspace/SKILL.md +26 -0
- package/dist/agent-src/skills/playbook-authoring/SKILL.md +28 -1
- package/dist/agent-src/skills/react-shadcn-ui/SKILL.md +1 -1
- package/dist/agent-src/skills/skill-writing/references/procedure.md +5 -5
- package/dist/agent-src/skills/sql-writing/SKILL.md +7 -1
- package/dist/agent-src/skills/standards-from-config/SKILL.md +27 -4
- package/dist/agent-src/skills/supply-chain-intake/SKILL.md +11 -4
- package/dist/agent-src/skills/symfony-workflow/SKILL.md +35 -0
- package/dist/agent-src/skills/test-case-discovery/SKILL.md +85 -0
- package/dist/agent-src/skills/test-driven-development/SKILL.md +33 -9
- package/dist/agent-src/skills/testing-anti-patterns/SKILL.md +1 -1
- package/dist/agent-src/skills/testing-anti-patterns/process-anti-patterns.md +37 -0
- package/dist/agent-src/skills/ui-component-architect/SKILL.md +45 -2
- package/dist/agent-src/skills/upstream-contribute/SKILL.md +38 -1
- package/dist/agent-src/skills/verify-repair-loop/SKILL.md +9 -4
- package/dist/agent-src/templates/agents/agent-project-settings.example.yml +1 -1
- package/dist/agent-src/templates/roadmaps.md +97 -3
- package/dist/agent-src/templates/scripts/check_memory.ts +258 -17
- package/dist/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/dist/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/dist/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/dist/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/dist/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/dist/cli/commands/uiAudit.js +124 -5
- package/dist/cli/registry.js +2 -0
- package/dist/cli-delegate/{chunk-HYWSRNUX.js → chunk-2DK2VJWG.js} +3 -3
- package/dist/cli-delegate/{chunk-JCW7ZLHK.js → chunk-4FS4WRXP.js} +5 -0
- package/dist/cli-delegate/{chunk-OMIVVCG2.js → chunk-A4QJ6ENT.js} +24 -0
- package/dist/cli-delegate/{chunk-JJA2QEWA.js → chunk-AKJGUQNO.js} +466 -285
- package/dist/cli-delegate/{chunk-E46KPN6K.js → chunk-BTWUYDGN.js} +424 -363
- package/dist/cli-delegate/{chunk-K4FFODTS.js → chunk-ILMUPLGW.js} +1227 -316
- package/dist/cli-delegate/{chunk-FF7WAFMR.js → chunk-KPBWFOPZ.js} +5 -1
- package/dist/cli-delegate/cmd_adr_effective.js +102 -0
- package/dist/cli-delegate/cmd_conformance.js +42 -10
- package/dist/cli-delegate/cmd_doctor.js +3 -3
- package/dist/cli-delegate/cmd_export.js +3 -2
- package/dist/cli-delegate/cmd_fleet.js +2 -2
- package/dist/cli-delegate/cmd_handoff.js +1 -1
- package/dist/cli-delegate/cmd_preflight.js +2 -2
- package/dist/cli-delegate/cmd_prune.js +3 -2
- package/dist/cli-delegate/cmd_settings_get.js +1 -1
- package/dist/cli-delegate/cmd_settings_migrate.js +3 -2
- package/dist/cli-delegate/cmd_settings_set.js +2 -2
- package/dist/cli-delegate/cmd_sync.js +3 -2
- package/dist/cli-delegate/cmd_uninstall.js +3 -2
- package/dist/cli-delegate/cmd_validate.js +3 -2
- package/dist/discovery/deprecation-report.md +1 -1
- package/dist/discovery/discovery-manifest.json +57 -57
- package/dist/discovery/discovery-manifest.json.sha256 +1 -1
- package/dist/discovery/discovery-manifest.summary.md +1 -1
- package/dist/discovery/dormancy-report.md +1 -1
- package/dist/discovery/orphan-report.md +1 -1
- package/dist/discovery/packs.json +2 -2
- package/dist/discovery/trust-report.md +1 -1
- package/dist/discovery/workspaces.json +2 -2
- package/dist/hooks/dispatch.js +1836 -712
- package/dist/install/claudeRuleRewrite.js +254 -0
- package/dist/install/detect.js +29 -16
- package/dist/install/detect_php_shape.js +159 -0
- package/dist/install/install.mjs +634 -428
- package/dist/mcp/awesome-mcp-servers.row.md +1 -1
- package/dist/mcp/mcp-cloudflare-catalogue.json +1 -1
- package/dist/mcp/registry-manifest.json +3 -3
- package/dist/mcp/server.json +3 -3
- package/dist/mcp/server.mjs +4818 -2645
- package/dist/server/schemas/settings.js +3 -0
- package/dist/ui/assets/index-D0jaCRPM.js +29 -0
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +1 -1
- package/docs/THIRD-PARTY-NOTICES.md +25 -0
- package/docs/contracts/provider-lifecycle.md +1 -1
- package/docs/contracts/settings-classes.md +10 -4
- package/docs/guidelines/agent-infra/5w2h-analysis.md +3 -3
- package/docs/guidelines/agent-infra/agent-interaction-and-decision-quality.md +1 -0
- package/docs/guidelines/agent-infra/artifact-drafting-protocol-mechanics.md +58 -3
- package/docs/guidelines/agent-infra/ask-when-uncertain-demos.md +2 -2
- package/docs/guidelines/agent-infra/asking-and-brevity-examples.md +3 -3
- package/docs/guidelines/agent-infra/break-glass-usage.md +7 -7
- package/docs/guidelines/agent-infra/carve-out-predicates.md +3 -3
- package/docs/guidelines/agent-infra/comparison-matrix.md +1 -1
- package/docs/guidelines/agent-infra/critical-thinking.md +4 -4
- package/docs/guidelines/agent-infra/direct-answers-demos.md +2 -2
- package/docs/guidelines/agent-infra/domain-eval-anti-pattern.md +1 -1
- package/docs/guidelines/agent-infra/engineering-memory-data-format.md +3 -3
- package/docs/guidelines/agent-infra/existence-question-verdicts.md +80 -0
- package/docs/guidelines/agent-infra/first-principles.md +2 -2
- package/docs/guidelines/agent-infra/framework-neutrality-patterns.md +2 -2
- package/docs/guidelines/agent-infra/guarded-baseline.md +39 -0
- package/docs/guidelines/agent-infra/inversion-thinking.md +5 -5
- package/docs/guidelines/agent-infra/language-and-tone-examples.md +1 -1
- package/docs/guidelines/agent-infra/layered-settings.md +10 -10
- package/docs/guidelines/agent-infra/memory-access.md +1 -1
- package/docs/guidelines/agent-infra/mental-models.md +3 -3
- package/docs/guidelines/agent-infra/reuse-verdict-mechanics.md +71 -0
- package/docs/guidelines/agent-infra/review-routing-data-format.md +7 -7
- package/docs/guidelines/agent-infra/roadmap-progress-mechanics.md +13 -7
- package/docs/guidelines/agent-infra/role-contracts.md +2 -2
- package/docs/guidelines/agent-infra/role-mode-router.md +3 -3
- package/docs/guidelines/agent-infra/rule-type-governance.md +2 -2
- package/docs/guidelines/agent-infra/scqa-framework.md +5 -5
- package/docs/guidelines/agent-infra/self-improvement-pipeline.md +6 -6
- package/docs/guidelines/agent-infra/simplicity-and-goal-demos.md +2 -2
- package/docs/guidelines/agent-infra/six-hats.md +3 -3
- package/docs/guidelines/agent-infra/skill-quality-checklist.md +6 -6
- package/docs/guidelines/agent-infra/source-confidentiality-mechanics.md +109 -0
- package/docs/guidelines/agent-infra/systems-thinking.md +1 -1
- package/docs/guidelines/agent-infra/verify-before-complete-demos.md +2 -2
- package/docs/guidelines/augment-portability-patterns.md +6 -4
- package/docs/guidelines/component-oriented-and-oop-development.md +3 -2
- package/docs/guidelines/cross-role-handoff.md +2 -2
- package/docs/guidelines/design-fidelity-mechanics.md +1 -1
- package/docs/guidelines/docs/readme-size-and-splitting.md +3 -3
- package/docs/guidelines/php/database.md +90 -7
- package/docs/guidelines/prompt-templates.md +7 -7
- package/llms.txt +2 -2
- package/package.json +5 -2
- package/src/agent-src/scripts/archive_completed_roadmaps.ts +247 -3
- package/src/agent-src/scripts/guarded_baseline.ts +328 -0
- package/src/agent-src/scripts/roadmap_set_step.ts +253 -0
- package/src/agent-src/scripts/stub_queue.ts +82 -0
- package/src/agent-src/scripts/stubs_due.ts +400 -0
- package/src/agent-src/scripts/ticket_bundles.ts +84 -0
- package/src/agent-src/scripts/update_roadmap_progress.ts +8 -4
- package/src/agent-src/templates/scripts/check_memory.ts +258 -17
- package/src/agent-src/templates/scripts/check_memory_proposal.ts +9 -3
- package/src/agent-src/templates/scripts/memory_lookup.ts +86 -0
- package/src/agent-src/templates/scripts/memory_signal.ts +44 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/implement.ts +90 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/index.ts +11 -0
- package/src/agent-src/templates/scripts/work_engine/directives/backend/test.ts +23 -1
- package/src/agent-src/templates/scripts/work_engine/directives/backend/verify.ts +10 -1
- package/src/agent-src/templates/scripts/work_engine/directives/mixed/stitch.ts +5 -1
- package/src/agent-src/templates/scripts/work_engine/stack/runner.ts +102 -14
- package/src/config/agent-settings.template.yml +32 -3
- package/src/config/assurance-capability-registry.json +21 -9
- package/src/config/canonical-terms.yml +119 -0
- package/src/config/capability-native-outcome-bars.json +81 -0
- package/src/config/ci-local-parity.yml +80 -0
- package/src/config/drafting-channels.yml +43 -0
- package/src/config/estate-count-budget.json +38 -36
- package/src/config/evaluator-budgets.json +14 -14
- package/src/config/gate-coverage.yml +579 -63
- package/src/config/gate-reachability-exemptions.json +21 -0
- package/src/config/gate-violation-baselines.json +37 -19
- package/src/config/harness-evolution-budget.json +25 -0
- package/src/config/lapsed-beta-baseline.json +96 -0
- package/src/config/memory-twin-verdicts.yml +129 -0
- package/src/config/metric-registry.yml +124 -0
- package/src/config/pack-size-budget.json +16 -15
- package/src/config/preamble-payload-budget.json +8 -8
- package/src/config/profiles/balanced.ini +27 -0
- package/src/config/profiles/full.ini +27 -0
- package/src/config/profiles/minimal.ini +27 -0
- package/src/config/publish-surface.json +9 -2
- package/src/config/routing-coverage-seed.json +16 -2
- package/src/config/rule-stub-ceilings.json +126 -112
- package/src/config/turnaround-budget.json +66 -0
- package/src/install/claudeRuleRewrite.ts +288 -0
- package/src/install/detect.ts +60 -17
- package/src/install/detect_php_shape.ts +191 -0
- package/src/scripts/_cli/cmd_doctor.ts +27 -112
- package/src/scripts/_dispatch.bash +27 -0
- package/src/scripts/_lib/activation_ladder.ts +211 -0
- package/src/scripts/_lib/activation_states.ts +88 -0
- package/src/scripts/_lib/adherence_detectors.ts +143 -0
- package/src/scripts/_lib/arith_claims.ts +181 -0
- package/src/scripts/_lib/arm_ranking.ts +62 -0
- package/src/scripts/_lib/asset_delivery_ledger.ts +303 -0
- package/src/scripts/_lib/audit_field_provenance.ts +54 -0
- package/src/scripts/_lib/auto_dispatch.ts +32 -3
- package/src/scripts/_lib/bench_ab_size_claim.ts +98 -4
- package/src/scripts/_lib/benchmark_cache_fields.ts +158 -0
- package/src/scripts/_lib/candidate_proposer.ts +402 -0
- package/src/scripts/_lib/candidate_record.ts +601 -0
- package/src/scripts/_lib/capture_rate.ts +319 -0
- package/src/scripts/_lib/collector_denominator.ts +771 -0
- package/src/scripts/_lib/collector_record.ts +340 -0
- package/src/scripts/_lib/collector_store.ts +664 -0
- package/src/scripts/_lib/collector_supervision.ts +827 -0
- package/src/scripts/_lib/concern_estate.ts +77 -0
- package/src/scripts/_lib/config_chain.ts +477 -0
- package/src/scripts/_lib/config_cost.ts +209 -0
- package/src/scripts/_lib/council_settings_block.ts +6 -0
- package/src/scripts/_lib/curator_ops.ts +172 -0
- package/src/scripts/_lib/delivery_arm_experiment.ts +266 -0
- package/src/scripts/_lib/delivery_criticality.ts +126 -0
- package/src/scripts/_lib/doctor_runtime_checks.ts +70 -0
- package/src/scripts/_lib/empty_cycles.ts +88 -0
- package/src/scripts/_lib/eval_publication.ts +354 -0
- package/src/scripts/_lib/evaluation_vector.ts +283 -0
- package/src/scripts/_lib/evaluator_promotion.ts +225 -0
- package/src/scripts/_lib/evidence_basis.ts +118 -0
- package/src/scripts/_lib/exec_evidence.ts +8 -0
- package/src/scripts/_lib/experience_card.ts +272 -0
- package/src/scripts/_lib/experience_report.ts +151 -0
- package/src/scripts/_lib/experiment_freeze.ts +112 -0
- package/src/scripts/_lib/generated_by.ts +66 -0
- package/src/scripts/_lib/harness_evolution_guards.ts +229 -0
- package/src/scripts/_lib/hook_effect_probe.ts +141 -0
- package/src/scripts/_lib/host_denominator.ts +687 -0
- package/src/scripts/_lib/host_projection_reach.ts +157 -0
- package/src/scripts/_lib/ignored_blocker.ts +148 -0
- package/src/scripts/_lib/install_reach_checks.ts +495 -0
- package/src/scripts/_lib/json_duplicate_keys.ts +150 -0
- package/src/scripts/_lib/judge_hygiene.ts +228 -0
- package/src/scripts/_lib/legacy_boundary_map.ts +228 -0
- package/src/scripts/_lib/loop_guards.ts +299 -0
- package/src/scripts/_lib/loss_class.ts +129 -0
- package/src/scripts/_lib/machine_wake.ts +51 -0
- package/src/scripts/_lib/md_prose_lines.ts +177 -0
- package/src/scripts/_lib/minimality_tiebreak.ts +168 -0
- package/src/scripts/_lib/one_resolver_invariant.ts +449 -0
- package/src/scripts/_lib/orchestration_record.ts +253 -5
- package/src/scripts/_lib/outcome_envelope.ts +227 -0
- package/src/scripts/_lib/outcome_vocabularies.ts +125 -0
- package/src/scripts/_lib/paired_stats.ts +245 -0
- package/src/scripts/_lib/paired_verdict.ts +226 -0
- package/src/scripts/_lib/prefix_stable_surfaces.ts +123 -0
- package/src/scripts/_lib/privacy_class.ts +47 -0
- package/src/scripts/_lib/regression_neighbourhood.ts +259 -0
- package/src/scripts/_lib/repeated_failure.ts +105 -0
- package/src/scripts/_lib/repo_root.ts +111 -0
- package/src/scripts/_lib/review_skipped_record.ts +37 -0
- package/src/scripts/_lib/role_split.ts +338 -0
- package/src/scripts/_lib/runtime_journal.ts +1499 -0
- package/src/scripts/_lib/runtime_wiring_checks.ts +430 -0
- package/src/scripts/_lib/skill_catalogue.ts +11 -1
- package/src/scripts/_lib/source_digest.ts +200 -0
- package/src/scripts/_lib/source_redact.ts +260 -0
- package/src/scripts/_lib/source_shape.ts +502 -0
- package/src/scripts/_lib/source_snapshot_dedup.ts +199 -0
- package/src/scripts/_lib/strip_comments.d.mts +8 -0
- package/src/scripts/_lib/strip_comments.mjs +65 -0
- package/src/scripts/_lib/test_red_state.ts +154 -0
- package/src/scripts/_lib/trigger_shift.ts +144 -0
- package/src/scripts/_lib/untrusted_content.ts +82 -7
- package/src/scripts/adoption_report.ts +1 -1
- package/src/scripts/adr/evidence_census.ts +1 -1
- package/src/scripts/adr/regenerate_index.ts +26 -2
- package/src/scripts/adr_cite_check.ts +164 -1
- package/src/scripts/ai_council/blind_review.ts +11 -1
- package/src/scripts/ai_council/clients.ts +4 -0
- package/src/scripts/ai_council/config.ts +3 -0
- package/src/scripts/ai_council/consensus.ts +28 -8
- package/src/scripts/ai_council/consensus_round.ts +226 -0
- package/src/scripts/ai_council/disagreement_signal.ts +338 -0
- package/src/scripts/ai_council/inline_findings.ts +340 -0
- package/src/scripts/ai_council/judge_position_bias.ts +197 -0
- package/src/scripts/ai_council/orchestrator.ts +16 -203
- package/src/scripts/ai_council/orchestrator_results.ts +23 -3
- package/src/scripts/ai_council/pricing.ts +106 -0
- package/src/scripts/ai_council/prompts.ts +74 -7
- package/src/scripts/ai_council/provider_leakage_bench.ts +327 -0
- package/src/scripts/ai_council/py_format.ts +14 -3
- package/src/scripts/ai_council/recouncil_guard.ts +293 -0
- package/src/scripts/ai_council/response_record.ts +50 -0
- package/src/scripts/ai_council/topology_bench_manifest.ts +860 -0
- package/src/scripts/ai_council/topology_vocabulary.ts +175 -0
- package/src/scripts/asset_delivery_ledger.ts +157 -0
- package/src/scripts/attest_artifact.ts +193 -0
- package/src/scripts/audit_auto_rules.ts +2 -1
- package/src/scripts/audit_command_surface.ts +1 -1
- package/src/scripts/audit_mcp_tools.ts +2 -2
- package/src/scripts/audit_skill_overlap_allowlist.json +6 -1
- package/src/scripts/audit_user_type_axis.ts +2 -1
- package/src/scripts/bench_ab_clone.ts +215 -10
- package/src/scripts/bench_ab_integrity.ts +119 -5
- package/src/scripts/bench_ab_v2_stats.ts +40 -213
- package/src/scripts/build_archive_index.ts +1 -1
- package/src/scripts/build_linear_digest.ts +1 -1
- package/src/scripts/build_proof.ts +11 -4
- package/src/scripts/build_rule_trigger_matrix.ts +1 -1
- package/src/scripts/build_source_digests.ts +157 -0
- package/src/scripts/cache_realization_report.ts +103 -0
- package/src/scripts/check_adr_frontmatter.ts +126 -0
- package/src/scripts/check_benchmark_report_fields.ts +207 -0
- package/src/scripts/check_beta_review_markers.ts +235 -6
- package/src/scripts/check_bundle_path_leakage.ts +34 -1
- package/src/scripts/check_claims.ts +576 -6
- package/src/scripts/check_composite_arming.ts +35 -0
- package/src/scripts/check_concern_admissions.ts +365 -0
- package/src/scripts/check_enforcement_coverage.ts +28 -0
- package/src/scripts/check_estate_count.ts +142 -1
- package/src/scripts/check_gate_reachability.ts +383 -0
- package/src/scripts/check_host_loadability.ts +104 -0
- package/src/scripts/check_loss_class_declared.ts +308 -0
- package/src/scripts/check_md_language.ts +15 -98
- package/src/scripts/check_memory_proposal.ts +18 -11
- package/src/scripts/check_no_currency_in_cost_surfaces.ts +225 -0
- package/src/scripts/check_no_external_sources.ts +281 -6
- package/src/scripts/check_pack_conformance_fixture.ts +11 -1
- package/src/scripts/check_pack_size.ts +59 -2
- package/src/scripts/check_preamble_payload_budget.ts +114 -3
- package/src/scripts/check_prefix_stable_mutation.ts +372 -0
- package/src/scripts/check_publish_surface.ts +10 -1
- package/src/scripts/check_references.ts +24 -1
- package/src/scripts/check_rule_evidence_declaration.ts +316 -0
- package/src/scripts/check_rule_projection_integrity.ts +14 -4
- package/src/scripts/check_score_contract.ts +26 -3
- package/src/scripts/check_static_parity.ts +285 -0
- package/src/scripts/check_supervision_claim_atomicity.ts +498 -0
- package/src/scripts/check_suppression_hygiene.ts +15 -0
- package/src/scripts/ci_settle.ts +39 -2
- package/src/scripts/code_graph/build.ts +60 -3
- package/src/scripts/code_graph/extract.ts +44 -0
- package/src/scripts/code_graph/types.ts +24 -0
- package/src/scripts/collector_daemon.ts +714 -0
- package/src/scripts/config_cost_report.ts +302 -0
- package/src/scripts/consumer_matrix.ts +74 -1
- package/src/scripts/cost/budget.mjs +10 -3
- package/src/scripts/cost/track.mjs +10 -3
- package/src/scripts/council_cli.ts +37 -54
- package/src/scripts/dispatch_r2_reviewer.ts +46 -5
- package/src/scripts/evolution_lab.ts +980 -0
- package/src/scripts/external_sources_denylist.json +5 -18
- package/src/scripts/extract_audit_patterns.ts +21 -0
- package/src/scripts/fold_intake.ts +12 -1
- package/src/scripts/gen_discovery_baseline.ts +1 -1
- package/src/scripts/generate_capabilities_index.ts +12 -5
- package/src/scripts/generate_capability_matrix.ts +3 -3
- package/src/scripts/generate_command_flows.ts +2 -2
- package/src/scripts/generate_cookbook.ts +2 -2
- package/src/scripts/generate_index.ts +8 -1
- package/src/scripts/generate_knowledge_index.ts +2 -1
- package/src/scripts/generate_ownership_matrix.ts +2 -1
- package/src/scripts/generate_pack_manifests.ts +10 -2
- package/src/scripts/generate_role_experiences_catalog.ts +2 -2
- package/src/scripts/generate_settings_reference.ts +1 -1
- package/src/scripts/git_authorization_hook.ts +22 -1
- package/src/scripts/grade_target_readiness.ts +48 -5
- package/src/scripts/hook_effect_doctor.ts +261 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +98 -11
- package/src/scripts/hooks/block_speaking_inbox_dir.ts +271 -0
- package/src/scripts/hooks/block_unauthorized_git.ts +18 -0
- package/src/scripts/hooks/concern_registry.ts +4 -0
- package/src/scripts/hooks/dispatch_hook.ts +32 -0
- package/src/scripts/hooks/journal_record_hook.ts +345 -0
- package/src/scripts/hooks/prepush_metadata_sources.sh +110 -0
- package/src/scripts/hooks/run_continuation_hook.ts +44 -9
- package/src/scripts/hooks/shims/install.sh +61 -0
- package/src/scripts/hooks/shims/php +101 -0
- package/src/scripts/hooks/suggestion_capture_hook.ts +10 -0
- package/src/scripts/hot_context_hook.ts +16 -0
- package/src/scripts/install-hooks.sh +20 -5
- package/src/scripts/install.ts +17 -35
- package/src/scripts/inventory_frontmatter.ts +2 -1
- package/src/scripts/lint_adr_sweep_routing.ts +12 -0
- package/src/scripts/lint_canonical_terms.ts +604 -0
- package/src/scripts/lint_composition_review.ts +619 -0
- package/src/scripts/lint_consolidation_lineage.ts +666 -0
- package/src/scripts/lint_eval_specs.ts +593 -0
- package/src/scripts/lint_hook_manifest.ts +19 -0
- package/src/scripts/lint_host_portability.ts +373 -0
- package/src/scripts/lint_judge_prompt_expectation.ts +27 -1
- package/src/scripts/lint_memory_twin_parity.ts +335 -0
- package/src/scripts/lint_metric_consumers.ts +355 -0
- package/src/scripts/lint_skill_descriptions.ts +18 -1
- package/src/scripts/lint_skill_frontmatter_safety.ts +58 -0
- package/src/scripts/lint_skill_link_reach.ts +252 -0
- package/src/scripts/lint_skill_trigger_corpus.ts +426 -23
- package/src/scripts/lint_ticket_buildable.ts +17 -1
- package/src/scripts/mcp_server/consumer_tool_catalog.json +1 -1
- package/src/scripts/mcp_server/tool_catalog_source.ts +1 -1
- package/src/scripts/measure_docs_dead_links.ts +348 -0
- package/src/scripts/measure_host_capture.ts +461 -0
- package/src/scripts/migration_status.ts +3 -3
- package/src/scripts/model_rule_injection.ts +60 -0
- package/src/scripts/orchestration_record.ts +14 -0
- package/src/scripts/preamble_byte_census.ts +139 -10
- package/src/scripts/prepack-check.mjs +4 -1
- package/src/scripts/probe_turnaround.ts +384 -0
- package/src/scripts/probe_unblocked_ask.ts +279 -0
- package/src/scripts/render_benchmark_md.ts +1 -1
- package/src/scripts/rule_activation_census.ts +70 -9
- package/src/scripts/rule_backlinks.ts +1 -1
- package/src/scripts/run_lifecycle_suite.ts +208 -0
- package/src/scripts/run_skill_evals.ts +40 -0
- package/src/scripts/schemas/delivery-manifest.schema.json +43 -0
- package/src/scripts/schemas/drafting-channels.schema.json +43 -0
- package/src/scripts/schemas/evals.schema.json +24 -1
- package/src/scripts/schemas/rule.schema.json +52 -1
- package/src/scripts/schemas/skill.schema.json +17 -2
- package/src/scripts/skill_overlap.ts +7 -1
- package/src/scripts/skill_tools/run_block_d_eval.ts +8 -1
- package/src/scripts/skill_tools/score_skill_relevance.ts +84 -3
- package/src/scripts/skill_tools/suggest_skill_for_task.ts +8 -1
- package/src/scripts/skill_usage_report.ts +2 -1
- package/src/scripts/stubs_due.ts +211 -0
- package/src/scripts/sweep_source_surfaces.ts +678 -0
- package/src/scripts/sync_pr_branch.ts +3 -3
- package/src/scripts/trigger_eval_grandfather.json +202 -222
- package/src/scripts/utilization_report.ts +22 -2
- package/src/server/schemas/settings.ts +5 -0
- package/src/templates/marketing-copy.yml +1 -1
- package/dist/ui/assets/index-Cxgy63kz.js +0 -26
|
@@ -0,0 +1,664 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The supervised collector's record store — deletion, opt-out, retention, and
|
|
3
|
+
* the five upgrade transitions.
|
|
4
|
+
*
|
|
5
|
+
* `road-to-supervised-telemetry-collector` Phase 2 steps 2.3 and 2.4 (AC-4,
|
|
6
|
+
* AC-5). `_lib/collector_record.ts` fixed the record's SHAPE; nothing yet held a
|
|
7
|
+
* record, so "deletion and opt-out are implemented" had nowhere to be true.
|
|
8
|
+
* This is that somewhere, and it is deliberately the smallest thing that makes
|
|
9
|
+
* both steps' `verify:` lines executable rather than documentable.
|
|
10
|
+
*
|
|
11
|
+
* **Revision 2, after a blind R2 review found 12 findings (2 high).** The two
|
|
12
|
+
* high ones were evidence-strength failures on steps this roadmap had already
|
|
13
|
+
* flipped: the quarantine growth-budget test was tautological, and transition
|
|
14
|
+
* 2's "migration" was a version stamp with a test that could not detect that.
|
|
15
|
+
* Both are addressed below and each fix names the finding it closes.
|
|
16
|
+
*
|
|
17
|
+
* ## Class A, and provably so — this is NOT the collector
|
|
18
|
+
*
|
|
19
|
+
* Open, write or read, close, return. No resident process, no socket, no timer,
|
|
20
|
+
* no state surviving a call. The supervised process of Phase 4 is a *client* of
|
|
21
|
+
* this module; the module itself has the same shape as
|
|
22
|
+
* `_lib/runtime_journal.ts` and `_lib/test_red_state.ts`, so nothing here
|
|
23
|
+
* consumes the ADR-249 permission for a resident process. Phase 4 does that,
|
|
24
|
+
* with the four governance conditions attached.
|
|
25
|
+
*
|
|
26
|
+
* ## Where it lives — the uniqueness blocker's answer, executed
|
|
27
|
+
*
|
|
28
|
+
* `<user-root>/agent-collector/collector.sqlite`, where `<user-root>` defaults
|
|
29
|
+
* to `~/.event4u/agent-config/`. This is not a fresh choice: the
|
|
30
|
+
* `uniqueness-namespace` blocker resolved **(b) — exactly one collector per OS
|
|
31
|
+
* user** (AI council 2026-08-28, 2/2 convergent), on the ground that the
|
|
32
|
+
* uniqueness scope should match the state's scope and the configuration already
|
|
33
|
+
* lives under the user's home. A per-repository store would have contradicted
|
|
34
|
+
* that resolution while looking like a smaller decision.
|
|
35
|
+
*
|
|
36
|
+
* Two consequences worth naming rather than discovering:
|
|
37
|
+
*
|
|
38
|
+
* - **A repository is never discovered from the filesystem.** The same blocker
|
|
39
|
+
* forbids it explicitly. Records carry `machine_id` / `episode_id` and no
|
|
40
|
+
* path, so attribution survives multiplexing without the store ever holding a
|
|
41
|
+
* location. {@link CollectorRecord} could not hold one if it wanted to.
|
|
42
|
+
* - **The shared-machine case is a documented limitation, not a design.** A
|
|
43
|
+
* per-user store is the wrong boundary for a machine several people share,
|
|
44
|
+
* and the blocker records that as a known gap rather than solving it here.
|
|
45
|
+
*
|
|
46
|
+
* ## Opt-out prevents the WRITE, and that distinction is the whole step
|
|
47
|
+
*
|
|
48
|
+
* 2.3's second clause is *"opt-out prevents the write rather than filtering the
|
|
49
|
+
* read"*, which is a real architectural difference and not a phrasing
|
|
50
|
+
* preference. A read filter leaves the data on disk: the record exists, the
|
|
51
|
+
* consent choice is honoured only by whoever remembers to apply the filter, and
|
|
52
|
+
* a future reader that forgets is a leak. {@link writeRecord} refuses before it
|
|
53
|
+
* touches SQLite, and {@link isOptedOut} is checked there rather than by the
|
|
54
|
+
* caller, so a caller cannot forget.
|
|
55
|
+
*
|
|
56
|
+
* The marker is a FILE, not a settings key, and reachable without the
|
|
57
|
+
* collector's cooperation — the same property the kill switch of 3.3 needs. A
|
|
58
|
+
* user who cannot start the package can still `touch` it.
|
|
59
|
+
*
|
|
60
|
+
* **Cost, recorded rather than left for 3.2 to discover (R2 finding 12):** the
|
|
61
|
+
* check is one `existsSync` per write, i.e. one stat syscall on the write path.
|
|
62
|
+
* That is a deliberate trade: honouring a mid-session opt-out immediately is
|
|
63
|
+
* worth a stat, and caching the marker's value for the life of a handle would
|
|
64
|
+
* mean a user who opts out during a session keeps being recorded until it ends.
|
|
65
|
+
* The syscall is a line item owed to step 3.2's CPU budget, not a defect.
|
|
66
|
+
*
|
|
67
|
+
* ## Deduplication is a READ, and the duplicate stays visible
|
|
68
|
+
*
|
|
69
|
+
* Metric item 4 of the roadmap's own definition: *"Deduplication happens at read
|
|
70
|
+
* time over the store, never at write time, so a duplicate is observable as a
|
|
71
|
+
* defect rather than silently collapsed."* The first implementation used a
|
|
72
|
+
* `PRIMARY KEY` on the dedup key plus `INSERT OR REPLACE`, which satisfies the
|
|
73
|
+
* item's first sentence by violating its second — the retry is absorbed and
|
|
74
|
+
* nobody can see it happened. Self-caught before the review. The table is
|
|
75
|
+
* append-only now, {@link readRecords} collapses repeats with the first
|
|
76
|
+
* occurrence winning, and {@link readSummary} reports `rows`, `unique` and
|
|
77
|
+
* `duplicates` so a non-zero duplicate count is a defect somebody can find.
|
|
78
|
+
*
|
|
79
|
+
* ## Retention — declared, because an append-only table without one is a leak
|
|
80
|
+
*
|
|
81
|
+
* R2 finding 8: `collector_events` grows per event and had no TTL, pruning job,
|
|
82
|
+
* partition rotation or archive path, while this module reasoned about R-A7 for
|
|
83
|
+
* the neighbouring quarantine *directory*. {@link RETENTION_DAYS} is the
|
|
84
|
+
* declared policy and {@link pruneOlderThan} is the job. The number is the
|
|
85
|
+
* metric definition's own hard stop (item 8: the observation window extends to
|
|
86
|
+
* a hard stop at 63 days), so a record that can no longer belong to any live
|
|
87
|
+
* window has no reason to be kept. The DISK CEILING remains step 3.2's, and a
|
|
88
|
+
* retention policy is not a ceiling.
|
|
89
|
+
*
|
|
90
|
+
* ## Deletion is a path, exercised
|
|
91
|
+
*
|
|
92
|
+
* The governance roadmap's first draft required deletion to be *documented*. A
|
|
93
|
+
* documented deletion path that nobody executed is a claim, so
|
|
94
|
+
* {@link deleteMachine} is the supported path and the tests drive it and then
|
|
95
|
+
* assert the store no longer serves those records — including through
|
|
96
|
+
* {@link readRecords} with no filter, because "gone" has to mean gone from the
|
|
97
|
+
* store and not merely absent from one query.
|
|
98
|
+
*/
|
|
99
|
+
|
|
100
|
+
import * as crypto from 'node:crypto';
|
|
101
|
+
import * as fs from 'node:fs';
|
|
102
|
+
import * as os from 'node:os';
|
|
103
|
+
import * as path from 'node:path';
|
|
104
|
+
import type { DatabaseSync } from 'node:sqlite';
|
|
105
|
+
|
|
106
|
+
import {
|
|
107
|
+
type CollectorRecord,
|
|
108
|
+
COLLECTOR_SCHEMA_VERSION,
|
|
109
|
+
dedupKey,
|
|
110
|
+
validateRecord,
|
|
111
|
+
} from './collector_record.js';
|
|
112
|
+
import {
|
|
113
|
+
isSqliteAvailableSync,
|
|
114
|
+
loadSqliteSync,
|
|
115
|
+
readUserVersion,
|
|
116
|
+
stampUserVersion,
|
|
117
|
+
} from './sqlite_guard.js';
|
|
118
|
+
|
|
119
|
+
/** Directory name under the user root. */
|
|
120
|
+
export const COLLECTOR_DIR_NAME = 'agent-collector';
|
|
121
|
+
/** The database file. */
|
|
122
|
+
export const COLLECTOR_FILE_NAME = 'collector.sqlite';
|
|
123
|
+
/** The opt-out marker. Its presence is the whole signal; content is ignored. */
|
|
124
|
+
export const OPT_OUT_MARKER_NAME = 'OPT-OUT';
|
|
125
|
+
/** Written while a migration is in flight; its presence means "crashed mid-migration". */
|
|
126
|
+
export const MIGRATION_MARKER_NAME = 'MIGRATION-IN-FLIGHT';
|
|
127
|
+
/** Where a store this revision cannot read is moved. Never deleted. */
|
|
128
|
+
export const QUARANTINE_DIR_NAME = 'quarantine';
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Declared retention for `collector_events`, in days.
|
|
132
|
+
*
|
|
133
|
+
* 63 is the metric definition's hard stop for the observation window (item 8:
|
|
134
|
+
* 21 days, extending in 7-day increments to a hard stop at 63). A record older
|
|
135
|
+
* than that cannot belong to any live window, so keeping it serves nothing.
|
|
136
|
+
*/
|
|
137
|
+
export const RETENTION_DAYS = 63;
|
|
138
|
+
|
|
139
|
+
/** Identifies this module in `sqlite_guard`'s unavailability message. */
|
|
140
|
+
const CALLER = 'collector_store';
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Raised when the store cannot be opened at all — today, only because
|
|
144
|
+
* `node:sqlite` is unavailable in the runtime.
|
|
145
|
+
*
|
|
146
|
+
* Named for what it is rather than for what a wider class might one day be:
|
|
147
|
+
* R2 finding 11 caught the previous docstring ("asked to do something the
|
|
148
|
+
* contract forbids") describing a class this error has never had a member of.
|
|
149
|
+
*/
|
|
150
|
+
export class CollectorStoreError extends Error {}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Why a write did not land. A blind zero is what this enum prevents.
|
|
154
|
+
*
|
|
155
|
+
* `sqlite-unavailable` was removed (R2 finding 11): no code path returned it,
|
|
156
|
+
* because unavailability throws from {@link openCollectorStore} before a write
|
|
157
|
+
* is reachable. An unreachable member forces an exhaustive consumer switch to
|
|
158
|
+
* handle a case that cannot occur, while the real failure arrives as an
|
|
159
|
+
* exception it does not handle.
|
|
160
|
+
*/
|
|
161
|
+
export type WriteRefusal = 'opted-out' | 'invalid-record' | 'schema-quarantined';
|
|
162
|
+
|
|
163
|
+
export interface WriteOutcome {
|
|
164
|
+
written: boolean;
|
|
165
|
+
/** Set when `written` is false. Never null-and-written. */
|
|
166
|
+
refusal: WriteRefusal | null;
|
|
167
|
+
/** Validation errors, when the refusal is `invalid-record`. */
|
|
168
|
+
errors: readonly string[];
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
export interface StoreLocation {
|
|
172
|
+
/** The user root the store hangs off. Carried so nothing has to re-derive it. */
|
|
173
|
+
readonly userRoot: string;
|
|
174
|
+
readonly root: string;
|
|
175
|
+
readonly path: string;
|
|
176
|
+
readonly optOutMarker: string;
|
|
177
|
+
readonly migrationMarker: string;
|
|
178
|
+
readonly quarantineDir: string;
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/** The default user root — where configuration already lives. */
|
|
182
|
+
export function defaultUserRoot(): string {
|
|
183
|
+
return path.join(os.homedir(), '.event4u', 'agent-config');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Resolve every path the store uses. Pure: creates nothing. */
|
|
187
|
+
export function resolveCollectorStore(userRoot: string = defaultUserRoot()): StoreLocation {
|
|
188
|
+
const dir = path.join(userRoot, COLLECTOR_DIR_NAME);
|
|
189
|
+
return {
|
|
190
|
+
userRoot,
|
|
191
|
+
root: dir,
|
|
192
|
+
path: path.join(dir, COLLECTOR_FILE_NAME),
|
|
193
|
+
optOutMarker: path.join(dir, OPT_OUT_MARKER_NAME),
|
|
194
|
+
migrationMarker: path.join(dir, MIGRATION_MARKER_NAME),
|
|
195
|
+
quarantineDir: path.join(dir, QUARANTINE_DIR_NAME),
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/** True when this user has opted out. Checked at write time, never at read. */
|
|
200
|
+
export function isOptedOut(userRoot: string = defaultUserRoot()): boolean {
|
|
201
|
+
return fs.existsSync(resolveCollectorStore(userRoot).optOutMarker);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Create the opt-out marker. Idempotent. */
|
|
205
|
+
export function optOut(userRoot: string = defaultUserRoot()): void {
|
|
206
|
+
const loc = resolveCollectorStore(userRoot);
|
|
207
|
+
fs.mkdirSync(loc.root, { recursive: true });
|
|
208
|
+
fs.writeFileSync(
|
|
209
|
+
loc.optOutMarker,
|
|
210
|
+
'This user has opted out of collector capture. Delete this file to opt back in.\n',
|
|
211
|
+
);
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/** Remove the opt-out marker. Idempotent. */
|
|
215
|
+
export function optIn(userRoot: string = defaultUserRoot()): void {
|
|
216
|
+
fs.rmSync(resolveCollectorStore(userRoot).optOutMarker, { force: true });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export interface StoreHandle {
|
|
220
|
+
readonly db: DatabaseSync;
|
|
221
|
+
readonly location: StoreLocation;
|
|
222
|
+
/**
|
|
223
|
+
* True when **this open** moved a store aside it could not read, and is
|
|
224
|
+
* handing back a fresh one.
|
|
225
|
+
*
|
|
226
|
+
* SESSION-SCOPED BY DESIGN, and named for it (R2 finding 4). The previous
|
|
227
|
+
* name (`quarantined`) implied a durable lockout it never was: the fresh
|
|
228
|
+
* store is stamped at the current version, so the NEXT open over the same
|
|
229
|
+
* on-disk state returns `false` and writes land. That is correct — the
|
|
230
|
+
* incompatible store is preserved and the fallback is usable — but "one-shot
|
|
231
|
+
* signal" and "durable refusal" were indistinguishable from the code, so a
|
|
232
|
+
* test now pins the post-restart behaviour explicitly.
|
|
233
|
+
*
|
|
234
|
+
* Writes through THIS handle are refused so the incompatible-schema event
|
|
235
|
+
* reaches the caller instead of being absorbed by a silent fallback.
|
|
236
|
+
*/
|
|
237
|
+
readonly quarantine_performed: boolean;
|
|
238
|
+
close(): void;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* APPEND-ONLY, and `dedup_key` is deliberately NOT unique.
|
|
243
|
+
*
|
|
244
|
+
* Metric item 4 requires read-time dedup so a duplicate is observable. A
|
|
245
|
+
* `PRIMARY KEY` on `dedup_key` plus `INSERT OR REPLACE` satisfies "a repeated
|
|
246
|
+
* key is one record" by violating "observable as a defect", which is the shape
|
|
247
|
+
* of contract this whole roadmap exists to refuse. So the table accepts both
|
|
248
|
+
* rows, {@link readRecords} collapses them, and {@link readSummary} reports how
|
|
249
|
+
* many were collapsed.
|
|
250
|
+
*/
|
|
251
|
+
const SCHEMA = `
|
|
252
|
+
CREATE TABLE IF NOT EXISTS collector_events (
|
|
253
|
+
row_id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
254
|
+
dedup_key TEXT NOT NULL,
|
|
255
|
+
schema_version INTEGER NOT NULL,
|
|
256
|
+
machine_id TEXT NOT NULL,
|
|
257
|
+
episode_id TEXT NOT NULL,
|
|
258
|
+
event TEXT NOT NULL,
|
|
259
|
+
sequence INTEGER NOT NULL,
|
|
260
|
+
outcome TEXT NOT NULL,
|
|
261
|
+
platform TEXT NOT NULL,
|
|
262
|
+
occurred_on TEXT NOT NULL,
|
|
263
|
+
collector_version TEXT NOT NULL
|
|
264
|
+
);
|
|
265
|
+
CREATE INDEX IF NOT EXISTS collector_events_dedup
|
|
266
|
+
ON collector_events (dedup_key);
|
|
267
|
+
CREATE INDEX IF NOT EXISTS collector_events_machine
|
|
268
|
+
ON collector_events (machine_id, occurred_on);
|
|
269
|
+
`;
|
|
270
|
+
|
|
271
|
+
/**
|
|
272
|
+
* One migration step: `from` → `from + 1`. Registered explicitly, so a version
|
|
273
|
+
* with no registered step is a REFUSAL rather than a silent stamp.
|
|
274
|
+
*
|
|
275
|
+
* R2 finding 2, and it is the finding that mattered most. The previous
|
|
276
|
+
* implementation ran `db.exec(SCHEMA)` — `CREATE TABLE IF NOT EXISTS`, a no-op
|
|
277
|
+
* on an existing table of ANY shape — and then stamped `user_version` forward.
|
|
278
|
+
* The whole "migration" was the stamp, and its test seeded only the version
|
|
279
|
+
* integer backwards over the CURRENT table shape, so it passed for a stamp-only
|
|
280
|
+
* implementation and could never have caught the missing DDL. The first real
|
|
281
|
+
* column change would have stamped an old-shaped store as current.
|
|
282
|
+
*
|
|
283
|
+
* The ladder makes the mechanism real and falsifiable. The one registered step
|
|
284
|
+
* (0 → 1) is genuinely a no-op **because no schema delta exists yet**, and that
|
|
285
|
+
* is stated here rather than implied by an empty function body: version 0 is the
|
|
286
|
+
* un-stamped state a database gets before this module writes to it, and the
|
|
287
|
+
* table it lands with is already the v1 shape.
|
|
288
|
+
*/
|
|
289
|
+
export type MigrationStep = (db: DatabaseSync) => void;
|
|
290
|
+
|
|
291
|
+
export const MIGRATIONS: Readonly<Record<number, MigrationStep>> = Object.freeze({
|
|
292
|
+
// 0 → 1. No DDL: `user_version` 0 is the default SQLite assigns, and a
|
|
293
|
+
// database created by this module is already at the v1 shape. The entry
|
|
294
|
+
// exists so the path RESOLVES rather than being inferred from its absence.
|
|
295
|
+
0: () => {},
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* The steps needed to walk `from` → `to`, or `null` when the walk is impossible.
|
|
300
|
+
*
|
|
301
|
+
* Pure, exported, and tested directly — which is what makes the ladder
|
|
302
|
+
* falsifiable without seeding a database per hypothesis. `null` means the caller
|
|
303
|
+
* must quarantine: refusing an unmigratable store is the safe branch, and
|
|
304
|
+
* stamping it is the unsafe one this replaces.
|
|
305
|
+
*/
|
|
306
|
+
export function migrationPath(from: number, to: number): number[] | null {
|
|
307
|
+
if (from > to) return null; // a NEWER store; quarantine, never downgrade
|
|
308
|
+
const steps: number[] = [];
|
|
309
|
+
for (let v = from; v < to; v++) {
|
|
310
|
+
if (!(v in MIGRATIONS)) return null;
|
|
311
|
+
steps.push(v);
|
|
312
|
+
}
|
|
313
|
+
return steps;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** Is `node:sqlite` usable in this runtime? */
|
|
317
|
+
export function isStoreAvailable(): boolean {
|
|
318
|
+
return isSqliteAvailableSync();
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Move a store this revision cannot read aside, preserving it byte-for-byte.
|
|
323
|
+
*
|
|
324
|
+
* 3.1's rollback matrix requires an incompatible schema to be *preserved
|
|
325
|
+
* without reading or rewriting*. Quarantine is that: a rename, and the only
|
|
326
|
+
* read is a whole-file digest to name the destination — **the bytes are never
|
|
327
|
+
* interpreted as records**, which is the claim row 5 means and the earlier
|
|
328
|
+
* wording ("a rename, never a read") overstated (R2 finding 10). The digest read
|
|
329
|
+
* is a line item owed to step 3.2's disk budget.
|
|
330
|
+
*
|
|
331
|
+
* The name carries a CONTENT DIGEST rather than a timestamp so two quarantines
|
|
332
|
+
* of the same bytes collapse instead of accumulating — R-A7 applied to a
|
|
333
|
+
* directory nobody prunes.
|
|
334
|
+
*
|
|
335
|
+
* WAL sidecars are **moved, not deleted** (R2 finding 6). Deleting them would
|
|
336
|
+
* drop any committed data still in the WAL while a byte-equality assertion on
|
|
337
|
+
* the main file stayed green — preserving the file and discarding its tail is
|
|
338
|
+
* not preservation.
|
|
339
|
+
*
|
|
340
|
+
* **Measured while fixing it, and it demotes the finding rather than confirming
|
|
341
|
+
* it:** the loss is NOT reachable through {@link openCollectorStore}. That
|
|
342
|
+
* function opens the database before reading its version, and opening removes an
|
|
343
|
+
* unrecognised `-wal` (a real WAL-mode close checkpoints its contents into the
|
|
344
|
+
* main file first), so by the time this function runs there is no sidecar left
|
|
345
|
+
* to lose. No `PRAGMA journal_mode = WAL` is set anywhere either. Two tests pin
|
|
346
|
+
* both halves — the move, called directly, and the removal-on-open that makes
|
|
347
|
+
* the end-to-end path unreachable — so enabling WAL later reds the second one
|
|
348
|
+
* and re-opens the question deliberately instead of silently.
|
|
349
|
+
*/
|
|
350
|
+
export function quarantine(location: StoreLocation): string {
|
|
351
|
+
fs.mkdirSync(location.quarantineDir, { recursive: true });
|
|
352
|
+
const digest = crypto
|
|
353
|
+
.createHash('sha256')
|
|
354
|
+
.update(fs.readFileSync(location.path))
|
|
355
|
+
.digest('hex')
|
|
356
|
+
.slice(0, 12);
|
|
357
|
+
const target = path.join(location.quarantineDir, `${COLLECTOR_FILE_NAME}.${digest}`);
|
|
358
|
+
if (fs.existsSync(target)) {
|
|
359
|
+
// Same bytes already preserved. Drop the duplicate rather than growing
|
|
360
|
+
// the directory: the artefact is already there, byte-identical.
|
|
361
|
+
fs.rmSync(location.path, { force: true });
|
|
362
|
+
for (const sidecar of ['-wal', '-shm']) {
|
|
363
|
+
fs.rmSync(`${location.path}${sidecar}`, { force: true });
|
|
364
|
+
}
|
|
365
|
+
return target;
|
|
366
|
+
}
|
|
367
|
+
fs.renameSync(location.path, target);
|
|
368
|
+
for (const sidecar of ['-wal', '-shm']) {
|
|
369
|
+
const from = `${location.path}${sidecar}`;
|
|
370
|
+
if (fs.existsSync(from)) fs.renameSync(from, `${target}${sidecar}`);
|
|
371
|
+
}
|
|
372
|
+
return target;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
/**
|
|
376
|
+
* Open the store, migrating or quarantining as the on-disk version requires.
|
|
377
|
+
*
|
|
378
|
+
* The five transitions of 2.4 all resolve here:
|
|
379
|
+
*
|
|
380
|
+
* 1. **Fresh store** — created at {@link COLLECTOR_SCHEMA_VERSION}.
|
|
381
|
+
* 2. **Older records, newer package** — walked forward through
|
|
382
|
+
* {@link MIGRATIONS} inside a marked window. A version with NO registered
|
|
383
|
+
* path is quarantined, not stamped.
|
|
384
|
+
* 3. **Newer records, older package** — QUARANTINED. Never read as records,
|
|
385
|
+
* never rewritten. Reading a record shape you do not understand is how a
|
|
386
|
+
* field gets silently dropped, and dropping is what this schema refuses.
|
|
387
|
+
* 4. **Crash mid-migration** — the marker survives the crash, so the next open
|
|
388
|
+
* finds a half-migrated store and quarantines it rather than resuming a
|
|
389
|
+
* migration whose progress nothing recorded.
|
|
390
|
+
* 5. **Uninstall** — {@link uninstall}, which removes the store and leaves the
|
|
391
|
+
* quarantine directory, because a quarantined store is evidence.
|
|
392
|
+
*/
|
|
393
|
+
export function openCollectorStore(userRoot: string = defaultUserRoot()): StoreHandle {
|
|
394
|
+
if (!isSqliteAvailableSync()) {
|
|
395
|
+
throw new CollectorStoreError('node:sqlite is unavailable in this runtime');
|
|
396
|
+
}
|
|
397
|
+
const location = resolveCollectorStore(userRoot);
|
|
398
|
+
fs.mkdirSync(location.root, { recursive: true });
|
|
399
|
+
const sqlite = loadSqliteSync(CALLER);
|
|
400
|
+
|
|
401
|
+
// Transition 4 comes FIRST: a store carrying an in-flight marker is
|
|
402
|
+
// half-migrated by definition, and its version stamp cannot be trusted to
|
|
403
|
+
// say which half. Checked before the version is even read.
|
|
404
|
+
//
|
|
405
|
+
// The marker is cleared whenever it EXISTS, independent of the database
|
|
406
|
+
// (R2 finding 3). Previously the guard required marker AND db, while
|
|
407
|
+
// `quarantine()` renames the db away before the marker is removed — so a
|
|
408
|
+
// crash between those two steps left marker-present / db-absent, the next
|
|
409
|
+
// open took the fresh-store branch and never cleared it, and the open after
|
|
410
|
+
// that quarantined a healthy store.
|
|
411
|
+
let markerQuarantined = false;
|
|
412
|
+
if (fs.existsSync(location.migrationMarker)) {
|
|
413
|
+
if (fs.existsSync(location.path)) {
|
|
414
|
+
quarantine(location);
|
|
415
|
+
markerQuarantined = true;
|
|
416
|
+
}
|
|
417
|
+
fs.rmSync(location.migrationMarker, { force: true });
|
|
418
|
+
}
|
|
419
|
+
if (markerQuarantined) {
|
|
420
|
+
const db = new sqlite.DatabaseSync(location.path);
|
|
421
|
+
db.exec(SCHEMA);
|
|
422
|
+
stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
|
|
423
|
+
return handleFor(db, location, false);
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
const existed = fs.existsSync(location.path);
|
|
427
|
+
const db = new sqlite.DatabaseSync(location.path);
|
|
428
|
+
|
|
429
|
+
if (!existed) {
|
|
430
|
+
db.exec(SCHEMA);
|
|
431
|
+
stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
|
|
432
|
+
return handleFor(db, location, false);
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
const onDisk = readUserVersion(db);
|
|
436
|
+
const steps = migrationPath(onDisk, COLLECTOR_SCHEMA_VERSION);
|
|
437
|
+
|
|
438
|
+
if (steps === null) {
|
|
439
|
+
// Transition 3, and the no-registered-path case with it. Close before
|
|
440
|
+
// renaming: a quarantine that leaves an open handle on the moved file is
|
|
441
|
+
// a rename that looks successful and is not.
|
|
442
|
+
db.close();
|
|
443
|
+
quarantine(location);
|
|
444
|
+
const fresh = new sqlite.DatabaseSync(location.path);
|
|
445
|
+
fresh.exec(SCHEMA);
|
|
446
|
+
stampUserVersion(fresh, COLLECTOR_SCHEMA_VERSION);
|
|
447
|
+
return handleFor(fresh, location, true);
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
if (steps.length > 0) {
|
|
451
|
+
// Transition 2, inside a marked window so transition 4 can see a crash.
|
|
452
|
+
fs.writeFileSync(
|
|
453
|
+
location.migrationMarker,
|
|
454
|
+
`${onDisk} -> ${COLLECTOR_SCHEMA_VERSION} via ${steps.join(',')}\n`,
|
|
455
|
+
);
|
|
456
|
+
try {
|
|
457
|
+
db.exec(SCHEMA);
|
|
458
|
+
for (const step of steps) {
|
|
459
|
+
(MIGRATIONS[step] as MigrationStep)(db);
|
|
460
|
+
}
|
|
461
|
+
stampUserVersion(db, COLLECTOR_SCHEMA_VERSION);
|
|
462
|
+
} finally {
|
|
463
|
+
fs.rmSync(location.migrationMarker, { force: true });
|
|
464
|
+
}
|
|
465
|
+
return handleFor(db, location, false);
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
db.exec(SCHEMA);
|
|
469
|
+
return handleFor(db, location, false);
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
function handleFor(
|
|
473
|
+
db: DatabaseSync,
|
|
474
|
+
location: StoreLocation,
|
|
475
|
+
quarantine_performed: boolean,
|
|
476
|
+
): StoreHandle {
|
|
477
|
+
return {
|
|
478
|
+
db,
|
|
479
|
+
location,
|
|
480
|
+
quarantine_performed,
|
|
481
|
+
close(): void {
|
|
482
|
+
db.close();
|
|
483
|
+
},
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
/**
|
|
488
|
+
* Write one record, or refuse with a named reason.
|
|
489
|
+
*
|
|
490
|
+
* Opt-out is checked HERE and not by the caller, which is the point: a consent
|
|
491
|
+
* check a caller can forget is a consent check that will be forgotten.
|
|
492
|
+
*/
|
|
493
|
+
export function writeRecord(handle: StoreHandle, candidate: unknown): WriteOutcome {
|
|
494
|
+
if (isOptedOut(handle.location.userRoot)) {
|
|
495
|
+
return { written: false, refusal: 'opted-out', errors: [] };
|
|
496
|
+
}
|
|
497
|
+
if (handle.quarantine_performed) {
|
|
498
|
+
return { written: false, refusal: 'schema-quarantined', errors: [] };
|
|
499
|
+
}
|
|
500
|
+
const verdict = validateRecord(candidate);
|
|
501
|
+
if (!verdict.ok) {
|
|
502
|
+
return { written: false, refusal: 'invalid-record', errors: verdict.errors };
|
|
503
|
+
}
|
|
504
|
+
const record = candidate as CollectorRecord;
|
|
505
|
+
handle.db
|
|
506
|
+
.prepare(
|
|
507
|
+
`INSERT INTO collector_events
|
|
508
|
+
(dedup_key, schema_version, machine_id, episode_id, event, sequence,
|
|
509
|
+
outcome, platform, occurred_on, collector_version)
|
|
510
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
511
|
+
)
|
|
512
|
+
.run(
|
|
513
|
+
dedupKey(record),
|
|
514
|
+
record.schema_version,
|
|
515
|
+
record.machine_id,
|
|
516
|
+
record.episode_id,
|
|
517
|
+
record.event,
|
|
518
|
+
record.sequence,
|
|
519
|
+
record.outcome,
|
|
520
|
+
record.platform,
|
|
521
|
+
record.occurred_on,
|
|
522
|
+
record.collector_version,
|
|
523
|
+
);
|
|
524
|
+
return { written: true, refusal: null, errors: [] };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
/** What a read collapsed, so a duplicate is observable rather than invisible. */
|
|
528
|
+
export interface StoreSummary {
|
|
529
|
+
/** Rows physically present. */
|
|
530
|
+
rows: number;
|
|
531
|
+
/** Distinct `(machine_id, episode_id, event, sequence)` keys among them. */
|
|
532
|
+
unique: number;
|
|
533
|
+
/** `rows - unique`. Non-zero is a DEFECT to investigate, not a tidy-up. */
|
|
534
|
+
duplicates: number;
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
/**
|
|
538
|
+
* Row and key counts for the whole store.
|
|
539
|
+
*
|
|
540
|
+
* This is the observability half of metric item 4. Read-time dedup without a
|
|
541
|
+
* duplicate count would hide the defect just as effectively as a write-time
|
|
542
|
+
* `INSERT OR REPLACE` — the records would be collapsed by a different mechanism
|
|
543
|
+
* and nobody would know a retry had double-written.
|
|
544
|
+
*/
|
|
545
|
+
export function readSummary(handle: StoreHandle): StoreSummary {
|
|
546
|
+
if (handle.quarantine_performed) return { rows: 0, unique: 0, duplicates: 0 };
|
|
547
|
+
const row = handle.db
|
|
548
|
+
.prepare('SELECT COUNT(*) AS n, COUNT(DISTINCT dedup_key) AS uniq FROM collector_events')
|
|
549
|
+
.get() as { n: number; uniq: number };
|
|
550
|
+
return { rows: row.n, unique: row.uniq, duplicates: row.n - row.uniq };
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
/**
|
|
554
|
+
* Every record, or every record for one machine. Never filtered by consent.
|
|
555
|
+
*
|
|
556
|
+
* DEDUPLICATED AT READ TIME, first occurrence by insertion order winning — the
|
|
557
|
+
* metric definition's item 4. The rows themselves stay on disk, so
|
|
558
|
+
* {@link readSummary} can still say a duplicate happened.
|
|
559
|
+
*/
|
|
560
|
+
export function readRecords(handle: StoreHandle, machineId?: string): CollectorRecord[] {
|
|
561
|
+
if (handle.quarantine_performed) return [];
|
|
562
|
+
const columns = `dedup_key, schema_version, machine_id, episode_id, event, sequence,
|
|
563
|
+
outcome, platform, occurred_on, collector_version`;
|
|
564
|
+
const rows = (
|
|
565
|
+
machineId === undefined
|
|
566
|
+
? handle.db.prepare(`SELECT ${columns} FROM collector_events ORDER BY row_id`).all()
|
|
567
|
+
: handle.db
|
|
568
|
+
.prepare(
|
|
569
|
+
`SELECT ${columns} FROM collector_events WHERE machine_id = ?
|
|
570
|
+
ORDER BY row_id`,
|
|
571
|
+
)
|
|
572
|
+
.all(machineId)
|
|
573
|
+
) as unknown as ({ dedup_key: string } & CollectorRecord)[];
|
|
574
|
+
|
|
575
|
+
const seen = new Set<string>();
|
|
576
|
+
const out: CollectorRecord[] = [];
|
|
577
|
+
for (const row of rows) {
|
|
578
|
+
if (seen.has(row.dedup_key)) continue;
|
|
579
|
+
seen.add(row.dedup_key);
|
|
580
|
+
const { dedup_key: _ignored, ...record } = row;
|
|
581
|
+
out.push(record);
|
|
582
|
+
}
|
|
583
|
+
return out;
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
/**
|
|
587
|
+
* The supported deletion path. Returns how many ROWS were removed.
|
|
588
|
+
*
|
|
589
|
+
* A GDPR Art. 17 request names a subject, and on this store the subject is a
|
|
590
|
+
* machine — `machine_id` is the only identity a record carries. Deletion is
|
|
591
|
+
* therefore by machine and nothing coarser is offered: a "delete everything"
|
|
592
|
+
* verb would make a targeted request impossible to serve honestly.
|
|
593
|
+
*
|
|
594
|
+
* The count is the DELETE's own `changes` (R2 finding 7). It used to be a
|
|
595
|
+
* pre-delete SELECT count, which over-reports the moment the two disagree — on
|
|
596
|
+
* the one path this module frames as serving a legal request — and materialised
|
|
597
|
+
* every matching row into objects purely to take `.length`.
|
|
598
|
+
*/
|
|
599
|
+
export function deleteMachine(handle: StoreHandle, machineId: string): number {
|
|
600
|
+
if (handle.quarantine_performed) return 0;
|
|
601
|
+
const result = handle.db
|
|
602
|
+
.prepare('DELETE FROM collector_events WHERE machine_id = ?')
|
|
603
|
+
.run(machineId);
|
|
604
|
+
return Number(result.changes);
|
|
605
|
+
}
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* Drop records older than the declared retention. Returns rows removed.
|
|
609
|
+
*
|
|
610
|
+
* The R-A7 obligation this module owed and did not have (R2 finding 8): an
|
|
611
|
+
* append-only table must declare retention as a TTL, a pruning job, a partition
|
|
612
|
+
* rotation or an archive path. This is the job; {@link RETENTION_DAYS} is the
|
|
613
|
+
* policy. `today` is passed in rather than read from the clock so the boundary
|
|
614
|
+
* is testable without freezing time.
|
|
615
|
+
*
|
|
616
|
+
* Compares `occurred_on`, a UTC calendar DATE, as a string — which is why the
|
|
617
|
+
* record's date field is a date and not a timestamp.
|
|
618
|
+
*/
|
|
619
|
+
export function pruneOlderThan(
|
|
620
|
+
handle: StoreHandle,
|
|
621
|
+
today: string,
|
|
622
|
+
days: number = RETENTION_DAYS,
|
|
623
|
+
): number {
|
|
624
|
+
if (handle.quarantine_performed) return 0;
|
|
625
|
+
const cutoff = new Date(Date.parse(`${today}T00:00:00Z`) - days * 86_400_000)
|
|
626
|
+
.toISOString()
|
|
627
|
+
.slice(0, 10);
|
|
628
|
+
const result = handle.db
|
|
629
|
+
.prepare('DELETE FROM collector_events WHERE occurred_on < ?')
|
|
630
|
+
.run(cutoff);
|
|
631
|
+
return Number(result.changes);
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/**
|
|
635
|
+
* What uninstall removes: the database, its sidecars, and the migration marker.
|
|
636
|
+
*
|
|
637
|
+
* It does NOT remove the quarantine directory. A quarantined store is the
|
|
638
|
+
* record of an incompatible-schema event, and uninstall is not the moment to
|
|
639
|
+
* destroy evidence about it.
|
|
640
|
+
*
|
|
641
|
+
* It does NOT remove the **opt-out marker**, and that is a decision rather than
|
|
642
|
+
* an omission (R2 finding 5): reinstalling must not silently revoke a consent
|
|
643
|
+
* choice the user made, so opt-out outlives uninstall. The previous docstring
|
|
644
|
+
* said "the markers", plural, which would have invited a future edit to
|
|
645
|
+
* reconcile code to prose by deleting it. Named, and tested.
|
|
646
|
+
*
|
|
647
|
+
* Returns the paths actually removed.
|
|
648
|
+
*/
|
|
649
|
+
export function uninstall(userRoot: string = defaultUserRoot()): string[] {
|
|
650
|
+
const location = resolveCollectorStore(userRoot);
|
|
651
|
+
const removed: string[] = [];
|
|
652
|
+
for (const target of [
|
|
653
|
+
location.path,
|
|
654
|
+
`${location.path}-wal`,
|
|
655
|
+
`${location.path}-shm`,
|
|
656
|
+
location.migrationMarker,
|
|
657
|
+
]) {
|
|
658
|
+
if (fs.existsSync(target)) {
|
|
659
|
+
fs.rmSync(target, { force: true });
|
|
660
|
+
removed.push(target);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
return removed;
|
|
664
|
+
}
|