@event4u/agent-config 15.0.0 → 16.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.augment-plugin/marketplace.json +3 -3
- package/.augment-plugin/plugin.json +1 -1
- package/.claude-plugin/marketplace.json +1 -1
- package/CHANGELOG.md +301 -347
- 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 +77 -29
- 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/contexts/authority/scope-growth-ownership.md +80 -0
- package/dist/agent-src/contexts/communication/rules-auto/user-interaction-mechanics.md +97 -0
- package/dist/agent-src/contexts/execution/auto-orchestration-activation.md +44 -3
- package/dist/agent-src/contexts/execution/autonomy-mechanics.md +15 -15
- 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/user-interaction.md +31 -30
- 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 +50 -0
- package/dist/agent-src/skills/error-handling-patterns/SKILL.md +9 -0
- package/dist/agent-src/skills/existing-ui-audit/SKILL.md +14 -4
- package/dist/agent-src/skills/experiment-loop/SKILL.md +31 -1
- 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/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 +188 -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/cli-delegate/check_update_banner.js +40 -2
- package/dist/cli-delegate/chunk-4E2W63DA.js +335 -0
- 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-2XY7ILBT.js → chunk-FC75PGJE.js} +42 -2
- package/dist/cli-delegate/{chunk-GYC3VRYN.js → chunk-FXYUYU4N.js} +57 -13
- package/dist/cli-delegate/{chunk-55ACLDUE.js → chunk-GJCBB2UG.js} +168 -10
- package/dist/cli-delegate/{chunk-AK6HODNQ.js → chunk-HDVVGPQW.js} +873 -539
- package/dist/cli-delegate/chunk-NUHLI2RX.js +80 -0
- package/dist/cli-delegate/{chunk-HZ52BRGG.js → chunk-P6ZHP5W2.js} +15 -90
- package/dist/cli-delegate/{chunk-TYSY33MD.js → chunk-QJWAUMJW.js} +12 -2
- 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-2KKXJW25.js → chunk-S3VFXMTM.js} +1 -1
- package/dist/cli-delegate/{chunk-3FKR64R5.js → chunk-UNCRJBT3.js} +31 -7
- 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/cmd_analyze_session.js +1 -1
- package/dist/cli-delegate/cmd_brand_status.js +1 -1
- package/dist/cli-delegate/cmd_conformance.js +918 -325
- package/dist/cli-delegate/cmd_doctor.js +7 -5
- package/dist/cli-delegate/cmd_explain.js +5 -3
- 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 +6 -6
- package/dist/cli-delegate/cmd_mcp_available.js +1 -1
- package/dist/cli-delegate/cmd_migrate.js +2 -2
- package/dist/cli-delegate/cmd_packs_active.js +3 -2
- package/dist/cli-delegate/cmd_preflight.js +4 -2
- package/dist/cli-delegate/cmd_prune.js +10 -8
- package/dist/cli-delegate/cmd_route_audit.js +2 -2
- 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 +14 -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 +87 -40
- 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 +2601 -1073
- 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 +2008 -1481
- 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 +1612 -667
- 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_lock.js +1 -1
- 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/io/yamlIO.js +50 -0
- package/dist/server/routes/install.js +8 -0
- package/dist/server/routes/wizard.js +9 -3
- package/dist/server/schemas/settings.js +17 -5
- package/dist/shared/missionExecution.js +97 -0
- package/dist/ui/assets/{index-D0jaCRPM.js → index-DH1xc62v.js} +17 -17
- package/dist/ui/index.html +1 -1
- package/docs/MIGRATION.md +103 -0
- package/docs/contracts/settings-classes.md +26 -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 +1 -1
- 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/config/agent-settings.template.yml +139 -27
- package/src/config/auto-rule-budget.json +16 -0
- package/src/config/ci-local-parity.yml +18 -5
- package/src/config/continuity-surface.json +28 -0
- package/src/config/gate-coverage.yml +146 -13
- package/src/config/gate-violation-baselines.json +6 -6
- package/src/config/hook-token-budget.json +2 -0
- package/src/config/host-injection-effect.json +1 -1
- package/src/config/loop-surfaces.yaml +215 -0
- package/src/config/platform-anchor.json +3 -2
- package/src/config/preamble-payload-budget.json +12 -7
- package/src/config/preamble-payload-exceptions.json +28 -0
- package/src/config/release-gate-locality.yml +11 -0
- package/src/config/routing-coverage-seed.json +18 -4
- 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 +4 -3
- 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 +90 -0
- package/src/scripts/_dispatch.bash +3 -1
- 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/base_ref_payload.ts +315 -0
- package/src/scripts/_lib/cascade_base.ts +94 -0
- package/src/scripts/_lib/changelog_eras.ts +7 -2
- 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/dropped_decision.ts +142 -0
- package/src/scripts/_lib/forge_protection.ts +267 -0
- package/src/scripts/_lib/hook_platform_keys.ts +27 -0
- package/src/scripts/_lib/installed_lock.ts +1 -1
- package/src/scripts/_lib/json_python_parity.ts +127 -0
- package/src/scripts/_lib/lean_projection_mode.ts +14 -3
- package/src/scripts/_lib/loop_surfaces.ts +382 -0
- package/src/scripts/_lib/measured_payload_ceiling.ts +424 -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 +402 -0
- package/src/scripts/_lib/orchestration_record.ts +11 -20
- package/src/scripts/_lib/payload_catalogue_completeness.ts +211 -0
- package/src/scripts/_lib/release_findings_ingest.ts +324 -0
- package/src/scripts/_lib/release_highlights.ts +199 -10
- package/src/scripts/_lib/release_holds.ts +493 -0
- package/src/scripts/_lib/release_migration_gate.ts +184 -0
- package/src/scripts/_lib/release_tag_dispatch.ts +63 -0
- package/src/scripts/_lib/review_baseline.ts +254 -0
- package/src/scripts/_lib/review_skipped_record.ts +61 -1
- package/src/scripts/_lib/rides_along.ts +102 -0
- package/src/scripts/_lib/settings_renamed_keys.ts +82 -0
- package/src/scripts/_lib/standing_bound_ratchet.ts +216 -103
- 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 +51 -4
- package/src/scripts/_lib/typed_op_grant.ts +246 -0
- package/src/scripts/_lib/typed_op_watch.ts +185 -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/ask_block_census.ts +285 -15
- package/src/scripts/check_beta_review_markers.ts +35 -4
- package/src/scripts/check_enforcement_matrix.ts +624 -0
- package/src/scripts/check_finding_dispositions.ts +167 -16
- package/src/scripts/check_gate_reachability.ts +173 -10
- package/src/scripts/check_generator_sync.ts +954 -0
- package/src/scripts/check_held_object_arrivals.ts +381 -0
- package/src/scripts/check_preamble_payload_budget.ts +474 -124
- package/src/scripts/check_release_highlights.ts +33 -1
- package/src/scripts/check_release_holds.ts +567 -0
- package/src/scripts/check_release_published.ts +1 -1
- package/src/scripts/check_reply_consistency.ts +86 -17
- package/src/scripts/check_rule_projection_integrity.ts +22 -12
- package/src/scripts/check_test_delta.ts +294 -0
- package/src/scripts/check_test_weakening.ts +299 -0
- package/src/scripts/closure_scan.ts +333 -0
- package/src/scripts/compile_hook_manifest.ts +43 -3
- package/src/scripts/council_record_shape.ts +209 -0
- package/src/scripts/hook_manifest.json +1 -1
- package/src/scripts/hook_manifest.yaml +127 -12
- package/src/scripts/hooks/block_kernel_rule_writes.ts +12 -2
- package/src/scripts/hooks/concern_registry.ts +6 -0
- package/src/scripts/hooks/design_pass_hook.ts +128 -3
- package/src/scripts/hooks/end_review_nudge_hook.ts +101 -21
- package/src/scripts/hooks/evidence_independence.ts +96 -0
- package/src/scripts/hooks/memory_recall_hook.ts +540 -0
- package/src/scripts/hooks/obligation_settle_hook.ts +182 -0
- package/src/scripts/hooks/review_baseline_hook.ts +130 -0
- package/src/scripts/hooks/rule_inject_hook.ts +49 -1
- package/src/scripts/hooks/run_continuation_hook.ts +17 -20
- package/src/scripts/hooks/turn_end_gate_hook.ts +232 -14
- package/src/scripts/hooks_doctor.ts +89 -1
- package/src/scripts/hooks_status.ts +19 -0
- package/src/scripts/install.ts +107 -207
- package/src/scripts/interruption_report.ts +8 -2
- package/src/scripts/janitor.ts +15 -1
- package/src/scripts/lint_decision_classes.ts +464 -0
- package/src/scripts/lint_hook_manifest.ts +11 -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 +61 -0
- package/src/scripts/lint_roadmap_producers.ts +202 -0
- package/src/scripts/measure_rule_budget.ts +101 -5
- package/src/scripts/measure_turn_end_gate.ts +302 -5
- package/src/scripts/new_roadmap.ts +37 -0
- package/src/scripts/orchestration_record.ts +0 -5
- package/src/scripts/release.ts +66 -162
- package/src/scripts/release_drill.ts +460 -15
- package/src/scripts/release_env.ts +1 -1
- package/src/scripts/release_publication.ts +52 -1
- package/src/scripts/report_enforcement_drift.ts +566 -0
- package/src/scripts/report_held_object_arrivals.ts +456 -0
- package/src/scripts/report_host_injection_effect.ts +3 -11
- package/src/scripts/report_standing_payload_by_host.ts +26 -152
- package/src/scripts/schemas/agent-settings.schema.json +23 -0
- package/src/scripts/schemas/command.schema.json +4 -0
- package/src/scripts/scope_growth.ts +219 -0
- package/src/scripts/sync_agent_settings.ts +140 -18
- package/src/scripts/trigger_eval_grandfather.json +0 -1
- package/src/scripts/ui_conformance_probe.ts +622 -0
- package/src/server/io/yamlIO.ts +48 -0
- package/src/server/schemas/settings.ts +31 -7
- package/src/shared/missionExecution.ts +126 -0
- package/dist/cli-delegate/chunk-523HCFYO.js +0 -90
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Per-test independence provenance — `road-to-adversarial-verification-and-long-runs` AC-2.
|
|
3
|
+
*
|
|
4
|
+
* `evaluator-independence` § Tests are evaluators states the five levels and
|
|
5
|
+
* says critical behaviour targets L3 or L4 wherever two providers are
|
|
6
|
+
* configured. It states no place to WRITE that down, so before this module the
|
|
7
|
+
* level a given test actually reached was unrecorded and therefore uncheckable —
|
|
8
|
+
* which is the same shape as the recorded failure that rule exists over: a
|
|
9
|
+
* verdict nobody could trace back to the prompt that produced it.
|
|
10
|
+
*
|
|
11
|
+
* The record is a comment line directly above a `describe(`:
|
|
12
|
+
*
|
|
13
|
+
* ```
|
|
14
|
+
* // provenance: level=L4 | critical=yes | evidence=ac2-authorship-run-a
|
|
15
|
+
* describe('T9 — a typed op reaches an exact-object ask', () => {
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* **Three decisions worth reading, because each has a plausible opposite.**
|
|
19
|
+
*
|
|
20
|
+
* An unmarked group is `ungoverned`, never defaulted to L0. Defaulting would
|
|
21
|
+
* make "provenance is recorded per test" true by construction, and a claim that
|
|
22
|
+
* cannot be false is not a record.
|
|
23
|
+
*
|
|
24
|
+
* The provider count is a PARAMETER, not a probe. `agent-config council:status`
|
|
25
|
+
* resolves from a user-global file that a CI runner does not have, so probing
|
|
26
|
+
* it would make the floor depend on the machine and read "no providers, no
|
|
27
|
+
* obligation" exactly where the obligation matters. The caller supplies the
|
|
28
|
+
* count it is asserting about, and a repository with two configured providers
|
|
29
|
+
* pins 2.
|
|
30
|
+
*
|
|
31
|
+
* `evidence` is required at L3 and L4 and ignored below. An unattributed claim
|
|
32
|
+
* of independence cannot be checked for the independence it claims; at L0-L2
|
|
33
|
+
* there is no cross-provider claim to attribute, so demanding an artefact there
|
|
34
|
+
* would be ceremony.
|
|
35
|
+
*
|
|
36
|
+
* Pure — a string in, findings out. No filesystem, so both polarities are
|
|
37
|
+
* testable without a fixture tree.
|
|
38
|
+
*/
|
|
39
|
+
|
|
40
|
+
/** The five levels of `evaluator-independence` § Tests are evaluators. */
|
|
41
|
+
export const INDEPENDENCE_LEVELS = ['L0', 'L1', 'L2', 'L3', 'L4'] as const;
|
|
42
|
+
export type IndependenceLevel = (typeof INDEPENDENCE_LEVELS)[number];
|
|
43
|
+
|
|
44
|
+
/** What critical behaviour targets where two providers are configured. */
|
|
45
|
+
export const CRITICAL_FLOOR: IndependenceLevel = 'L3';
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The honest ceiling below two providers.
|
|
49
|
+
*
|
|
50
|
+
* With one provider, L3 ("another provider") is unreachable by definition, so
|
|
51
|
+
* the floor relaxes rather than demanding a level the install cannot produce. A
|
|
52
|
+
* floor nobody can clear gets deleted rather than met.
|
|
53
|
+
*/
|
|
54
|
+
export const SINGLE_PROVIDER_FLOOR: IndependenceLevel = 'L2';
|
|
55
|
+
|
|
56
|
+
/** One group's recorded independence. */
|
|
57
|
+
export interface ProvenanceRecord {
|
|
58
|
+
/** The `describe` title this record governs. */
|
|
59
|
+
readonly group: string;
|
|
60
|
+
readonly level: IndependenceLevel;
|
|
61
|
+
/** Security, authority, data loss or merge control. */
|
|
62
|
+
readonly critical: boolean;
|
|
63
|
+
/** The artefact the level came from. Empty is legal below L3. */
|
|
64
|
+
readonly evidence: string;
|
|
65
|
+
/** 1-based line of the marker, so a finding can point at it. */
|
|
66
|
+
readonly line: number;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export interface ProvenanceScan {
|
|
70
|
+
readonly records: readonly ProvenanceRecord[];
|
|
71
|
+
/** Titles of `describe`s carrying no valid marker. */
|
|
72
|
+
readonly ungoverned: readonly string[];
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Rank within the ordered vocabulary. L0 is 0. */
|
|
76
|
+
export function levelRank(level: IndependenceLevel): number {
|
|
77
|
+
return INDEPENDENCE_LEVELS.indexOf(level);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const MARKER = /^\s*\/\/\s*provenance:\s*(.*)$/;
|
|
81
|
+
const DESCRIBE = /^\s*describe\(\s*(['"`])(.*?)\1/;
|
|
82
|
+
|
|
83
|
+
function isLevel(v: string): v is IndependenceLevel {
|
|
84
|
+
return (INDEPENDENCE_LEVELS as readonly string[]).includes(v);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Parse `key=value | key=value` into a map. Unknown keys are ignored. */
|
|
88
|
+
function fields(rest: string): Map<string, string> {
|
|
89
|
+
const out = new Map<string, string>();
|
|
90
|
+
for (const part of rest.split('|')) {
|
|
91
|
+
const eq = part.indexOf('=');
|
|
92
|
+
if (eq < 0) continue;
|
|
93
|
+
out.set(part.slice(0, eq).trim(), part.slice(eq + 1).trim());
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Scan a test file's source for provenance records.
|
|
100
|
+
*
|
|
101
|
+
* A marker governs the `describe` it sits directly above — blank lines between
|
|
102
|
+
* them are tolerated, any other statement is not. Without that adjacency rule a
|
|
103
|
+
* marker anywhere in the file would appear to cover the next group, which is how
|
|
104
|
+
* one record silently becomes a claim about several.
|
|
105
|
+
*/
|
|
106
|
+
export function parseProvenance(source: string): ProvenanceScan {
|
|
107
|
+
const lines = source.split('\n');
|
|
108
|
+
const records: ProvenanceRecord[] = [];
|
|
109
|
+
const ungoverned: string[] = [];
|
|
110
|
+
|
|
111
|
+
for (let i = 0; i < lines.length; i += 1) {
|
|
112
|
+
const d = DESCRIBE.exec(lines[i] ?? '');
|
|
113
|
+
if (d === null) continue;
|
|
114
|
+
const group = d[2] ?? '';
|
|
115
|
+
|
|
116
|
+
// Walk back over blank lines to the candidate marker.
|
|
117
|
+
let j = i - 1;
|
|
118
|
+
while (j >= 0 && (lines[j] ?? '').trim() === '') j -= 1;
|
|
119
|
+
const m = j >= 0 ? MARKER.exec(lines[j] ?? '') : null;
|
|
120
|
+
if (m === null) {
|
|
121
|
+
ungoverned.push(group);
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
const f = fields(m[1] ?? '');
|
|
125
|
+
const level = f.get('level') ?? '';
|
|
126
|
+
if (!isLevel(level)) {
|
|
127
|
+
ungoverned.push(group);
|
|
128
|
+
continue;
|
|
129
|
+
}
|
|
130
|
+
records.push({
|
|
131
|
+
group,
|
|
132
|
+
level,
|
|
133
|
+
critical: f.get('critical') === 'yes',
|
|
134
|
+
evidence: f.get('evidence') ?? '',
|
|
135
|
+
line: j + 1,
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
return { records, ungoverned };
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Critical groups that do not clear the floor for this provider count.
|
|
143
|
+
*
|
|
144
|
+
* Returns one human-readable finding per gap, naming the group, the level it
|
|
145
|
+
* records and what it owed — a caller reading the list should not have to open
|
|
146
|
+
* the file to know what to change.
|
|
147
|
+
*/
|
|
148
|
+
export function criticalGaps(
|
|
149
|
+
records: readonly ProvenanceRecord[],
|
|
150
|
+
providerCount: number,
|
|
151
|
+
): string[] {
|
|
152
|
+
const floor = providerCount >= 2 ? CRITICAL_FLOOR : SINGLE_PROVIDER_FLOOR;
|
|
153
|
+
const gaps: string[] = [];
|
|
154
|
+
for (const r of records) {
|
|
155
|
+
if (!r.critical) continue;
|
|
156
|
+
if (levelRank(r.level) < levelRank(floor)) {
|
|
157
|
+
gaps.push(
|
|
158
|
+
`${r.group} (line ${String(r.line)}) records ${r.level}; critical behaviour ` +
|
|
159
|
+
`needs ${floor} or above with ${String(providerCount)} provider(s) configured`,
|
|
160
|
+
);
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (levelRank(r.level) >= levelRank(CRITICAL_FLOOR) && r.evidence === '') {
|
|
164
|
+
gaps.push(
|
|
165
|
+
`${r.group} (line ${String(r.line)}) claims ${r.level} with no evidence; an ` +
|
|
166
|
+
`unattributed claim of independence cannot be checked for the independence it claims`,
|
|
167
|
+
);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return gaps;
|
|
171
|
+
}
|
|
@@ -41,15 +41,21 @@ import * as path from 'node:path';
|
|
|
41
41
|
import { read_lockfile } from './installed_lock.js';
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
|
-
* The
|
|
44
|
+
* The five detectors, in the order the gate runs them.
|
|
45
45
|
*
|
|
46
46
|
* The roadmap's § 0 names three (A promissory, B language, C verification).
|
|
47
47
|
* That was true of the draft and is not true of the tree: detector D
|
|
48
|
-
* (`completion`) landed under round 7 § Phase 1
|
|
49
|
-
*
|
|
48
|
+
* (`completion`) landed under round 7 § Phase 1, detector E
|
|
49
|
+
* (`pending-decision`) under road-to-a-question-that-survives-the-turn, and
|
|
50
|
+
* detector F (`untested`) on 2026-09-11 — all of which run in the same detector
|
|
51
|
+
* list as the first three. Counting three would silently drop a detector's
|
|
50
52
|
* refusals, so the set is read off `DetectorId` in the gate rather than off the
|
|
51
53
|
* prose.
|
|
52
54
|
*
|
|
55
|
+
* F is the one that asks whether a change is TESTED rather than whether
|
|
56
|
+
* something ran: C is satisfied by `eslint`, which is how a feature with zero
|
|
57
|
+
* test lines passed every guard in that file.
|
|
58
|
+
*
|
|
53
59
|
* This said "the same UNCONDITIONAL list" until 2026-08-18, which was wrong and
|
|
54
60
|
* is worth naming rather than quietly rewording: `main()` runs A and D only when
|
|
55
61
|
* no subagent dispatch is open. It does not change this set — every detector
|
|
@@ -63,6 +69,8 @@ export const DETECTOR_IDS = [
|
|
|
63
69
|
'language',
|
|
64
70
|
'verification',
|
|
65
71
|
'completion',
|
|
72
|
+
'pending-decision',
|
|
73
|
+
'untested',
|
|
66
74
|
] as const;
|
|
67
75
|
|
|
68
76
|
export type RefusalDetectorId = (typeof DETECTOR_IDS)[number];
|
|
@@ -138,10 +146,39 @@ export interface RefusalRecord {
|
|
|
138
146
|
counts?: Partial<DetectorCounts>;
|
|
139
147
|
/** Package version recorded at the most recent refusal, when readable. */
|
|
140
148
|
agent_config_version?: string;
|
|
149
|
+
/**
|
|
150
|
+
* The host's own id for the prompt being processed at the most recent
|
|
151
|
+
* refusal, when the payload carries one.
|
|
152
|
+
*
|
|
153
|
+
* Recorded BESIDE `refused_turn`, never instead of it. The ordinal stays the
|
|
154
|
+
* re-entrancy key — its drift history is why both guard layers exist, and
|
|
155
|
+
* swapping the key of a wedge-critical guard on an axis nothing measures is
|
|
156
|
+
* a behaviour change dressed as a correction. What the pair buys is the
|
|
157
|
+
* measurement: two refusals carrying the same `refused_prompt_id` and
|
|
158
|
+
* different `refused_turn` are one prompt whose ordinal drifted, which is
|
|
159
|
+
* the failure this file's own header describes and which the ordinal alone
|
|
160
|
+
* cannot distinguish from two genuine turns.
|
|
161
|
+
*/
|
|
162
|
+
refused_prompt_id?: string;
|
|
141
163
|
}
|
|
142
164
|
|
|
165
|
+
/**
|
|
166
|
+
* A zeroed counter per detector, DERIVED from `DETECTOR_IDS` rather than
|
|
167
|
+
* written out.
|
|
168
|
+
*
|
|
169
|
+
* It was an object literal of four keys until detector E landed, and the
|
|
170
|
+
* literal is what made adding a detector a silent arithmetic defect rather than
|
|
171
|
+
* a type error: `DetectorCounts` is `Record<RefusalDetectorId, number>`, the
|
|
172
|
+
* literal satisfied it for the union of the day, and a fifth id then read back
|
|
173
|
+
* as `undefined` — so every `+=` over it produced `NaN` and the whole rollup
|
|
174
|
+
* went quiet. The sibling test asserts that the id list and the gate's union
|
|
175
|
+
* agree; nothing asserted that the COUNTER covered the list, which is the half
|
|
176
|
+
* this construction removes the need to assert.
|
|
177
|
+
*/
|
|
143
178
|
export function emptyCounts(): DetectorCounts {
|
|
144
|
-
|
|
179
|
+
const out = {} as DetectorCounts;
|
|
180
|
+
for (const id of DETECTOR_IDS) out[id] = 0;
|
|
181
|
+
return out;
|
|
145
182
|
}
|
|
146
183
|
|
|
147
184
|
function isDetector(v: unknown): v is RefusalDetectorId {
|
|
@@ -174,6 +211,9 @@ export function parseRecord(raw: string): RefusalRecord | null {
|
|
|
174
211
|
if (typeof o['agent_config_version'] === 'string') {
|
|
175
212
|
rec.agent_config_version = o['agent_config_version'];
|
|
176
213
|
}
|
|
214
|
+
if (typeof o['refused_prompt_id'] === 'string') {
|
|
215
|
+
rec.refused_prompt_id = o['refused_prompt_id'];
|
|
216
|
+
}
|
|
177
217
|
const counts = o['counts'];
|
|
178
218
|
if (typeof counts === 'object' && counts !== null && !Array.isArray(counts)) {
|
|
179
219
|
const parsed: Partial<DetectorCounts> = {};
|
|
@@ -231,6 +271,7 @@ export function foldRefusal(
|
|
|
231
271
|
turnOrdinal: number;
|
|
232
272
|
at: string;
|
|
233
273
|
version?: string | undefined;
|
|
274
|
+
promptId?: string | undefined;
|
|
234
275
|
},
|
|
235
276
|
): RefusalRecord {
|
|
236
277
|
const counts = prev === null ? emptyCounts() : countsOf(prev);
|
|
@@ -247,6 +288,12 @@ export function foldRefusal(
|
|
|
247
288
|
else if (prev?.agent_config_version !== undefined) {
|
|
248
289
|
rec.agent_config_version = prev.agent_config_version;
|
|
249
290
|
}
|
|
291
|
+
// Not inherited from `prev` when this refusal carries none: the field
|
|
292
|
+
// describes THIS refusal's prompt, and carrying a previous one forward would
|
|
293
|
+
// read as an identity the payload never supplied.
|
|
294
|
+
if (input.promptId !== undefined && input.promptId !== '') {
|
|
295
|
+
rec.refused_prompt_id = input.promptId;
|
|
296
|
+
}
|
|
250
297
|
return rec;
|
|
251
298
|
}
|
|
252
299
|
|
|
@@ -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
|
+
}
|