@christang/keel 5.49.0 → 5.50.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/assets/bootstrap/AGENTS.md +1 -1
- package/assets/openspec/schemas/keel-spec-driven/templates/tasks.md +10 -3
- package/package.json +1 -1
- package/plugins/keel/.claude-plugin/plugin.json +1 -1
- package/plugins/keel/.codex-plugin/plugin.json +1 -1
- package/scripts/validate_plugin.py +379 -16
- package/src/core/task-contract.js +58 -3
|
@@ -21,9 +21,15 @@
|
|
|
21
21
|
- Touch:
|
|
22
22
|
- <path>
|
|
23
23
|
- Verify:
|
|
24
|
-
<!-- verification discipline: Strategy is one of
|
|
25
|
-
regression-first, characterization,
|
|
26
|
-
rendered-behavior, or evidence-first.
|
|
24
|
+
<!-- verification discipline: Strategy is REQUIRED and is one of
|
|
25
|
+
vertical-tdd, regression-first, characterization,
|
|
26
|
+
snapshot-characterization, rendered-behavior, or evidence-first.
|
|
27
|
+
Nothing is supplied by default, because the value an omission
|
|
28
|
+
would select is the one with no red-green requirement.
|
|
29
|
+
evidence-first additionally carries a `Reason:` entry beside
|
|
30
|
+
Strategy, stating why no meaningful red-green loop applies —
|
|
31
|
+
docs, configuration, diagnosis, or another reason nothing here
|
|
32
|
+
can fail first. No mode exempts it. Each M<n> check must prove the
|
|
27
33
|
resolved Acceptance through the public interface, not build-only or
|
|
28
34
|
shape-only evidence. Red-green strategies record per-label `.red` and
|
|
29
35
|
`.green` Evidence entries IN ADDITION TO the bare `M<n>` entry, which
|
|
@@ -78,6 +84,7 @@
|
|
|
78
84
|
- none
|
|
79
85
|
- Verify:
|
|
80
86
|
- Strategy: evidence-first
|
|
87
|
+
- Reason: <why no meaningful red-green loop applies — a diagnosis task reproduces and reports, so nothing it writes can fail first>
|
|
81
88
|
- M1: <reproduction or diagnosis check with its observable evidence>
|
|
82
89
|
- Evidence:
|
|
83
90
|
- Contract: pending
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keel",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.50.0",
|
|
4
4
|
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TanglmChris",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "keel",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.50.0",
|
|
4
4
|
"description": "Keel OpenSpec execution discipline: stateless continuity, task capsules, deterministic gates, and expectation alignment for Codex and Claude Code.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "TanglmChris",
|
|
@@ -37,8 +37,8 @@ REQUIRED_SCRIPTS = [
|
|
|
37
37
|
"scripts/validate_plugin.py",
|
|
38
38
|
]
|
|
39
39
|
|
|
40
|
-
PACKAGE_VERSION = "5.
|
|
41
|
-
PROTOCOL_VERSION = "5.
|
|
40
|
+
PACKAGE_VERSION = "5.50.0"
|
|
41
|
+
PROTOCOL_VERSION = "5.50.0"
|
|
42
42
|
LEGACY_MANAGED_START = "<!-- keel:start version=2.1 -->"
|
|
43
43
|
OPENSPEC_SCHEMA_NAME = "keel-spec-driven"
|
|
44
44
|
# Mirrors KEEL_PACKAGE_NAME in scripts/install_to_repo.py, one of the two
|
|
@@ -3377,6 +3377,8 @@ def gate_task(
|
|
|
3377
3377
|
"Covers": "\n - E1: hook behavior",
|
|
3378
3378
|
"Read": "\n - openspec/changes/demo/proposal.md",
|
|
3379
3379
|
"Touch": "\n - src/example.js",
|
|
3380
|
+
"Verification Strategy": "evidence-first",
|
|
3381
|
+
"Verification Reason": "this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first",
|
|
3380
3382
|
"Commands": "\n - M1: npm test",
|
|
3381
3383
|
"Acceptance": "the required command succeeds",
|
|
3382
3384
|
"Execution recommendation": "Claude Code implementation note (advisory)",
|
|
@@ -4329,6 +4331,7 @@ def task_contract_fixture(
|
|
|
4329
4331
|
touch: tuple[str, ...] = ("src/feature.js",),
|
|
4330
4332
|
commands: tuple[str, ...] = ("M1: node test.js",),
|
|
4331
4333
|
evidence: tuple[str, ...] = ("M1: pending",),
|
|
4334
|
+
strategy: str | None = "evidence-first",
|
|
4332
4335
|
) -> str:
|
|
4333
4336
|
touch_lines = "".join(f" - {item}\n" for item in touch)
|
|
4334
4337
|
command_lines = "".join(f" - {item}\n" for item in commands)
|
|
@@ -4344,8 +4347,11 @@ def task_contract_fixture(
|
|
|
4344
4347
|
" - README.md\n"
|
|
4345
4348
|
" - Touch:\n"
|
|
4346
4349
|
f"{touch_lines}"
|
|
4347
|
-
" -
|
|
4348
|
-
f"
|
|
4350
|
+
+ (f" - Verification Strategy: {strategy}\n" if strategy else "")
|
|
4351
|
+
+ (f" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
4352
|
+
if strategy == "evidence-first" else "")
|
|
4353
|
+
+ " - Commands:\n"
|
|
4354
|
+
+ f"{command_lines}"
|
|
4349
4355
|
" - Acceptance:\n"
|
|
4350
4356
|
" - Public behavior passes.\n"
|
|
4351
4357
|
" - Autonomy boundary:\n"
|
|
@@ -4555,6 +4561,7 @@ def section_boundary_task(
|
|
|
4555
4561
|
f"{touch}"
|
|
4556
4562
|
" - Verify:\n"
|
|
4557
4563
|
" - Strategy: evidence-first\n"
|
|
4564
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
4558
4565
|
" - M1: node test.js reports the public behavior passing\n"
|
|
4559
4566
|
" - Acceptance:\n"
|
|
4560
4567
|
" - Public behavior passes.\n"
|
|
@@ -7091,6 +7098,7 @@ def task_capsule_expanded_fixture() -> str:
|
|
|
7091
7098
|
).replace(
|
|
7092
7099
|
" - Acceptance:\n",
|
|
7093
7100
|
" - Verification Strategy: evidence-first\n"
|
|
7101
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
7094
7102
|
" - Acceptance:\n",
|
|
7095
7103
|
)
|
|
7096
7104
|
|
|
@@ -7107,6 +7115,7 @@ def task_capsule_compact_fixture() -> str:
|
|
|
7107
7115
|
" - src/feature.js\n"
|
|
7108
7116
|
" - Verify:\n"
|
|
7109
7117
|
" - Strategy: evidence-first\n"
|
|
7118
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
7110
7119
|
" - M1: node test.js\n"
|
|
7111
7120
|
" - Autonomy boundary:\n"
|
|
7112
7121
|
" - Default: hard-stop\n"
|
|
@@ -7226,7 +7235,7 @@ def validate_non_concrete_verify_diagnostic_scenario() -> int:
|
|
|
7226
7235
|
# absent-verification-form-is-one-problem scenario.
|
|
7227
7236
|
bare = task_capsule_compact_fixture()
|
|
7228
7237
|
for block in (
|
|
7229
|
-
" - Verify:\n - Strategy: evidence-first\n - M1: node test.js\n",
|
|
7238
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n - M1: node test.js\n",
|
|
7230
7239
|
):
|
|
7231
7240
|
bare = bare.replace(block, "")
|
|
7232
7241
|
write_text(repo / "openspec/changes/bare/tasks.md", bare)
|
|
@@ -7738,7 +7747,11 @@ SPEC_TEMPLATE_RELATIVE = "schemas/keel-spec-driven/templates/spec.md"
|
|
|
7738
7747
|
|
|
7739
7748
|
|
|
7740
7749
|
SLOT_FILLER = "the recorded feed status"
|
|
7741
|
-
|
|
7750
|
+
# The filler must be a strategy the generic task can use as written.
|
|
7751
|
+
# `evidence-first` no longer is: it carries a `Reason:` the generic task
|
|
7752
|
+
# has no slot for, and giving it one would put a line there that is wrong
|
|
7753
|
+
# for every other strategy the slot can take.
|
|
7754
|
+
SLOT_VOCABULARY = {"<strategy>": "vertical-tdd"}
|
|
7742
7755
|
|
|
7743
7756
|
|
|
7744
7757
|
def fill_template_slots(text: str) -> str:
|
|
@@ -7815,7 +7828,7 @@ def validate_guard_scope_is_the_repository_scenario() -> int:
|
|
|
7815
7828
|
"- [ ] 1.1 Exercise the guard\n"
|
|
7816
7829
|
" - Covers:\n - demo-cap / The system emits a feed status\n"
|
|
7817
7830
|
" - Touch:\n - src/feature.js\n"
|
|
7818
|
-
" - Verify:\n - Strategy: evidence-first\n - M1: node test.js\n"
|
|
7831
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n - M1: node test.js\n"
|
|
7819
7832
|
" - Evidence:\n - Contract: pending\n - M1: pending\n"
|
|
7820
7833
|
" - Review:\n - Status: pending\n"
|
|
7821
7834
|
" - Acceptance check: pending\n - Scope check: pending\n"
|
|
@@ -8726,7 +8739,7 @@ def validate_default_completion_attributes_writes_scenario() -> int:
|
|
|
8726
8739
|
"- [ ] 1.1 Exercise task contract\n"
|
|
8727
8740
|
" - Covers:\n - E1: Public behavior passes.\n"
|
|
8728
8741
|
" - Touch:\n - src/declared.js\n"
|
|
8729
|
-
" - Verify:\n - Strategy: evidence-first\n"
|
|
8742
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
8730
8743
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
8731
8744
|
" - Evidence:\n - Contract: pending\n - M1: the suite passed\n"
|
|
8732
8745
|
" - Review:\n - Status: pass\n"
|
|
@@ -8924,7 +8937,7 @@ def validate_git_paths_carry_no_escaping_scenario() -> int:
|
|
|
8924
8937
|
" - Covers:\n - E1: Public behavior passes.\n"
|
|
8925
8938
|
" - Touch:\n"
|
|
8926
8939
|
f"{touch_lines}"
|
|
8927
|
-
" - Verify:\n - Strategy: evidence-first\n"
|
|
8940
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
8928
8941
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
8929
8942
|
" - Evidence:\n - Contract: pending\n - M1: the suite passed\n"
|
|
8930
8943
|
" - Review:\n - Status: pass\n"
|
|
@@ -9138,7 +9151,7 @@ def validate_guard_containment_is_resolved_scenario() -> int:
|
|
|
9138
9151
|
"- [ ] 1.1 Exercise the guard\n"
|
|
9139
9152
|
" - Covers:\n - demo-cap / The system emits a feed status\n"
|
|
9140
9153
|
" - Touch:\n - src/allowed.js\n - docs/**\n"
|
|
9141
|
-
" - Verify:\n - Strategy: evidence-first\n"
|
|
9154
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
9142
9155
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
9143
9156
|
" - Evidence:\n - Contract: pending\n - M1: pending\n",
|
|
9144
9157
|
)
|
|
@@ -9268,6 +9281,7 @@ def validate_completion_requires_a_recorded_anchor_scenario() -> int:
|
|
|
9268
9281
|
" - src/feature.js\n"
|
|
9269
9282
|
" - Verify:\n"
|
|
9270
9283
|
" - Strategy: evidence-first\n"
|
|
9284
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
9271
9285
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
9272
9286
|
" - Evidence:\n"
|
|
9273
9287
|
f" - Contract: {contract}\n"
|
|
@@ -9428,6 +9442,7 @@ def _anchor_fixture(
|
|
|
9428
9442
|
f" - {touch}\n"
|
|
9429
9443
|
" - Verify:\n"
|
|
9430
9444
|
" - Strategy: evidence-first\n"
|
|
9445
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
9431
9446
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
9432
9447
|
" - Evidence:\n"
|
|
9433
9448
|
f" - Contract: {contract}\n"
|
|
@@ -9718,6 +9733,7 @@ def validate_task_body_ends_at_heading_scenario() -> int:
|
|
|
9718
9733
|
" - src/feature.js\n"
|
|
9719
9734
|
" - Verify:\n"
|
|
9720
9735
|
" - Strategy: evidence-first\n"
|
|
9736
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
9721
9737
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
9722
9738
|
" - Evidence:\n"
|
|
9723
9739
|
f" - Contract: {contract}\n"
|
|
@@ -9835,9 +9851,17 @@ def validate_task_body_ends_at_heading_scenario() -> int:
|
|
|
9835
9851
|
# rather than merely measured, so a future extent change cannot move one
|
|
9836
9852
|
# silently. A deliberate capsule-shape change will fail here too — that
|
|
9837
9853
|
# is the point; it should be looked at, not absorbed.
|
|
9854
|
+
#
|
|
9855
|
+
# Moved once, deliberately, when `evidence-first` began requiring a
|
|
9856
|
+
# stated reason: this fixture gained a `Verification Reason:` field, so
|
|
9857
|
+
# its own capsule changed. What did not change is the capsule of a task
|
|
9858
|
+
# that states no reason — the `reason` key is emitted only when there is
|
|
9859
|
+
# one, and a vertical-tdd task compiles to the identical block and the
|
|
9860
|
+
# identical fingerprint before and after, measured both ways. So no
|
|
9861
|
+
# existing anchor drifted; this fixture is simply a different task now.
|
|
9838
9862
|
pinned = {
|
|
9839
|
-
"1.1": "
|
|
9840
|
-
"2.1": "
|
|
9863
|
+
"1.1": "11110c1cdde55df1008c6a2bd9f6bde2bffa286a34ae93addb9cf66fc1e5b90f",
|
|
9864
|
+
"2.1": "62f7dfaec87e0614dae7e4935ce1749554f141a23518b094632eb8423e96d54c",
|
|
9841
9865
|
}
|
|
9842
9866
|
plain = (
|
|
9843
9867
|
"# Tasks\n\n"
|
|
@@ -10093,6 +10117,7 @@ def validate_task_complete_selection_requires_a_started_task_scenario() -> int:
|
|
|
10093
10117
|
" - src/feature.js\n"
|
|
10094
10118
|
" - Verify:\n"
|
|
10095
10119
|
" - Strategy: evidence-first\n"
|
|
10120
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
10096
10121
|
" - M1: node test.js\n"
|
|
10097
10122
|
" - Evidence:\n"
|
|
10098
10123
|
f" - Contract: {contract}\n"
|
|
@@ -10228,7 +10253,11 @@ def validate_absent_verification_form_is_one_problem_scenario() -> int:
|
|
|
10228
10253
|
" - src/feature.js\n"
|
|
10229
10254
|
)
|
|
10230
10255
|
evidence = " - Evidence:\n - M1: pending\n"
|
|
10231
|
-
commands =
|
|
10256
|
+
commands = (
|
|
10257
|
+
" - Verification Strategy: evidence-first\n"
|
|
10258
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
10259
|
+
" - Commands:\n - M1: node test.js\n"
|
|
10260
|
+
)
|
|
10232
10261
|
|
|
10233
10262
|
with tempfile.TemporaryDirectory(prefix="keel-absent-verification-") as raw:
|
|
10234
10263
|
repo = Path(raw)
|
|
@@ -10563,7 +10592,7 @@ def validate_unusable_contract_names_only_its_cause_scenario() -> int:
|
|
|
10563
10592
|
# 6. Suppression must not hide a task that genuinely declares no
|
|
10564
10593
|
# verification form. The refusal names the compact field to add.
|
|
10565
10594
|
noform = clean.replace(
|
|
10566
|
-
" - Verify:\n - Strategy: evidence-first\n - M1: node test.js\n",
|
|
10595
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n - M1: node test.js\n",
|
|
10567
10596
|
"",
|
|
10568
10597
|
)
|
|
10569
10598
|
write_text(repo / "openspec/changes/noform/tasks.md", noform)
|
|
@@ -10855,6 +10884,8 @@ def tracker_owner_tasks(findings: str, closure: str) -> str:
|
|
|
10855
10884
|
" - README.md\n"
|
|
10856
10885
|
" - Touch:\n"
|
|
10857
10886
|
" - src/feature.js\n"
|
|
10887
|
+
" - Verification Strategy: evidence-first\n"
|
|
10888
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
10858
10889
|
" - Commands:\n"
|
|
10859
10890
|
" - M1: node test.js\n"
|
|
10860
10891
|
" - Acceptance:\n"
|
|
@@ -11255,6 +11286,8 @@ def review_extent_tasks(review: str, blocker: str = "none") -> str:
|
|
|
11255
11286
|
" - README.md\n"
|
|
11256
11287
|
" - Touch:\n"
|
|
11257
11288
|
" - src/feature.js\n"
|
|
11289
|
+
" - Verification Strategy: evidence-first\n"
|
|
11290
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
11258
11291
|
" - Commands:\n"
|
|
11259
11292
|
" - M1: node test.js\n"
|
|
11260
11293
|
" - Acceptance:\n"
|
|
@@ -11524,6 +11557,8 @@ def reauthorizations_tasks(reauthorizations: str, blocker: str = "none") -> str:
|
|
|
11524
11557
|
" - README.md\n"
|
|
11525
11558
|
" - Touch:\n"
|
|
11526
11559
|
" - src/feature.js\n"
|
|
11560
|
+
" - Verification Strategy: evidence-first\n"
|
|
11561
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
11527
11562
|
" - Commands:\n"
|
|
11528
11563
|
" - M1: node test.js\n"
|
|
11529
11564
|
" - Acceptance:\n"
|
|
@@ -12655,6 +12690,8 @@ def validate_core_gates_scenario() -> int:
|
|
|
12655
12690
|
" - Touch:\n"
|
|
12656
12691
|
" - src/feature.js\n"
|
|
12657
12692
|
" - openspec/changes/demo/tasks.md\n"
|
|
12693
|
+
" - Verification Strategy: evidence-first\n"
|
|
12694
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
12658
12695
|
" - Commands:\n"
|
|
12659
12696
|
" - M1: node test.js\n"
|
|
12660
12697
|
" - Acceptance:\n"
|
|
@@ -12805,6 +12842,8 @@ def validate_core_gates_scenario() -> int:
|
|
|
12805
12842
|
" - Touch:\n"
|
|
12806
12843
|
" - src/feature.js\n"
|
|
12807
12844
|
" - openspec/changes/demo/tasks.md\n"
|
|
12845
|
+
" - Verification Strategy: evidence-first\n"
|
|
12846
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
12808
12847
|
" - Commands:\n"
|
|
12809
12848
|
" - M1: node test.js\n"
|
|
12810
12849
|
" - Acceptance:\n"
|
|
@@ -13298,6 +13337,8 @@ def validate_core_gates_scenario() -> int:
|
|
|
13298
13337
|
" - Touch:\n"
|
|
13299
13338
|
+ touch
|
|
13300
13339
|
+ " - openspec/changes/demo/tasks.md\n"
|
|
13340
|
+
" - Verification Strategy: evidence-first\n"
|
|
13341
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
13301
13342
|
" - Commands:\n"
|
|
13302
13343
|
" - M1: node test.js\n"
|
|
13303
13344
|
" - Acceptance:\n"
|
|
@@ -13519,6 +13560,8 @@ def validate_core_gates_scenario() -> int:
|
|
|
13519
13560
|
" - README.md\n"
|
|
13520
13561
|
" - Touch:\n"
|
|
13521
13562
|
" - src/feature.js\n"
|
|
13563
|
+
" - Verification Strategy: evidence-first\n"
|
|
13564
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
13522
13565
|
" - Commands:\n"
|
|
13523
13566
|
" - M1: node test.js\n"
|
|
13524
13567
|
" - Acceptance:\n"
|
|
@@ -13689,6 +13732,8 @@ def validate_scope_rename_attribution_scenario() -> int:
|
|
|
13689
13732
|
" - src/renamed-from.js\n"
|
|
13690
13733
|
" - src/renamed-to.js\n"
|
|
13691
13734
|
" - openspec/changes/demo/tasks.md\n"
|
|
13735
|
+
" - Verification Strategy: evidence-first\n"
|
|
13736
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
13692
13737
|
" - Commands:\n"
|
|
13693
13738
|
" - M1: node test.js\n"
|
|
13694
13739
|
" - Acceptance:\n"
|
|
@@ -15865,6 +15910,8 @@ def validate_native_runtime_projection_scenario() -> int:
|
|
|
15865
15910
|
" - README.md\n"
|
|
15866
15911
|
" - Touch:\n"
|
|
15867
15912
|
" - src/feature.js\n"
|
|
15913
|
+
" - Verification Strategy: evidence-first\n"
|
|
15914
|
+
" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
15868
15915
|
" - Commands:\n"
|
|
15869
15916
|
" - M1: node test.js\n"
|
|
15870
15917
|
" - Acceptance:\n"
|
|
@@ -16457,7 +16504,7 @@ def _goal_task_block(
|
|
|
16457
16504
|
title: str = "Deliver one bounded behavior",
|
|
16458
16505
|
checked: bool = False,
|
|
16459
16506
|
acceptance: tuple[str, ...] = ("Observable result is proven by M1.",),
|
|
16460
|
-
strategy: str | None =
|
|
16507
|
+
strategy: str | None = "evidence-first",
|
|
16461
16508
|
filled: bool = False,
|
|
16462
16509
|
blocker: str = "none",
|
|
16463
16510
|
redgreen: bool = False,
|
|
@@ -16476,6 +16523,8 @@ def _goal_task_block(
|
|
|
16476
16523
|
]
|
|
16477
16524
|
if strategy:
|
|
16478
16525
|
lines.append(" - Verification Strategy: %s" % strategy)
|
|
16526
|
+
if strategy == "evidence-first":
|
|
16527
|
+
lines.append(" - Verification Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first")
|
|
16479
16528
|
lines.append(" - Commands:")
|
|
16480
16529
|
lines.append(" - M1: node test.js")
|
|
16481
16530
|
lines.append(" - Acceptance:")
|
|
@@ -17101,6 +17150,7 @@ def standing_authorization_task(boundary: str = "") -> str:
|
|
|
17101
17150
|
" - src/feature.js\n"
|
|
17102
17151
|
" - Verify:\n"
|
|
17103
17152
|
" - Strategy: evidence-first\n"
|
|
17153
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
17104
17154
|
" - M1: node test.js proves the public behavior\n"
|
|
17105
17155
|
+ boundary
|
|
17106
17156
|
+ " - Evidence:\n"
|
|
@@ -18096,6 +18146,7 @@ def validate_delegation_never_weakens_scenario() -> int:
|
|
|
18096
18146
|
" - src/feature.js\n"
|
|
18097
18147
|
" - Verify:\n"
|
|
18098
18148
|
" - Strategy: evidence-first\n"
|
|
18149
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
18099
18150
|
" - M1: node test.js proves the public behavior\n"
|
|
18100
18151
|
" - Evidence:\n"
|
|
18101
18152
|
" - Contract: pending\n"
|
|
@@ -18369,6 +18420,7 @@ def validate_delegation_goal_budget_scenario() -> int:
|
|
|
18369
18420
|
+ padding
|
|
18370
18421
|
+ " - Verify:\n"
|
|
18371
18422
|
" - Strategy: evidence-first\n"
|
|
18423
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
18372
18424
|
" - M1: node test.js proves the public behavior\n"
|
|
18373
18425
|
" - Evidence:\n"
|
|
18374
18426
|
" - Contract: pending\n"
|
|
@@ -18938,6 +18990,7 @@ def validate_triage_admits_only_a_start_scenario() -> int:
|
|
|
18938
18990
|
" - src/feature.js\n"
|
|
18939
18991
|
" - Verify:\n"
|
|
18940
18992
|
" - Strategy: evidence-first\n"
|
|
18993
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
18941
18994
|
" - M1: node test.js proves the public behavior\n"
|
|
18942
18995
|
" - Evidence:\n"
|
|
18943
18996
|
" - Contract: pending\n"
|
|
@@ -19200,6 +19253,7 @@ def validate_precedent_never_weakens_scenario() -> int:
|
|
|
19200
19253
|
" - src/feature.js\n"
|
|
19201
19254
|
" - Verify:\n"
|
|
19202
19255
|
" - Strategy: evidence-first\n"
|
|
19256
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
19203
19257
|
" - M1: node test.js proves the public behavior\n"
|
|
19204
19258
|
" - Evidence:\n"
|
|
19205
19259
|
" - Contract: pending\n"
|
|
@@ -19323,6 +19377,7 @@ def validate_standing_authorization_never_weakens_scenario() -> int:
|
|
|
19323
19377
|
" - src/feature.js\n"
|
|
19324
19378
|
" - Verify:\n"
|
|
19325
19379
|
" - Strategy: evidence-first\n"
|
|
19380
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
19326
19381
|
" - M1: node test.js proves the public behavior\n"
|
|
19327
19382
|
" - Evidence:\n"
|
|
19328
19383
|
" - Contract: pending\n"
|
|
@@ -19481,6 +19536,7 @@ def validate_continuation_authorization_scenario() -> int:
|
|
|
19481
19536
|
" - src/feature.js\n"
|
|
19482
19537
|
" - Verify:\n"
|
|
19483
19538
|
" - Strategy: evidence-first\n"
|
|
19539
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
19484
19540
|
" - M1: node test.js proves the public behavior\n"
|
|
19485
19541
|
" - Evidence:\n"
|
|
19486
19542
|
" - Contract: pending\n"
|
|
@@ -19925,6 +19981,7 @@ def validate_verify_layer_tag_scenario() -> int:
|
|
|
19925
19981
|
" - src/feature.js\n"
|
|
19926
19982
|
" - Verify:\n"
|
|
19927
19983
|
" - Strategy: evidence-first\n"
|
|
19984
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
19928
19985
|
" - M1 (fast): node fast.js\n"
|
|
19929
19986
|
" - M2: node full.js\n"
|
|
19930
19987
|
" - Autonomy boundary:\n"
|
|
@@ -21334,6 +21391,7 @@ def guard_task_fixture(checked: bool = False) -> str:
|
|
|
21334
21391
|
" - docs/**\n"
|
|
21335
21392
|
" - Verify:\n"
|
|
21336
21393
|
" - Strategy: evidence-first\n"
|
|
21394
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
21337
21395
|
" - M1: node test.js\n"
|
|
21338
21396
|
" - Evidence:\n"
|
|
21339
21397
|
" - M1: pending\n"
|
|
@@ -21433,6 +21491,7 @@ def record_layer_tasks(checked: bool = False, touch: str = "src/feature.js") ->
|
|
|
21433
21491
|
f" - {touch}\n"
|
|
21434
21492
|
" - Verify:\n"
|
|
21435
21493
|
" - Strategy: evidence-first\n"
|
|
21494
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
21436
21495
|
" - M1: node test.js\n"
|
|
21437
21496
|
" - Evidence:\n"
|
|
21438
21497
|
" - M1: pending\n"
|
|
@@ -21450,6 +21509,7 @@ def mode_fixture_tasks(mode: str, touch: str) -> str:
|
|
|
21450
21509
|
f" - {touch}\n"
|
|
21451
21510
|
" - Verify:\n"
|
|
21452
21511
|
" - Strategy: evidence-first\n"
|
|
21512
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
21453
21513
|
" - M1: git rev-parse HEAD resolves and git log reports one commit\n"
|
|
21454
21514
|
" - Evidence:\n"
|
|
21455
21515
|
" - Contract: pending\n"
|
|
@@ -21571,6 +21631,7 @@ def sibling_scope_tasks(sibling_checked: bool, sibling_touch: str) -> str:
|
|
|
21571
21631
|
+ "".join(f" - {entry}\n" for entry in touch.split(","))
|
|
21572
21632
|
+ " - Verify:\n"
|
|
21573
21633
|
" - Strategy: evidence-first\n"
|
|
21634
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
21574
21635
|
" - M1: node test.js\n"
|
|
21575
21636
|
" - Evidence:\n"
|
|
21576
21637
|
" - M1: verified\n"
|
|
@@ -22182,6 +22243,7 @@ def validate_touch_write_guard_scenario() -> int:
|
|
|
22182
22243
|
" - src/other.js\n"
|
|
22183
22244
|
" - Verify:\n"
|
|
22184
22245
|
" - Strategy: evidence-first\n"
|
|
22246
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
22185
22247
|
" - M1: node test.js\n"
|
|
22186
22248
|
" - Evidence:\n"
|
|
22187
22249
|
" - M1: pending\n"
|
|
@@ -22272,6 +22334,7 @@ def compaction_task_fixture() -> str:
|
|
|
22272
22334
|
" - src/feature.js\n"
|
|
22273
22335
|
" - Verify:\n"
|
|
22274
22336
|
" - Strategy: evidence-first\n"
|
|
22337
|
+
" - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
22275
22338
|
" - M1: node test.js\n"
|
|
22276
22339
|
" - Evidence:\n"
|
|
22277
22340
|
" - Contract: sha256:"
|
|
@@ -23569,7 +23632,7 @@ def validate_declared_paths_are_read_whole_scenario() -> int:
|
|
|
23569
23632
|
"- [x] 1.1 Exercise the declared-path readers\n"
|
|
23570
23633
|
" - Covers:\n - E1: Public behavior passes.\n"
|
|
23571
23634
|
" - Touch:\n - src/declared.js\n"
|
|
23572
|
-
" - Verify:\n - Strategy: evidence-first\n"
|
|
23635
|
+
" - Verify:\n - Strategy: evidence-first\n - Reason: this is a gate fixture; it exercises contract structure and has no executable behavior that can fail first\n"
|
|
23573
23636
|
" - M1: node test.js asserts the recorded feed status\n"
|
|
23574
23637
|
" - Evidence:\n"
|
|
23575
23638
|
" - Contract: keel-task-capsule/v1 sha256:"
|
|
@@ -24861,6 +24924,304 @@ def validate_output_survives_the_pipe_scenario() -> int:
|
|
|
24861
24924
|
return 0
|
|
24862
24925
|
|
|
24863
24926
|
|
|
24927
|
+
# A helper the strategy scenarios share. The task body is written out rather
|
|
24928
|
+
# than reusing `change_verify_task` because these scenarios need to omit fields
|
|
24929
|
+
# that helper always emits.
|
|
24930
|
+
def strategy_probe_task(
|
|
24931
|
+
*,
|
|
24932
|
+
strategy: str | None,
|
|
24933
|
+
reason: str | None = None,
|
|
24934
|
+
mode: str = "implementation",
|
|
24935
|
+
form: str = "Verify",
|
|
24936
|
+
commands: tuple[str, ...] = ("M1: node test.js asserts the public behavior",),
|
|
24937
|
+
) -> str:
|
|
24938
|
+
lines = [
|
|
24939
|
+
"- [ ] 1.1 Strategy probe",
|
|
24940
|
+
" - Owner: claude",
|
|
24941
|
+
f" - Mode: {mode}",
|
|
24942
|
+
" - Covers:",
|
|
24943
|
+
" - E1: the task proves its own behavior",
|
|
24944
|
+
" - Read:",
|
|
24945
|
+
" - README.md",
|
|
24946
|
+
" - Touch:",
|
|
24947
|
+
" - src/example.js",
|
|
24948
|
+
f" - {form}:",
|
|
24949
|
+
]
|
|
24950
|
+
if strategy is not None:
|
|
24951
|
+
label = "Strategy" if form == "Verify" else "Verification Strategy"
|
|
24952
|
+
lines.append(f" - {label}: {strategy}")
|
|
24953
|
+
if reason is not None:
|
|
24954
|
+
lines.append(f" - Reason: {reason}")
|
|
24955
|
+
lines.extend(f" - {entry}" for entry in commands)
|
|
24956
|
+
lines.extend(
|
|
24957
|
+
[
|
|
24958
|
+
" - Acceptance:",
|
|
24959
|
+
" - Public behavior passes.",
|
|
24960
|
+
" - Autonomy boundary:",
|
|
24961
|
+
" - Default: hard-stop",
|
|
24962
|
+
" - Pre-authorized fallback: none",
|
|
24963
|
+
" - Stop Rules:",
|
|
24964
|
+
" - Stop on failure.",
|
|
24965
|
+
" - Evidence:",
|
|
24966
|
+
" - Contract: pending",
|
|
24967
|
+
]
|
|
24968
|
+
)
|
|
24969
|
+
lines.extend(
|
|
24970
|
+
f" - {entry.split(':', 1)[0].split(' ')[0]}: pending" for entry in commands
|
|
24971
|
+
)
|
|
24972
|
+
lines.extend(
|
|
24973
|
+
[
|
|
24974
|
+
" - Review:",
|
|
24975
|
+
" - Status: pass",
|
|
24976
|
+
" - Acceptance check: behavior proven.",
|
|
24977
|
+
" - Scope check: writes stayed inside Touch.",
|
|
24978
|
+
" - Findings: none",
|
|
24979
|
+
" - Blocker: none",
|
|
24980
|
+
" - Stop if:",
|
|
24981
|
+
" - Requires files outside Touch.",
|
|
24982
|
+
]
|
|
24983
|
+
)
|
|
24984
|
+
return "\n".join(lines) + "\n"
|
|
24985
|
+
|
|
24986
|
+
|
|
24987
|
+
def strategy_probe_start(root: Path, name: str, task: str) -> dict:
|
|
24988
|
+
repo = root / name
|
|
24989
|
+
write_gate_fixture(repo, tasks=task)
|
|
24990
|
+
result = run_keel(
|
|
24991
|
+
repo, "gate", "task-start", "--change", "demo", "--task", "1.1", "--json"
|
|
24992
|
+
)
|
|
24993
|
+
try:
|
|
24994
|
+
return json.loads(result.stdout)
|
|
24995
|
+
except json.JSONDecodeError:
|
|
24996
|
+
return {"status": "unparsed", "problems": [{"message": result.stdout[:400]}]}
|
|
24997
|
+
|
|
24998
|
+
|
|
24999
|
+
def problem_text(payload: dict) -> str:
|
|
25000
|
+
return " ".join(
|
|
25001
|
+
str(entry.get("message", "")) for entry in (payload.get("problems") or [])
|
|
25002
|
+
)
|
|
25003
|
+
|
|
25004
|
+
|
|
25005
|
+
def problem_codes(payload: dict) -> list[str]:
|
|
25006
|
+
return [str(entry.get("code", "")) for entry in (payload.get("problems") or [])]
|
|
25007
|
+
|
|
25008
|
+
|
|
25009
|
+
# The strategy was the one capsule field the compiler supplied from a value no
|
|
25010
|
+
# spec documents as a default — and it supplied the weakest of the six, so
|
|
25011
|
+
# omitting the line was how a task opted out of red-green.
|
|
25012
|
+
def validate_strategy_is_declared_scenario() -> int:
|
|
25013
|
+
label = "a-strategy-is-declared"
|
|
25014
|
+
with tempfile.TemporaryDirectory(prefix="keel-strategy-") as raw:
|
|
25015
|
+
root = Path(raw)
|
|
25016
|
+
|
|
25017
|
+
missing = strategy_probe_start(
|
|
25018
|
+
root, "missing", strategy_probe_task(strategy=None)
|
|
25019
|
+
)
|
|
25020
|
+
if missing.get("status") != "fail":
|
|
25021
|
+
report(
|
|
25022
|
+
f"{label}: a task declaring Verify with no Strategy was not "
|
|
25023
|
+
f"refused; task-start returned {missing.get('status')!r}."
|
|
25024
|
+
)
|
|
25025
|
+
return 1
|
|
25026
|
+
text = problem_text(missing)
|
|
25027
|
+
for needed in ("vertical-tdd", "evidence-first", "strategy"):
|
|
25028
|
+
if needed not in text:
|
|
25029
|
+
report(
|
|
25030
|
+
f"{label}: the refusal for a missing strategy omits "
|
|
25031
|
+
f"{needed!r}; got {text!r}."
|
|
25032
|
+
)
|
|
25033
|
+
return 1
|
|
25034
|
+
# The failure this change exists to remove: reporting the task as using
|
|
25035
|
+
# a strategy it never named.
|
|
25036
|
+
if "unsupported" in text:
|
|
25037
|
+
report(
|
|
25038
|
+
f"{label}: a missing strategy was reported as an unsupported "
|
|
25039
|
+
f"one, which sends the reader looking for a typo; got {text!r}."
|
|
25040
|
+
)
|
|
25041
|
+
return 1
|
|
25042
|
+
if "missing-verification-strategy" not in problem_codes(missing):
|
|
25043
|
+
report(
|
|
25044
|
+
f"{label}: the refusal for a missing strategy carries no "
|
|
25045
|
+
f"missing-verification-strategy code; got "
|
|
25046
|
+
f"{problem_codes(missing)!r}."
|
|
25047
|
+
)
|
|
25048
|
+
return 1
|
|
25049
|
+
|
|
25050
|
+
legacy = strategy_probe_start(
|
|
25051
|
+
root,
|
|
25052
|
+
"legacy",
|
|
25053
|
+
strategy_probe_task(strategy=None, form="Commands"),
|
|
25054
|
+
)
|
|
25055
|
+
if legacy.get("status") != "fail":
|
|
25056
|
+
report(
|
|
25057
|
+
f"{label}: an expanded v3 task declaring Commands with no "
|
|
25058
|
+
"Verification Strategy was not refused; task-start returned "
|
|
25059
|
+
f"{legacy.get('status')!r}."
|
|
25060
|
+
)
|
|
25061
|
+
return 1
|
|
25062
|
+
|
|
25063
|
+
for strategy in (
|
|
25064
|
+
"vertical-tdd",
|
|
25065
|
+
"regression-first",
|
|
25066
|
+
"characterization",
|
|
25067
|
+
"snapshot-characterization",
|
|
25068
|
+
"rendered-behavior",
|
|
25069
|
+
):
|
|
25070
|
+
declared = strategy_probe_start(
|
|
25071
|
+
root, f"ok-{strategy}", strategy_probe_task(strategy=strategy)
|
|
25072
|
+
)
|
|
25073
|
+
if declared.get("status") != "pass":
|
|
25074
|
+
report(
|
|
25075
|
+
f"{label}: a task declaring {strategy} was refused; "
|
|
25076
|
+
f"{problem_text(declared)!r}."
|
|
25077
|
+
)
|
|
25078
|
+
return 1
|
|
25079
|
+
|
|
25080
|
+
unsupported = strategy_probe_start(
|
|
25081
|
+
root, "unsupported", strategy_probe_task(strategy="made-up-thing")
|
|
25082
|
+
)
|
|
25083
|
+
if unsupported.get("status") != "fail":
|
|
25084
|
+
report(
|
|
25085
|
+
f"{label}: an unsupported strategy was not refused; task-start "
|
|
25086
|
+
f"returned {unsupported.get('status')!r}."
|
|
25087
|
+
)
|
|
25088
|
+
return 1
|
|
25089
|
+
if "unsupported" not in problem_text(unsupported):
|
|
25090
|
+
report(
|
|
25091
|
+
f"{label}: an unsupported strategy lost its own diagnostic; "
|
|
25092
|
+
f"got {problem_text(unsupported)!r}."
|
|
25093
|
+
)
|
|
25094
|
+
return 1
|
|
25095
|
+
|
|
25096
|
+
# A task declaring no verification form at all is already reported once,
|
|
25097
|
+
# as the one field it is missing. Restating it as a missing strategy is
|
|
25098
|
+
# the cascade this repository has removed before.
|
|
25099
|
+
formless = strategy_probe_start(
|
|
25100
|
+
root,
|
|
25101
|
+
"formless",
|
|
25102
|
+
strategy_probe_task(strategy=None).replace(" - Verify:\n", ""),
|
|
25103
|
+
)
|
|
25104
|
+
codes = problem_codes(formless)
|
|
25105
|
+
if "missing-verification-strategy" in codes:
|
|
25106
|
+
report(
|
|
25107
|
+
f"{label}: a task declaring no verification form was also told "
|
|
25108
|
+
f"its strategy is missing; codes {codes!r}."
|
|
25109
|
+
)
|
|
25110
|
+
return 1
|
|
25111
|
+
if codes != ["missing-verification-form"]:
|
|
25112
|
+
report(
|
|
25113
|
+
f"{label}: a task declaring no verification form should be "
|
|
25114
|
+
f"reported once, as that; got codes {codes!r}."
|
|
25115
|
+
)
|
|
25116
|
+
return 1
|
|
25117
|
+
|
|
25118
|
+
report(f"{label} scenario passed.")
|
|
25119
|
+
return 0
|
|
25120
|
+
|
|
25121
|
+
|
|
25122
|
+
# `evidence-first` is scoped by an absence — work that cannot use a meaningful
|
|
25123
|
+
# red-green loop — and an absence is exactly the claim that has to be stated to
|
|
25124
|
+
# be reviewable. The gate checks the reason is there and is a sentence; whether
|
|
25125
|
+
# it is true belongs to the Review, the same contract `Discard reason:` has.
|
|
25126
|
+
def validate_weakest_strategy_states_its_reason_scenario() -> int:
|
|
25127
|
+
label = "the-weakest-strategy-states-its-reason"
|
|
25128
|
+
with tempfile.TemporaryDirectory(prefix="keel-reason-") as raw:
|
|
25129
|
+
root = Path(raw)
|
|
25130
|
+
|
|
25131
|
+
silent = strategy_probe_start(
|
|
25132
|
+
root, "silent", strategy_probe_task(strategy="evidence-first")
|
|
25133
|
+
)
|
|
25134
|
+
if silent.get("status") != "fail":
|
|
25135
|
+
report(
|
|
25136
|
+
f"{label}: an evidence-first task stating no reason was not "
|
|
25137
|
+
f"refused; task-start returned {silent.get('status')!r}."
|
|
25138
|
+
)
|
|
25139
|
+
return 1
|
|
25140
|
+
if "reason" not in problem_text(silent).lower():
|
|
25141
|
+
report(
|
|
25142
|
+
f"{label}: the refusal does not name the reason as what is "
|
|
25143
|
+
f"missing; got {problem_text(silent)!r}."
|
|
25144
|
+
)
|
|
25145
|
+
return 1
|
|
25146
|
+
|
|
25147
|
+
for slot in ("TODO", "<why>"):
|
|
25148
|
+
empty = strategy_probe_start(
|
|
25149
|
+
root,
|
|
25150
|
+
f"slot-{slot.strip('<>')}",
|
|
25151
|
+
strategy_probe_task(strategy="evidence-first", reason=slot),
|
|
25152
|
+
)
|
|
25153
|
+
if empty.get("status") != "fail":
|
|
25154
|
+
report(
|
|
25155
|
+
f"{label}: an evidence-first task whose reason is {slot!r} "
|
|
25156
|
+
f"was accepted; task-start returned {empty.get('status')!r}."
|
|
25157
|
+
)
|
|
25158
|
+
return 1
|
|
25159
|
+
|
|
25160
|
+
stated = strategy_probe_start(
|
|
25161
|
+
root,
|
|
25162
|
+
"stated",
|
|
25163
|
+
strategy_probe_task(
|
|
25164
|
+
strategy="evidence-first",
|
|
25165
|
+
reason="this task only moves documentation; there is no "
|
|
25166
|
+
"executable behavior that can fail first.",
|
|
25167
|
+
),
|
|
25168
|
+
)
|
|
25169
|
+
if stated.get("status") != "pass":
|
|
25170
|
+
report(
|
|
25171
|
+
f"{label}: an evidence-first task stating a reason was refused; "
|
|
25172
|
+
f"{problem_text(stated)!r}."
|
|
25173
|
+
)
|
|
25174
|
+
return 1
|
|
25175
|
+
verification = (
|
|
25176
|
+
(stated.get("contract") or {})
|
|
25177
|
+
.get("capsule", {})
|
|
25178
|
+
.get("verification", {})
|
|
25179
|
+
)
|
|
25180
|
+
if "documentation" not in str(verification.get("reason", "")):
|
|
25181
|
+
report(
|
|
25182
|
+
f"{label}: the compiled capsule does not carry the stated "
|
|
25183
|
+
f"reason in its verification block; got {verification!r}."
|
|
25184
|
+
)
|
|
25185
|
+
return 1
|
|
25186
|
+
# D5: the reason is a field, not a check. A reason that took an M<n>
|
|
25187
|
+
# label would be a check the author never wrote.
|
|
25188
|
+
labels = [entry.get("label") for entry in verification.get("commands", [])]
|
|
25189
|
+
if labels != ["M1"]:
|
|
25190
|
+
report(
|
|
25191
|
+
f"{label}: the reason was parsed as a check; the compiled "
|
|
25192
|
+
f"checks are {labels!r}."
|
|
25193
|
+
)
|
|
25194
|
+
return 1
|
|
25195
|
+
|
|
25196
|
+
# D3: no exemption. A mode the same author writes would be a second
|
|
25197
|
+
# escape hatch, and the first one is what this change closes.
|
|
25198
|
+
diagnostic = strategy_probe_start(
|
|
25199
|
+
root,
|
|
25200
|
+
"diagnose-only",
|
|
25201
|
+
strategy_probe_task(strategy="evidence-first", mode="diagnose-only"),
|
|
25202
|
+
)
|
|
25203
|
+
if diagnostic.get("status") != "fail":
|
|
25204
|
+
report(
|
|
25205
|
+
f"{label}: a diagnose-only task skipped the reason; task-start "
|
|
25206
|
+
f"returned {diagnostic.get('status')!r}."
|
|
25207
|
+
)
|
|
25208
|
+
return 1
|
|
25209
|
+
|
|
25210
|
+
# D4: the requirement reaches only the strategy it is about.
|
|
25211
|
+
redgreen = strategy_probe_start(
|
|
25212
|
+
root, "redgreen", strategy_probe_task(strategy="vertical-tdd")
|
|
25213
|
+
)
|
|
25214
|
+
if redgreen.get("status") != "pass":
|
|
25215
|
+
report(
|
|
25216
|
+
f"{label}: a vertical-tdd task was asked for a reason; "
|
|
25217
|
+
f"{problem_text(redgreen)!r}."
|
|
25218
|
+
)
|
|
25219
|
+
return 1
|
|
25220
|
+
|
|
25221
|
+
report(f"{label} scenario passed.")
|
|
25222
|
+
return 0
|
|
25223
|
+
|
|
25224
|
+
|
|
24864
25225
|
# A scenario name, as the registry spells one. Two registered names carry no
|
|
24865
25226
|
# hyphen — `cli` and `uninstall` — so requiring one would leave exactly those
|
|
24866
25227
|
# two unchecked, and allowing single words was measured to add no false
|
|
@@ -25092,6 +25453,8 @@ SCENARIOS: tuple = (
|
|
|
25092
25453
|
("doctor-openspec-honesty", validate_doctor_openspec_honesty_scenario),
|
|
25093
25454
|
("the-marker-version-is-read", validate_marker_version_is_read_scenario),
|
|
25094
25455
|
("output-survives-the-pipe", validate_output_survives_the_pipe_scenario),
|
|
25456
|
+
("a-strategy-is-declared", validate_strategy_is_declared_scenario),
|
|
25457
|
+
("the-weakest-strategy-states-its-reason", validate_weakest_strategy_states_its_reason_scenario),
|
|
25095
25458
|
(
|
|
25096
25459
|
"authored-scenario-names-are-registered",
|
|
25097
25460
|
validate_authored_scenario_names_scenario,
|
|
@@ -175,8 +175,16 @@ function isPassingReviewStatus(value) {
|
|
|
175
175
|
function verification(task) {
|
|
176
176
|
const compact = fieldValues(task, "Verify");
|
|
177
177
|
const strategyEntry = compact.find((entry) => /^Strategy:\s*/i.test(entry));
|
|
178
|
+
// `evidence-first` is the one strategy scoped by an absence — work that
|
|
179
|
+
// cannot use a meaningful red-green loop — so the task states why. It is a
|
|
180
|
+
// field beside `Strategy:`, not a check: everything else under `Verify` is a
|
|
181
|
+
// command, and a reason that took an `M<n>` label would be a check the author
|
|
182
|
+
// never wrote and evidence nobody can record.
|
|
183
|
+
const reasonEntry = compact.find((entry) => /^Reason:\s*/i.test(entry));
|
|
184
|
+
const isVerificationField = (entry) =>
|
|
185
|
+
/^Strategy:\s*/i.test(entry) || /^Reason:\s*/i.test(entry);
|
|
178
186
|
const commandSource = compact.length > 0
|
|
179
|
-
? compact.filter((entry) =>
|
|
187
|
+
? compact.filter((entry) => !isVerificationField(entry))
|
|
180
188
|
: fieldValues(task, "Commands");
|
|
181
189
|
const commands = commandSource.map((entry) => {
|
|
182
190
|
// An optional tag set after the M<n> label. `fast`/`full` marks which checks
|
|
@@ -202,11 +210,21 @@ function verification(task) {
|
|
|
202
210
|
});
|
|
203
211
|
return {
|
|
204
212
|
compact: compact.length > 0,
|
|
213
|
+
// No default. The strategy is not among the defaults the capsule inherits,
|
|
214
|
+
// and the value this once fell back to — `evidence-first` — is the one
|
|
215
|
+
// strategy with no red-green requirement, so omitting the line was how a
|
|
216
|
+
// task opted out of red-green with nothing on screen saying so. An absent
|
|
217
|
+
// strategy is now reported rather than chosen.
|
|
205
218
|
strategy: normalizeText(
|
|
206
219
|
strategyEntry
|
|
207
220
|
? strategyEntry.replace(/^Strategy:\s*/i, "")
|
|
208
221
|
: field(task, "Verification Strategy")
|
|
209
|
-
)
|
|
222
|
+
),
|
|
223
|
+
reason: normalizeText(
|
|
224
|
+
reasonEntry
|
|
225
|
+
? reasonEntry.replace(/^Reason:\s*/i, "")
|
|
226
|
+
: field(task, "Verification Reason")
|
|
227
|
+
),
|
|
210
228
|
commands,
|
|
211
229
|
};
|
|
212
230
|
}
|
|
@@ -920,7 +938,24 @@ function compileTaskContract(repo, change, task) {
|
|
|
920
938
|
});
|
|
921
939
|
}
|
|
922
940
|
}
|
|
923
|
-
|
|
941
|
+
// A task that declared no verification form at all is reported once, by
|
|
942
|
+
// requiredFieldProblems, as the one field it is missing. Telling it its
|
|
943
|
+
// strategy is missing too is the cascade that buries the actionable line.
|
|
944
|
+
const declaredVerificationForm =
|
|
945
|
+
fieldValues(task, "Verify").length > 0
|
|
946
|
+
|| fieldValues(task, "Commands").length > 0;
|
|
947
|
+
if (!taskVerification.strategy) {
|
|
948
|
+
if (declaredVerificationForm) {
|
|
949
|
+
resolved.diagnostics.push({
|
|
950
|
+
code: "missing-verification-strategy",
|
|
951
|
+
message:
|
|
952
|
+
"Verification strategy is missing; a task that declares checks "
|
|
953
|
+
+ "declares the strategy that governs them, because none is "
|
|
954
|
+
+ "supplied by default. Supported: "
|
|
955
|
+
+ `${SUPPORTED_VERIFICATION_STRATEGIES.join(", ")}.`,
|
|
956
|
+
});
|
|
957
|
+
}
|
|
958
|
+
} else if (
|
|
924
959
|
!SUPPORTED_VERIFICATION_STRATEGIES.includes(
|
|
925
960
|
taskVerification.strategy.toLowerCase()
|
|
926
961
|
)
|
|
@@ -931,6 +966,22 @@ function compileTaskContract(repo, change, task) {
|
|
|
931
966
|
`Verification strategy is unsupported: ${taskVerification.strategy}; `
|
|
932
967
|
+ `supported: ${SUPPORTED_VERIFICATION_STRATEGIES.join(", ")}.`,
|
|
933
968
|
});
|
|
969
|
+
} else if (
|
|
970
|
+
taskVerification.strategy.toLowerCase() === "evidence-first"
|
|
971
|
+
&& !isConcrete(taskVerification.reason)
|
|
972
|
+
) {
|
|
973
|
+
// Presence and concreteness, never truth — the contract `Discard reason:`
|
|
974
|
+
// already has. No mode exempts a task from stating it: an exemption keyed
|
|
975
|
+
// on a field the same author writes is a second escape hatch.
|
|
976
|
+
resolved.diagnostics.push({
|
|
977
|
+
code: "missing-evidence-first-reason",
|
|
978
|
+
message:
|
|
979
|
+
"evidence-first states why a meaningful red-green loop does not "
|
|
980
|
+
+ "apply. Add a `Reason:` entry beside `Strategy:` saying what makes "
|
|
981
|
+
+ "this task non-behavioral — docs, configuration, diagnosis, or "
|
|
982
|
+
+ "another reason nothing here can fail first. A red-green strategy "
|
|
983
|
+
+ "needs no reason.",
|
|
984
|
+
});
|
|
934
985
|
}
|
|
935
986
|
const couplingMode = normalizeText(field(task, "Coupling")).toLowerCase()
|
|
936
987
|
|| "none";
|
|
@@ -1048,6 +1099,10 @@ function compileTaskContract(repo, change, task) {
|
|
|
1048
1099
|
acceptance: [...new Set([...derivedAcceptance, ...explicitAcceptance])],
|
|
1049
1100
|
verification: {
|
|
1050
1101
|
strategy: taskVerification.strategy,
|
|
1102
|
+
// Emitted only when the strategy is the one that requires it, so every
|
|
1103
|
+
// other task keeps the capsule shape and fingerprint it had before the
|
|
1104
|
+
// field existed.
|
|
1105
|
+
...(taskVerification.reason ? { reason: taskVerification.reason } : {}),
|
|
1051
1106
|
// Emit a tag only when the check opts out of a default, so an untagged
|
|
1052
1107
|
// check keeps the capsule shape and fingerprint it had before either tag
|
|
1053
1108
|
// existed. `layer` appears only for `fast`, `regression` only when true.
|