@afokapu/atdd-bun 0.10.41 → 0.10.43

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.
@@ -21,12 +21,28 @@ content:
21
21
  finding belongs to `test-carries-urn-identity`.
22
22
  normative_text: |
23
23
  A URN says which test this is; the acceptance binding says which OBLIGATION it discharges. Only the second one closes the loop the gate reads.
24
- Two binding kinds are accepted because a journey test legitimately proves a train rather than a single acceptance. Forcing every test to name an acceptance would push authors to invent one, which pollutes the plan graph with acceptances that exist only to satisfy a linter.
24
+ The binding is a claim, not bookkeeping. A syntactically valid Acceptance or Train header is truthful only when the test actually exercises that declared subject and asserts the outcome it requires. Binding a convenient existing acceptance to an unrelated case creates false coverage while satisfying the structural detector.
25
+ Two binding kinds are accepted because a journey test legitimately proves a train rather than a single acceptance. Forcing every test to name an acceptance would push authors to invent one, which pollutes the plan graph with acceptances that exist only to satisfy a linter. The same principle applies in reverse: never select or invent a binding merely to make the test attributable.
25
26
  fix_hint: |
26
- Name the acceptance the test discharges, or the train it walks:
27
+ Derive the binding from the plan artifact the test actually proves.
27
28
 
28
- // Acceptance: acc:orders:E001-SMOKE-001-renders-row
29
- // Train: train:orders:checkout-journey
29
+ Claim:
30
+ * identify the declared acceptance whose behavior this file exercises and asserts, or the declared train the journey actually walks.
31
+
32
+ Bind:
33
+ * use that exact plan identity in the header:
34
+
35
+ // Acceptance: acc:orders:E001-SMOKE-001-renders-row
36
+ // Train: train:orders:checkout-journey
37
+
38
+ Avoid:
39
+ * picking any resolvable acceptance merely to satisfy coverage,
40
+ * changing the claimed binding to match a test that proves different behavior,
41
+ * inventing a new acceptance or train solely so an otherwise orphaned test can bind.
42
+
43
+ Verify:
44
+ * the test stimulus exercises the bound subject,
45
+ * its behavioral witness proves the bound obligation rather than an adjacent implementation detail.
30
46
  exceptions:
31
47
  - A `Train:` binding satisfies the rule for journey and E2E tests.
32
48
  - Files with no URN are owned by `test-carries-urn-identity`.
@@ -21,14 +21,20 @@ content:
21
21
  tag against `acc:{wagon}:{ACCEPTANCE-ID}`.
22
22
  normative_text: |
23
23
  The two-level model is the point. The file header binds the FILE to its acceptances; a `@covers` tag binds ONE test case. That gives many-cases-to-one-acceptance precision without forcing one acceptance per file — the design issue #1783 argued for, and the reason a real 65-spec suite can adopt ATDD without a rename.
24
- A malformed tag is `strict` because it is the worst of both worlds: it binds nothing, while looking to a reviewer exactly like a binding that works.
24
+ A `@covers` tag is therefore a semantic claim as well as a token. The case should name only acceptances whose behavior it directly exercises and whose outcome it actually asserts. Adding a valid acceptance URN merely to increase apparent coverage turns precise traceability into fiction.
25
+ A malformed tag is `strict` because it is the worst of both worlds: it binds nothing, while looking to a reviewer exactly like a binding that works. A well-formed but semantically false tag is outside this detector's static reach, so the convention guidance must prevent that form of gaming.
25
26
  fix_hint: |
26
- Use the full acceptance URN, not a bare id:
27
+ Use the full acceptance URN and bind only behavior this case actually proves:
27
28
 
28
- // @covers E005-UNIT-001 // wrong — no scheme, no wagon
29
- // @covers acc:orders:E005-UNIT-001 // right
29
+ // @covers E005-UNIT-001 // wrong — no scheme, no wagon
30
+ // @covers acc:orders:E005-UNIT-001 // structurally valid
30
31
 
31
- Several tags on one line are allowed; a case may cover more than one acceptance.
32
+ Before adding a valid tag:
33
+ * identify the acceptance claim exercised by this exact case,
34
+ * confirm the case's stimulus reaches that behavior,
35
+ * confirm at least one behavioral assertion witnesses its declared outcome.
36
+
37
+ Several tags on one line are allowed when one case genuinely proves more than one acceptance. Do not add extra tags for coverage credit when the case does not exercise and assert those obligations.
32
38
  exceptions:
33
39
  - Cases with no `@covers` tag are not required to have one — the file header binding still applies.
34
40
  - Only the tag's SHAPE is validated; resolution against `plan/` is a graph-level concern.
@@ -19,29 +19,32 @@ content:
19
19
  `// WMBT:` header names one the plan never mentions. Member of the `bun_tester_discipline_detector`
20
20
  FAMILY detector (checks/t_acceptance_resolves.mjs).
21
21
  normative_text: |
22
- Its sibling `tester.bun.acceptance-binding-declared` asks only whether a header is PRESENT, which
23
- a test satisfies by naming anything at all. Measured on a consumer, a test bound to
24
- `acc:does-not-exist:E999-NEVER-DECLARED` passed every gated rule in this workspace. That is the
25
- recurring defect this hub keeps finding: a NAME accepted where a CLAIM is required. The two rules
26
- are complementary — one requires the binding to exist, this one requires it to be true.
27
-
28
- Declared URNs are read as TOKENS rather than parsed structurally, on purpose: acceptances are
29
- stated under several keys across wagon, feature and WMBT documents, and a reference resolves if
30
- the plan states that URN anywhere. Over-accepting is the safe direction — the failure this rule
31
- exists to catch is a reference to something the plan never mentions.
32
-
33
- NOT_APPLICABLE when the consumer declares no acceptances at all: a repository with no plan has
34
- nothing to resolve against, and reporting there would fire on every unplanned project rather than
35
- on a broken reference.
22
+ Its sibling `tester.bun.acceptance-binding-declared` asks only whether a header is PRESENT, which a test satisfies by naming anything at all. Measured on a consumer, a test bound to `acc:does-not-exist:E999-NEVER-DECLARED` passed every gated rule in this workspace. That is the recurring defect this hub keeps finding: a NAME accepted where a CLAIM is required. The two rules are complementary — one requires the binding to exist, this one requires it to be true.
23
+
24
+ Resolution is still only the structural half of truth. A header can resolve to a real acceptance and still be semantically false if the case exercises or asserts something else. During test authoring, the plan is authoritative: repair a stale or mistaken binding to the acceptance the case genuinely proves. Do not create, rename, or repurpose a plan acceptance merely to make an already-written test resolve; adding behavior to the plan is planner work and must be justified by the behavior itself, not by this detector.
25
+
26
+ Declared URNs are read as TOKENS rather than parsed structurally, on purpose: acceptances are stated under several keys across wagon, feature and WMBT documents, and a reference resolves if the plan states that URN anywhere. Over-accepting is the safe direction — the failure this rule exists to catch is a reference to something the plan never mentions.
27
+
28
+ NOT_APPLICABLE when the consumer declares no acceptances at all: a repository with no plan has nothing to resolve against, and reporting there would fire on every unplanned project rather than on a broken reference.
36
29
  fix_hint: |
37
- Point the header at an acceptance the plan declares, or declare the acceptance:
30
+ Repair the binding from the plan, not the plan from the test.
31
+
32
+ If the acceptance was renamed or the header is stale:
33
+ * point the test at the declared acceptance it actually exercises and proves.
34
+
35
+ // URN: test:orders:checkout:E001-SMOKE-001-renders-row
36
+ // Acceptance: acc:orders:E001-SMOKE-001-renders-row # stated in plan/orders/E001.yaml
37
+ // WMBT: wmbt:orders:E001
38
+
39
+ If the test proves different behavior:
40
+ * bind it to that behavior's existing declared acceptance,
41
+ * adjust the test rather than the label when the current case does not prove the claimed acceptance.
38
42
 
39
- // URN: test:orders:checkout:E001-SMOKE-001-renders-row
40
- // Acceptance: acc:orders:E001-SMOKE-001-renders-row # stated in plan/orders/E001.yaml
41
- // WMBT: wmbt:orders:E001
43
+ If no declared acceptance represents the intended behavior:
44
+ * do not invent one merely to clear this tester finding,
45
+ * return to the authorized planning flow so the behavior can be declared deliberately before the test claims coverage.
42
46
 
43
- A renamed acceptance is the common cause — the plan moved and the header did not. This rule is
44
- `strict`: a test bound to an artifact that does not exist is not suppressible and fails CI.
47
+ A renamed acceptance is the common mechanical cause; a convenient but semantically unrelated acceptance is the dangerous gaming case. This rule is `strict`: a test bound to an artifact that does not exist is not suppressible and fails CI.
45
48
  exceptions:
46
49
  - A test carrying no binding header at all is NOT reported here; that is
47
50
  `tester.bun.acceptance-binding-declared`'s obligation, and reporting both would double-count
@@ -25,6 +25,10 @@ content:
25
25
  The core runtime call model (afokapu/atdd#1251) is `Station Master -> InterlockingRunner.resolveTrain
26
26
  -> TrainRunner.execute`. A test that claims to exercise an interlocking route but replaces either
27
27
  runner with a test double exercises the double, not production route control.
28
+ Referencing both production symbols is the detector's structural proxy; it is not sufficient semantic
29
+ proof by itself. The case must drive the production objects with guard-relevant inputs and observe the
30
+ route/train behavior it claims. Importing or instantiating the real runners while obtaining the asserted
31
+ result from a parallel fake path would be another descriptive green.
28
32
 
29
33
  REQUIRED production symbols an interlocking test must reference:
30
34
  - InterlockingRunner
@@ -43,13 +47,31 @@ content:
43
47
  WHICH per-stack discovery applies (here: Bun `e2e/**/*.ts` scanned for the production symbols and
44
48
  the forbidden patterns) is delegated to this realization.
45
49
  fix_hint: |
46
- Rewrite the flagged interlocking test to drive the real runners: import and call
47
- `new InterlockingRunner(interlockingYamlPath).resolveTrain(action, inputs)` to get the selected
48
- trainId, then `new TrainRunner(selectedTrainId).execute(...)`. Delete any `MockInterlockingRunner` /
49
- `MockTrainRunner`, any `mock.module` / `spyOn` / `mock` / `mock.restore` (bun:test) or
50
- `vi.mock` / `vi.spyOn` / jest equivalent aimed at the runners or their `resolveTrain` / `execute`
51
- methods, and any locally hand-built route resolver. This rule is `strict`: an interlocking test that
52
- mocks the route-control path is a descriptive green proving nothing about production and blocks.
50
+ Rewrite the flagged case so the production runner path is the path that produces the asserted evidence.
51
+
52
+ Boundary:
53
+ * import and call `new InterlockingRunner(interlockingYamlPath).resolveTrain(action, inputs)`,
54
+ * pass its selected `trainId` into the production `TrainRunner`,
55
+ * execute the selected train through the real runtime path.
56
+
57
+ Witness:
58
+ * assert the selected train and the route- or acceptance-specific result produced by its execution.
59
+
60
+ Remove:
61
+ * `MockInterlockingRunner` / `MockTrainRunner`,
62
+ * `mock.module` / `spyOn` / `mock` / `mock.restore` aimed at the runners,
63
+ * `vi.mock` / `vi.spyOn` / Jest equivalents around runner behavior,
64
+ * any locally hand-built route resolver standing in for InterlockingRunner.
65
+
66
+ Avoid:
67
+ * importing the production symbols only to satisfy this detector while assertions still depend on a fake path,
68
+ * asserting a plan literal without driving production resolution and execution.
69
+
70
+ Verify:
71
+ * breaking production `resolveTrain` or `execute` behavior would make the test red,
72
+ * the evidence comes from the real runner chain.
73
+
74
+ This rule is `strict`: an interlocking test that mocks the route-control path is a descriptive green proving nothing about production and blocks.
53
75
  exceptions:
54
76
  - Non-interlocking e2e tests (no reference to any interlocking route/train/interlocking token and no
55
77
  runner symbol) are out of scope — they are not interlocking tests.
@@ -28,17 +28,42 @@ content:
28
28
  The interlocking YAML is authoritative for the guarded route space (afokapu/atdd#1248): every entry
29
29
  under `routes:` is an admissible route the InterlockingRunner can resolve to exactly one `trainId`
30
30
  (afokapu/atdd#1251). Coverage of that route space is a tester obligation — an unexercised route is a
31
- branch of production route control that no test ever drives. A route is COVERED when at least one
32
- `e2e/**/*.ts` test references it by its `routeId` or by the `trainId` it resolves to. WHICH per-stack
33
- discovery applies (here: Bun `e2e/**/*.ts` scanned for the route/train tokens) is delegated to
34
- this realization; other stacks render the same obligation in their own e2e layout.
31
+ branch of production route control that no test ever drives. A route is COVERED to the detector when at
32
+ least one `e2e/**/*.ts` test references it by its `routeId` or by the `trainId` it resolves to.
33
+ That token is a discovery proxy, not the testing objective. A meaningful route proof must construct the
34
+ route's guard-relevant inputs, drive production resolution, and assert that the declared route selects
35
+ the expected train before that train is executed. Merely mentioning the route or train in executable
36
+ test text can satisfy lexical discovery while proving no route behavior.
37
+ WHICH per-stack discovery applies (here: Bun `e2e/**/*.ts` scanned for the route/train tokens) is
38
+ delegated to this realization; other stacks render the same obligation in their own e2e layout.
35
39
  fix_hint: |
36
- Add an end-to-end test that exercises the flagged route through the production runners. Place it
37
- under `e2e/interlockings/<interlocking_slug>/<slug>.routes.test.ts` and drive
38
- `new InterlockingRunner(...).resolveTrain(...)` so the route's guard-true case resolves to its
39
- expected `trainId`, then have `TrainRunner` execute it. Reference the route by its `routeId` or
40
- `trainId` so coverage is machine-discoverable. Do NOT satisfy this with a mock resolver or a
41
- descriptive assertion. This rule is `strict`: an admissible route with no covering e2e test blocks.
40
+ Add an end-to-end proof of the flagged route rather than merely adding a discoverable token.
41
+
42
+ Claim:
43
+ * read the declared route, its guard, and its resolved `trainId`,
44
+ * identify the guard-true inputs that distinguish this route from its alternatives.
45
+
46
+ Stimulus:
47
+ * place the test under `e2e/interlockings/<interlocking_slug>/<slug>.routes.test.ts`,
48
+ * drive `new InterlockingRunner(...).resolveTrain(action, inputs)` with those inputs,
49
+ * pass the selected train into `TrainRunner` and execute it.
50
+
51
+ Witness:
52
+ * assert that production resolution selected the route's declared `trainId`,
53
+ * assert the route- or acceptance-specific execution outcome when one is declared.
54
+
55
+ Avoid:
56
+ * inserting a `routeId` / `trainId` literal solely so lexical coverage sees it,
57
+ * a mock resolver or hand-built substitute for production route control,
58
+ * a descriptive assertion that restates the plan without exercising resolution,
59
+ * bypassing TrainRunner after proving only selection.
60
+
61
+ Verify:
62
+ * the case would fail if the route guard or route-to-train mapping regressed,
63
+ * the production runners actually execute the selected path,
64
+ * the discoverable route/train token corresponds to behavior the case genuinely proves.
65
+
66
+ This rule is `strict`: an admissible route with no covering e2e test blocks.
42
67
  exceptions:
43
68
  - YAML under the interlocking scope that declares no `routes:` (the `_interlockings.yaml` registry
44
69
  index and generated projections) carries no admissible route space and is out of scope.
@@ -22,13 +22,47 @@ content:
22
22
  For each `it()` in a RED-phase file, brace-matches the case body and reports it when it contains no
23
23
  `expect(` at all, or when every `expect(` is an existence matcher.
24
24
  normative_text: |
25
- RED's entire job is to fail before the implementation exists. `expect(checkout).toBeDefined()` passes the moment an empty function is exported, so it goes green against a stub — the test was never RED in any meaningful sense, and the acceptance it claims to prove is unproven from that point forward.
26
- The rule is deliberately narrow: it fires only when EVERY assertion in the case is vacuous. A case that checks existence and then checks behaviour is fine, because the behavioural assertion is the one that must fail.
25
+ RED's entire job is not merely to be red; it is to construct the smallest executable proof of the declared acceptance before implementation makes that proof pass. `expect(checkout).toBeDefined()` passes the moment an empty function is exported, so it goes green against a stub — the acceptance it claims to prove is unproven from that point forward.
26
+ Treat the acceptance as the source of the test, not the implementation. The acceptance identifies the claim; any declared harness and execution context constrain the proof boundary; its Given supplies only required preconditions; its When supplies the primary stimulus; and its Then supplies the behavioral witness. The assertion should observe the consequence the acceptance promises, not an implementation mechanism used to produce it.
27
+ A useful RED sensor is counterfactually sensitive: if the named behavior were absent, inverted, or replaced by a stub, the behavioral assertion would fail. Expected values computed by the production subject, assertions on mocks that replace the behavior being proved, or unrelated assertions added only to satisfy this detector can all produce structurally acceptable tests that prove the wrong thing.
28
+ The detector is deliberately narrower than that semantic obligation: it fires only when EVERY assertion in the case is vacuous. A case that checks existence and then checks behaviour is not flagged, because the behavioral assertion is the machine-visible proxy. The convention guidance defines what that assertion is for.
27
29
  fix_hint: |
28
- Assert the behaviour that does not yet exist:
30
+ Construct the RED case from the declared acceptance rather than from the code you expect to write.
29
31
 
30
- expect(checkout).toBeDefined(); // passes against a stub
31
- expect(() => checkout({ items: [] })).toThrow("CART_EMPTY"); // must fail first
32
+ Claim:
33
+ * read the bound Acceptance / @covers declaration and identify the one behavior this case is meant to prove,
34
+ * derive the expected outcome from the acceptance's Then; do not invent a more convenient assertion from the current implementation.
35
+
36
+ Boundary:
37
+ * honor the planned harness and execution context when they are declared,
38
+ * enter through the narrowest boundary that still proves the declared behavior,
39
+ * do not move inward to an easier internal seam when that would stop exercising the thing the acceptance claims.
40
+
41
+ Stimulus:
42
+ * translate the acceptance's When into one primary action,
43
+ * create only the Given state required for that action.
44
+
45
+ Witness:
46
+ * assert the observable consequence named by the acceptance — for example an exact error, status/body, state change, emitted item, or other declared outcome,
47
+ * prefer a witness that would turn red if the behavior were removed or inverted.
48
+
49
+ Avoid:
50
+ * existence-only assertions such as `toBeDefined()` as the behavioral proof,
51
+ * mocking or substituting the subject or collaboration whose behavior the acceptance is meant to prove,
52
+ * asserting incidental implementation details instead of the declared outcome,
53
+ * computing the expected value with the production code under test,
54
+ * copying the production algorithm into the test,
55
+ * adding an unrelated behavioral assertion merely to clear this detector,
56
+ * broadening one case to prove several unrelated behaviors.
57
+
58
+ Verify:
59
+ * run the target case against the current implementation,
60
+ * confirm execution reaches the intended stimulus and behavioral oracle,
61
+ * confirm the behavioral expectation is red because the required behavior is absent or wrong — not because of syntax, imports, fixture setup, unavailable infrastructure, or an unrelated exception,
62
+ * ask the counterfactual: if the acceptance behavior disappeared, would this case definitely turn red?
63
+
64
+ expect(checkout).toBeDefined(); // passes against a stub
65
+ expect(() => checkout({ items: [] })).toThrow("CART_EMPTY"); // behavioral witness
32
66
  exceptions:
33
67
  - Only RED-phase files are scanned.
34
68
  - A case mixing existence and behavioural assertions is not flagged.
@@ -24,16 +24,33 @@ content:
24
24
  Flags any `Phase: RED` file containing none of the core TypeScript RED markers. `test.failing(` is
25
25
  bun:test's spelling of vitest's `it.fails(` and is accepted identically.
26
26
  normative_text: |
27
- Whether a test ACTUALLY fails first is a RUNTIME property no static detector can observe. Core supplies the decidable STRUCTURAL proxy — a guaranteed-fail marker must be present — and that proxy is exactly what a per-stack detector can check. The obligation is agnostic; only the marker syntax is stack-specific.
28
- This rule and `red-behavioral-assertion` are complementary, not redundant. That one asks whether the assertions are about behaviour; this one asks whether anything guarantees the test is red at all.
27
+ Whether a test ACTUALLY fails first is a RUNTIME property no static detector can observe. Core therefore supplies a decidable STRUCTURAL proxy — a guaranteed-fail marker must be present — and that proxy is exactly what this per-stack detector checks. The obligation is agnostic; only the marker syntax is stack-specific.
28
+ The marker is a detector aid, not the RED objective and not the behavioral oracle. A test whose only meaningful reason for failing is `expect.fail(...)`, an unconditional throw before the stimulus, or a failing wrapper around an otherwise passing case is mechanically red while the acceptance remains unproven. The case must also contain the behavioral witness required by `red-behavioral-assertion`.
29
+ The two rules are therefore complementary: `red-behavioral-assertion` asks whether the case encodes the missing behavior; this rule gives the static gate evidence that the artifact is intentionally in RED. The strongest local check is causal: apart from the structural marker, the behavioral witness itself should fail for the expected missing-behavior reason.
29
30
  fix_hint: |
30
- Add a marker that cannot pass until the implementation exists:
31
+ Establish the behavioral oracle first, then add the guaranteed-fail marker required by the static RED gate.
31
32
 
32
- expect.fail("checkout rejection not implemented yet");
33
- // or
34
- throw new Error("Not implemented: checkout rejection");
33
+ Behavioral proof:
34
+ * derive the case from the bound acceptance's Given / When / Then,
35
+ * include an assertion that would fail because the required behavior is not implemented correctly.
35
36
 
36
- Remove it in GREEN, when the behavioural assertion carries the test on its own.
37
+ Structural proxy:
38
+ * add one accepted RED marker so the static detector can recognize the phase:
39
+
40
+ expect.fail("checkout rejection not implemented yet");
41
+ // or
42
+ throw new Error("Not implemented: checkout rejection");
43
+
44
+ Avoid:
45
+ * using the marker as the only meaningful reason the test is red,
46
+ * throwing before the declared stimulus is exercised,
47
+ * wrapping a vacuous or already-passing case in `it.fails` / `test.failing`,
48
+ * weakening the behavioral assertion because the marker already guarantees failure.
49
+
50
+ Verify:
51
+ * run the target case and confirm it reaches the intended behavior,
52
+ * confirm the behavioral oracle would still fail for the expected reason without relying on the unconditional marker,
53
+ * remove the marker in GREEN only when the behavioral assertion itself carries the proof and passes.
37
54
  exceptions:
38
55
  - Only RED-phase files are scanned.
39
56
  - "`it.fails` / `test.failing` are markers, NOT self-skips — `no-self-skip` deliberately does not flag them."
@@ -21,13 +21,31 @@ content:
21
21
  Identifies security tests from the URN and flags those whose body contains no denial assertion.
22
22
  normative_text: |
23
23
  A security test that only walks the happy path is a SILENT GREEN GAP — the worst shape a test can have. It passes CI, the gate counts it as covering the security acceptance, and it proves nothing about the denial path, which is the only path the acceptance is actually about.
24
+ The denial assertion should be the witness for the specific security claim, not a generic refusal added to satisfy the detector. Construct the unauthorized or under-privileged stimulus described by the acceptance and assert the refusal it requires. When the acceptance protects a mutation or other side effect, also observe that the protected effect did not occur; returning 403 after performing the mutation is still a security failure.
24
25
  Identification is by URN kind segment rather than filename, consistent with the rest of this package: the header is authoritative.
25
26
  fix_hint: |
26
- Assert the refusal, not the permission:
27
+ Prove the refusal named by the security acceptance.
28
+
29
+ Stimulus:
30
+ * construct the missing, invalid, or insufficient authorization state the acceptance describes,
31
+ * exercise the real protected boundary.
32
+
33
+ Witness:
34
+ * assert the expected denial signal — for example 401, 403, a permission error, rejection, or throw,
35
+ * when the acceptance protects a side effect, also assert that the protected state or action remains unchanged.
27
36
 
28
37
  const res = await fetch("/orders", { method: "POST" }); // anonymous
29
38
  expect(res.status).toBe(403);
30
39
 
40
+ Avoid:
41
+ * using a happy-path permission case as the only security proof,
42
+ * adding an unrelated 403 assertion that does not come from the bound protected operation,
43
+ * mocking away the authorization decision the acceptance is meant to prove.
44
+
45
+ Verify:
46
+ * the case would turn red if authorization were accidentally removed,
47
+ * where applicable, a denied request cannot still perform the protected effect.
48
+
31
49
  A happy-path case is welcome too — but it cannot be the only one.
32
50
  exceptions:
33
51
  - Only tests whose URN carries a SEC / RLS / AUTH kind segment are scanned.
@@ -21,13 +21,31 @@ content:
21
21
  Identifies input-validation tests from the URN and flags those with no rejection assertion.
22
22
  normative_text: |
23
23
  A test that submits a well-formed payload and checks it succeeds proves the parser works on good input. The obligation an input-validation acceptance carries is about BAD input — malformed, missing, adversarial — and that is precisely what a happy-path test does not touch.
24
+ The rejection assertion should witness the specific invalid condition declared by the acceptance, not merely any error. Construct the malformed input named by the claim, drive it through the intended boundary, and assert the refusal. When accepting the bad input could create or mutate state, also verify that the rejected request left that protected effect absent.
24
25
  Same silent-green-gap shape as `security-auth`, and it is grouped with the security family for the same reason: unvalidated input is the entry point for most of what the security rules defend against.
25
26
  fix_hint: |
26
- Assert the rejection:
27
+ Prove rejection of the invalid input the acceptance actually names.
28
+
29
+ Stimulus:
30
+ * construct the malformed, missing, boundary, or adversarial value from the acceptance,
31
+ * send it through the same boundary production receives.
32
+
33
+ Witness:
34
+ * assert the acceptance-specific rejection — exact error, 400/422 response, rejection, or throw,
35
+ * when invalid input could mutate state, assert that the invalid operation produced no protected side effect.
27
36
 
28
37
  expect(() => parseOrder({ sku: null })).toThrow("INVALID_SKU");
29
38
  // or over HTTP
30
39
  expect(res.status).toBe(422);
40
+
41
+ Avoid:
42
+ * testing only a well-formed payload,
43
+ * triggering some unrelated error and treating any rejection as proof,
44
+ * bypassing the production validation boundary with a direct fixture assertion.
45
+
46
+ Verify:
47
+ * the case would turn red if validation for the declared bad input were removed,
48
+ * the observed failure is caused by the intended validation rule, not by test setup.
31
49
  exceptions:
32
50
  - Only tests whose URN carries an INPUT / VALIDATION / VALIDATE kind segment are scanned.
33
51
  - Any of the listed rejection signals satisfies the rule.
@@ -21,9 +21,29 @@ content:
21
21
  Flags any substitution call in a SMOKE-phase file, reporting the exact line.
22
22
  normative_text: |
23
23
  Substituting a collaborator turns a SMOKE test into a unit test wearing a SMOKE label. The damage is not that the test is bad — it may be an excellent unit test — but that the gate counts it as evidence the real path works, when the real path is precisely the part that was replaced.
24
+ In a SMOKE proof the boundary is part of the claim. The test must enter through the real assembled path and preserve the collaborators whose behavior constitutes that path. Replacing one because it is expensive, inconvenient, or difficult to arrange changes the question the test answers rather than making the same proof cheaper.
24
25
  This is the failure that makes a green suite compatible with a broken deployment, which is the specific outcome ATDD's phase separation exists to prevent.
25
26
  fix_hint: |
26
- Let the SMOKE test exercise the real collaborator against real infrastructure, and move the substituted version into a UNIT-phase test where the substitution is honest and expected.
27
+ Preserve the real proof boundary.
28
+
29
+ Boundary:
30
+ * exercise the production collaborator through the assembled path required by the SMOKE acceptance,
31
+ * use the real infrastructure or environment that the declared smoke proof is meant to verify.
32
+
33
+ Prefer:
34
+ * arrange only the minimum preconditions needed for the declared stimulus,
35
+ * observe the acceptance-specific external result after the real collaborator runs.
36
+
37
+ Avoid:
38
+ * replacing the collaborator whose behavior the SMOKE claim depends on,
39
+ * moving the test inward to a unit seam merely because that path is easier to control,
40
+ * relabeling the case as SMOKE after using mocks or spies that bypass production behavior.
41
+
42
+ Verify:
43
+ * the case would fail if the real collaborator or production wiring were broken,
44
+ * the observable assertion proves the bound acceptance rather than the test fixture.
45
+
46
+ If substitution is the intended proof technique, move that case into a UNIT-phase test where the substitution is honest and expected.
27
47
  exceptions:
28
48
  - Only SMOKE-phase files are scanned; UNIT tests may substitute freely.
29
49
  metadata:
@@ -21,17 +21,34 @@ content:
21
21
  summary: >-
22
22
  Reports a SMOKE case that makes assertions but touches none of the observable channels.
23
23
  normative_text: |
24
- A SMOKE test exists to prove the system does something a person could witness. A case that builds an object and asserts on its fields has proven that the constructor works — which is a unit test's job — while carrying the label that tells the gate real behaviour was exercised.
25
- The observable vocabulary is stack-specific and that is the whole reason this rule is re-realized rather than shared: for this stack the canonical observation is an HTTP response and the markup inside it, because that markup is what htmx puts in front of the user.
24
+ A SMOKE test exists to prove the system does something a person or operator could witness. A case that builds an object and asserts on its fields has proven that the constructor works — which is a unit test's job — while carrying the label that tells the gate real behaviour was exercised.
25
+ Merely touching an observable channel is not enough semantically. The witness must correspond to the bound acceptance's promised outcome. A generic `200` can prove that a server answered while leaving the actual acceptance — for example that the newly created order is visible — completely unproven.
26
+ The observable vocabulary is stack-specific and that is the whole reason this rule is re-realized rather than shared: for this stack the canonical observation is often an HTTP response and the markup inside it, because that markup is what htmx puts in front of the user. The detector recognizes the channel; the convention tells the agent to choose the acceptance-specific evidence on that channel.
26
27
  fix_hint: |
27
- Assert what an operator would see:
28
+ Assert what an operator would observe about the bound acceptance.
29
+
30
+ Claim:
31
+ * read the acceptance's Then and identify the externally visible consequence that discharges it.
32
+
33
+ Witness:
34
+ * assert on the real HTTP response, rendered markup, stdout/stderr, exit code, persisted state, or other observable channel,
35
+ * make the assertion specific enough that the acceptance behavior disappearing would turn the case red.
28
36
 
29
37
  const order = buildOrder({ sku: "x" });
30
- expect(order.sku).toBe("x"); // internal
38
+ expect(order.sku).toBe("x"); // internal; not SMOKE evidence
31
39
 
32
40
  const res = await fetch("/orders");
33
- expect(res.status).toBe(200);
34
- expect(await res.text()).toContain("order-1"); // observable
41
+ expect(res.status).toBe(200); // useful transport evidence
42
+ expect(await res.text()).toContain("order-1"); // acceptance-specific observable witness
43
+
44
+ Avoid:
45
+ * treating any observable assertion as sufficient when it does not prove the declared outcome,
46
+ * asserting only on objects the test constructed itself,
47
+ * checking internal calls or mocks instead of the resulting system behavior.
48
+
49
+ Verify:
50
+ * the real path was exercised,
51
+ * the witness would fail if the bound acceptance behavior regressed.
35
52
  exceptions:
36
53
  - Only SMOKE-phase files are scanned.
37
54
  - Cases with no assertions are skipped — other rules own that.
@@ -19,11 +19,17 @@ content:
19
19
  Requires a `Phase:` field from the closed vocabulary.
20
20
  normative_text: |
21
21
  The phase is not documentation — it is what selects which other rules apply. A RED test must fail first and must assert behaviour; a SMOKE test must assert something operator-observable and must not substitute collaborators. Without a declared phase none of those obligations can be evaluated, so an undeclared test is exempt from the entire quality surface by omission.
22
- That is why this rule exists separately from the URN rule: a test can be perfectly identified and still be ungoverned.
22
+ Phase selection is part of the proof contract, not a way to choose the easiest rule set. Declare the phase that matches what the test is actually doing and the proof boundary it exercises. In particular, do not relabel a real-path SMOKE or assembled E2E proof as UNIT merely to permit substitution, and do not label a passing or vacuous case RED merely because a RED marker is easy to add.
23
+ That is why this rule exists separately from the URN rule: a test can be perfectly identified and still be ungoverned or governed by the wrong obligations.
23
24
  fix_hint: |
24
- Declare the phase the test belongs to:
25
+ Derive the phase from the test's actual role; do not choose a weaker label to silence a phase-gated rule.
25
26
 
26
- // Phase: SMOKE
27
+ // Phase: RED // the current acceptance proof is intentionally failing first
28
+ // Phase: UNIT // isolated local behavior
29
+ // Phase: SMOKE // real assembled path / infrastructure with operator-observable evidence
30
+ // Phase: E2E // end-to-end journey through the assembled system
31
+
32
+ Verify that the declared phase matches the boundary the case really exercises and that the corresponding phase-specific conventions now apply.
27
33
  exceptions:
28
34
  - Files with no URN are skipped.
29
35
  - The vocabulary is fixed by core; a new phase is a core change, not an extension one.
@@ -12,9 +12,26 @@ terms:
12
12
  A unit test that merely mentions a journey URN in a string is not one.'
13
13
  content:
14
14
  summary: A journey spec binds to a train or journey plan/ declares.
15
- normative_text: 'A spec bound to an undeclared subject is orphaned: it can pass forever while the plan it claims
16
- to cover says nothing about it.'
17
- fix_hint: Bind the spec to the declared id, or declare the train or journey.
15
+ normative_text: |
16
+ The binding is how a browser test is traced to the plan it exercises. A spec with no binding, two bindings, or an id the planner would reject cannot be traced.
17
+ Resolution is necessary but not sufficient: the declared train or journey must also be the subject the spec actually walks. Binding an arbitrary declared subject merely to make the file resolvable creates false journey coverage. Conversely, do not declare a new train or journey solely to rescue an already-written spec; the plan should change only because the product behavior requires that subject.
18
+ fix_hint: |
19
+ Bind the spec to the declared train or journey it actually exercises.
20
+
21
+ Derive:
22
+ * identify the plan subject reached by the spec's entrypoint and interactions,
23
+ * use that exact declared id in the header.
24
+
25
+ Avoid:
26
+ * choosing any resolvable subject merely to clear this finding,
27
+ * changing the binding to match a convenient test that proves another journey,
28
+ * declaring a new train or journey solely so the spec has something to bind.
29
+
30
+ Verify:
31
+ * the browser path actually walks the bound subject,
32
+ * the final observable assertion proves that subject's user-facing behavior.
33
+
34
+ If the intended subject is genuinely absent from the plan, return to the authorized planning flow rather than inventing it inside the tester repair.
18
35
  exceptions:
19
36
  - Vendored and generated trees (`node_modules`, `dist`, `build`, `_generated`) are never scanned.
20
37
  - A repository with no plan (no trains, no journeys) is out of scope.
@@ -13,9 +13,30 @@ terms:
13
13
  A unit test that merely mentions a journey URN in a string is not one.'
14
14
  content:
15
15
  summary: Every declared train has a browser spec.
16
- normative_text: 'A train with no browser spec is a silent gap: the journey it declares can regress with nothing
17
- turning red.'
18
- fix_hint: 'Add `e2e/<train-slug>.e2e.ts` bound with `// Train: <train id>`.'
16
+ normative_text: |
17
+ A train with no browser spec is a silent gap: the journey it declares can regress with nothing turning red.
18
+ The `// Train:` binding is the detector's coverage handle, not the proof itself. A meaningful browser spec starts from the declared user-facing entrypoint, performs the interactions that make the train relevant, and asserts the user-observable business outcome the journey promises. A spec that only binds the train, navigates successfully, or mentions its identifier can satisfy structural coverage while leaving the journey behavior unproven.
19
+ fix_hint: |
20
+ Add `e2e/<train-slug>.e2e.ts` bound with `// Train: <train id>`, then make that spec prove the train rather than merely name it.
21
+
22
+ Claim:
23
+ * read the declared train/journey behavior and identify the user-observable outcome it is meant to deliver.
24
+
25
+ Stimulus:
26
+ * enter through the declared user-facing entrypoint,
27
+ * perform the real browser interactions that exercise the train.
28
+
29
+ Witness:
30
+ * assert the final user-visible business outcome, not only navigation, a 200 response, or an internal state token.
31
+
32
+ Avoid:
33
+ * adding the Train header solely for coverage credit,
34
+ * calling an internal handler instead of walking the assembled journey,
35
+ * seeding the final result directly,
36
+ * asserting only that the page loaded while the train behavior itself remains unobserved.
37
+
38
+ Verify:
39
+ * the spec would turn red if the train's user-facing behavior regressed.
19
40
  exceptions:
20
41
  - Vendored and generated trees (`node_modules`, `dist`, `build`, `_generated`) are never scanned.
21
42
  - The rule reports at the train's plan file, where the gap was declared.
package/integrity.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "0.10.41",
2
+ "version": "0.10.43",
3
3
  "files": {
4
4
  "HOOK_AUDIT.md": "5329d840db37671b1918f688ead26865473b87db73dbc75f7c8b2a8bbe8d6d43",
5
5
  "PLANNER_PORT.md": "fb5935bac8b7ac18994de21e43ace3a5ef8cd55f85b0e3349fca261280054f11",
@@ -122,21 +122,21 @@
122
122
  "conventions/planner.telemetry/planner.telemetry.logical-ownership.convention.yaml": "6dd666a22fd7fd0cb6e5fe5505665c8889a504ee929fdebf3aee019df4aac2b4",
123
123
  "conventions/planner.telemetry/planner.telemetry.metric-cardinality.convention.yaml": "e61831f207ad4f09be983b300cbedc40f6ad08a4b2483612fc195e2343be93f6",
124
124
  "conventions/planner.telemetry/planner.telemetry.tracking-plan-schema.convention.yaml": "5453cdf438dc5bdc72bbe5828b089e4090b865112f6a1550bb70e2c7c72a0cdf",
125
- "conventions/tester.bun/tester.bun.acceptance-binding-declared.convention.yaml": "46b9db88cdb978bc75b85b17d19a627233263da5b4fc6456a21fa870646ae1d0",
126
- "conventions/tester.bun/tester.bun.acceptance-covers-tag-well-formed.convention.yaml": "8dd55d2dc36d255ab553fba8cf6f5cd7de1ba286e687d290458d3b5681fbb40f",
127
- "conventions/tester.bun/tester.bun.acceptance-resolves-to-declared.convention.yaml": "6c5cc0719749dbcdd3eb02877799a40998e77b433b2f17d27ac1dbca44292610",
128
- "conventions/tester.bun/tester.bun.interlocking-production-runner-used.convention.yaml": "7cfa67f19ae6b88be40d2facdf2a83440bca3c2a8b3824c75eb25fda8438297c",
129
- "conventions/tester.bun/tester.bun.interlocking-route-coverage.convention.yaml": "7c21809613d74cd38368238f82a35853bf74ab34cfacc07354e62f83b8514881",
125
+ "conventions/tester.bun/tester.bun.acceptance-binding-declared.convention.yaml": "4bc5489f8cca53eb9145decb07e3a74565c3856274fce1495338a1f3cd89d0c3",
126
+ "conventions/tester.bun/tester.bun.acceptance-covers-tag-well-formed.convention.yaml": "a7095e17f003ab3e08202f49408f4de7c834cc917b0946d6bc68036afdcdc265",
127
+ "conventions/tester.bun/tester.bun.acceptance-resolves-to-declared.convention.yaml": "44b77a6ae4b4a92d5b51b5bb3444a4db280f74dc1ed25d158e20e79cd16a2bb3",
128
+ "conventions/tester.bun/tester.bun.interlocking-production-runner-used.convention.yaml": "c2fee925c16946314bdebf30fe2130cfc76f62bb64d714bdc37b7311e932f788",
129
+ "conventions/tester.bun/tester.bun.interlocking-route-coverage.convention.yaml": "e75b1285aa650cf2a5d94d9c98b6351df4a24e3942dcf01e2faa9ed6676f1ca6",
130
130
  "conventions/tester.bun/tester.bun.interlocking-smoke-coverage-for-station-master.convention.yaml": "ae2e53865fe3f53982d5d1d9fca73dd116ee932e00c4c4f1184235415b1dd794",
131
131
  "conventions/tester.bun/tester.bun.interlocking-trace-binds-declared-route.convention.yaml": "8aea28e250368ed800a97d5e7e2f293c2935b198eb87be1422eef029223d71a4",
132
132
  "conventions/tester.bun/tester.bun.interlocking-train-sequence-is-exercised.convention.yaml": "3172abf9256efd8f831ed5f8f11872e17cf847114769bba0c42270507254e241",
133
- "conventions/tester.bun/tester.bun.red-behavioral-assertion.convention.yaml": "5ce6b2460d8796b0b921600aa54129e192de4c4dd8229c8e574ae1cf55388b60",
134
- "conventions/tester.bun/tester.bun.red-fails-first.convention.yaml": "0506c7fae92a85161df8e190e5c01c595d66c376d3ad5f16f1faf38d9503ac7c",
133
+ "conventions/tester.bun/tester.bun.red-behavioral-assertion.convention.yaml": "b604c0836eeff46df9a9e53195696c17f7997c6b2732c6c759b5207162a33d97",
134
+ "conventions/tester.bun/tester.bun.red-fails-first.convention.yaml": "232c42033cea8f935a84cd86769cfd273bf6e4772e2d2ff3ed66117a3c8e689d",
135
135
  "conventions/tester.bun/tester.bun.routing-runtime-family.convention.yaml": "6deeb37235ebe500c491aea9e06695c08df718a371114b1a6394bd1988fb66b1",
136
- "conventions/tester.bun/tester.bun.security-auth.convention.yaml": "eb192aa29ce0f2976a92c20d32fc425b5685b15d9bb384d52101646b08cb9c2e",
137
- "conventions/tester.bun/tester.bun.security-input.convention.yaml": "3118299e1e6a71b21be730f26377baab922b1c3d8b175b9b35f10d38d9ebaeb0",
138
- "conventions/tester.bun/tester.bun.smoke-no-collaborator-substitution.convention.yaml": "76187d91e6298ae9b89a39c2ebf9cb6f769e1d3ce08d7a9ea21727aadc6a019a",
139
- "conventions/tester.bun/tester.bun.smoke-observable-assertion.convention.yaml": "1d35f17221b7de3f9ad6bb3c1ced486bec401e1d024e2476147f4f9e52f05674",
136
+ "conventions/tester.bun/tester.bun.security-auth.convention.yaml": "b8224269911ae477756e6c160e1ed72d1438574277109f77704326251e720449",
137
+ "conventions/tester.bun/tester.bun.security-input.convention.yaml": "6f089d2641f264e59ab38973f848644bb48d6e254613dba62c2616f1edb82a68",
138
+ "conventions/tester.bun/tester.bun.smoke-no-collaborator-substitution.convention.yaml": "6ae2bdfdf3c842f855c02b9c776feaa0ac11efa1365a0b8f0b9c8343afd11102",
139
+ "conventions/tester.bun/tester.bun.smoke-observable-assertion.convention.yaml": "ba7c3981fd9b8860abb88eaeefde0a87bc696309a2d31e2bc9c637832d0fe1cb",
140
140
  "conventions/tester.bun/tester.bun.telemetry-captured-sink.convention.yaml": "7f1156dc041fc44d4becd18f379cd1fb68b99cd1e888b9a8f6dee4201ad5337b",
141
141
  "conventions/tester.bun/tester.bun.telemetry-emit.convention.yaml": "440ba54de6c23e25786d363d80a9c337f29aa2eaf59bc8b5c24d391cc1c3d3b7",
142
142
  "conventions/tester.bun/tester.bun.telemetry-identity-assertion.convention.yaml": "25f7cc3dcb21e70a183638be9416802ef081323482a98eba604223fabf389b55",
@@ -147,10 +147,10 @@
147
147
  "conventions/tester.bun/tester.bun.test-imports-bun-test.convention.yaml": "6eef1c745315a69357d23bf07d318e504d425f76fcfdc120166b8ef2d4c820d6",
148
148
  "conventions/tester.bun/tester.bun.test-isolation-no-live-state.convention.yaml": "3488af16431ea461e9128d15dbb0ad86324f4446bf36d5a662fab73687e8860e",
149
149
  "conventions/tester.bun/tester.bun.test-no-self-skip.convention.yaml": "23fca575611f9c0740bcf64849a3c1600ef4cc1cc64df3c106d328b67283d8a3",
150
- "conventions/tester.bun/tester.bun.test-phase-declared.convention.yaml": "cd22101bfcbc2eeca3da1aa2907ba6c944674c31cfaac5305f2149c6c429cf87",
150
+ "conventions/tester.bun/tester.bun.test-phase-declared.convention.yaml": "f6daa660417bdc07fe87765ac17bc5606cd2d8a5c8dbe456f9e4327ddd77fe73",
151
151
  "conventions/tester.htmx/tester.htmx.a11y-harness.convention.yaml": "e74328600b89761a8740ace465e3a5d5e38f967dcbeda7fe7023af53dd0089d4",
152
152
  "conventions/tester.htmx/tester.htmx.covered-train-is-routed.convention.yaml": "e70d05055733644addd63db11c6cb39cf38a34adc4cc39f7c7ee9964eb2391a4",
153
- "conventions/tester.htmx/tester.htmx.e2e-binds-declared-subject.convention.yaml": "48bb5a45380d80161c09e872a769020b3ea10a05a4c34a71a1a585f89a434583",
153
+ "conventions/tester.htmx/tester.htmx.e2e-binds-declared-subject.convention.yaml": "fa2b9079bc15ffab47d57c80cb804fb4c1b19fc377434226ef9e46e586890cbc",
154
154
  "conventions/tester.htmx/tester.htmx.e2e-spec-naming.convention.yaml": "4bd46400706bbe9b453784ca2811ef04ffb25871a762791e653da3d9a0fa07f2",
155
155
  "conventions/tester.htmx/tester.htmx.exposed-journey-e2e-coverage.convention.yaml": "eca949b38da805c9654d483fce4e06fe19292bd72b5b286af982f13c5782e1d4",
156
156
  "conventions/tester.htmx/tester.htmx.fragment-asserts-returned-markup.convention.yaml": "b8de8e893b582d8ec4b7c24acfa16da0eccd14e6e58e92baf9e9c10c9749ea0c",
@@ -162,7 +162,7 @@
162
162
  "conventions/tester.htmx/tester.htmx.responsive-harness.convention.yaml": "cec312935ab592c2c7a5c6a94623a02df771a9e2ca2035b721ef1006c4800fdf",
163
163
  "conventions/tester.htmx/tester.htmx.responsive-journey-coverage.convention.yaml": "2004d0a8975178475d792702e164f335b07407f36e83a09ceadef3fe49037111",
164
164
  "conventions/tester.htmx/tester.htmx.swap-oob-asserts-destination-id.convention.yaml": "65d8ffa62732ec0578d4999234b89f692e9c5e234ecd30e620bee7c44f053caa",
165
- "conventions/tester.htmx/tester.htmx.train-e2e-coverage.convention.yaml": "99096eaab1b6891eb66306d904624c7c28ab9bd2c0f7f9f68a515e3be45961f5",
165
+ "conventions/tester.htmx/tester.htmx.train-e2e-coverage.convention.yaml": "8d36a1e06985262a5d431678f9baef67b5625825f4cc843ac3d3cbff13022373",
166
166
  "conventions/tester.htmx/tester.htmx.verb-endpoint-coverage.convention.yaml": "41b6ff2a519040a5eb9834bcdee88ad1d470a69430bc9a39be667dc321dcd14c",
167
167
  "conventions/tester.htmx/tester.htmx.visual-harness.convention.yaml": "a2419b784202fc807558d0182e26d3d8c777aa9ec4ef4c252467a979dd903d50",
168
168
  "conventions/traceability/traceability.plan.executable-acceptance-has-test.convention.yaml": "52eca528c9dbdb82f3c1a13e4e1a3d5d21a9dd511b66e6f8c4135298412427f5",
@@ -1196,7 +1196,7 @@
1196
1196
  "src/ci.ts": "67e54d2cbfc44a9e42837d5e750af526d6255c539d3c452a4d5fca3226c0d9ac",
1197
1197
  "src/cli.ts": "cbc50f1e7fc7d0935f78230b7da6e232ed7ee6f04828e015c599fb6f6edcbfa2",
1198
1198
  "src/docs-capability.ts": "bdeeb414649049b1cd4fa8008cada3a576318be51c43bec91bfa46a8e250c270",
1199
- "src/enforce.ts": "2f624f2aa272466f44c552d77ce3976cb2a518491d3ec3c47815583fdd0f7ad8",
1199
+ "src/enforce.ts": "33e2b17ad1f8c46af439dd433918d4c0724b9bffa077305a32b800494fa0886a",
1200
1200
  "src/hooks.ts": "0b4ca8c857e637bbd144e737e78eda924e3cba7b038c0abe5e6916d200d73068",
1201
1201
  "src/index.ts": "4081f8b08d1d3ee9e15a9964143e53d9cfb94a5616ab3e58a180d24cc16dc381",
1202
1202
  "src/integrity.ts": "556976a42bae298df14b73675102d83b6bf4df0674d8e7211aad5315548f005f",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-bun",
3
- "version": "0.10.41",
3
+ "version": "0.10.43",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/afokapu/atdd-bun.git"
package/src/enforce.ts CHANGED
@@ -44,7 +44,7 @@ export const profileImplementations: Record<ConcreteProfile, string[]> = {
44
44
 
45
45
  /** Additional policy conventions a profile loads, whether local or contributed by an installed companion package. */
46
46
  export const profileConventions: Record<ConcreteProfile, Array<{ rule_id: string; path: string }>> = {
47
- traceability: [], topology: [], docs: [], planner: [], telemetry: [], coder: [{ rule_id: ["coder.bun", "phase-discipline"].join("."), path: "conventions/coder.bun/coder.bun.phase-discipline.convention.yaml" }], tester: [], security: [],
47
+ traceability: [], topology: [], docs: [], planner: [], telemetry: [], coder: [{ rule_id: "coder.bun.phase-discipline", path: "conventions/coder.bun/coder.bun.phase-discipline.convention.yaml" }], tester: [], security: [],
48
48
  architecture: [], metrics: [], runtime: [], interlocking: [], htmx: [], design: [],
49
49
  workflow: [{ rule_id: "atdd-workflow.workflow.lifecycle", path: "../atdd-workflow/conventions/atdd-workflow.workflow/atdd-workflow.workflow.lifecycle.convention.yaml" }],
50
50
  };