@codyswann/lisa 3.1.0 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +28 -12
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/expo/copy-overwrite/scripts/bdd/baseline.mjs +211 -121
  5. package/expo/copy-overwrite/scripts/bdd/contract.mjs +10 -2
  6. package/expo/copy-overwrite/scripts/bdd/envelope.mjs +3 -2
  7. package/expo/copy-overwrite/scripts/bdd/render.mjs +2 -2
  8. package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +45 -8
  9. package/expo/copy-overwrite/scripts/classify-maestro-failures.mjs +775 -0
  10. package/expo/create-only/.maestro/flake-classification.json +21 -0
  11. package/expo/create-only/bdd/coverage-map.json +1 -2
  12. package/expo/package-lisa/package.lisa.json +1 -0
  13. package/package.json +1 -1
  14. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  15. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  16. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +24 -0
  17. package/plugins/lisa/rules/eager/credential-substrate-precedence.md +52 -0
  18. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +19 -8
  19. package/plugins/lisa-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  22. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  23. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  24. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  26. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +24 -0
  27. package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +52 -0
  28. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +19 -8
  29. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  30. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +24 -0
  31. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +19 -8
  32. package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +57 -0
  33. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  35. package/plugins/lisa-expo-agy/plugin.json +1 -1
  36. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  40. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  41. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  45. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  46. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  51. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  55. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  56. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  60. package/plugins/lisa-rails-agy/plugin.json +1 -1
  61. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  65. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  66. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  67. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  68. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  70. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  71. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  72. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  73. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +24 -0
  74. package/plugins/src/base/rules/eager/credential-substrate-precedence.md +52 -0
  75. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +19 -8
  76. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
  77. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Harper/Fabric-specific Lisa rules for TypeScript component apps.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-harper-fabric",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Harper/Fabric-specific rules for TypeScript component apps",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "NestJS-specific skills and migration write-protection hooks.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-nestjs",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "NestJS-specific skills (GraphQL, TypeORM) and hooks (migration write-protection)",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-openclaw",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-phaser",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Phaser 4 game-development rules for TypeScript projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Ruby on Rails-specific skills and hooks for RuboCop and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-rails",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Ruby on Rails-specific hooks — RuboCop linting/formatting and ast-grep scanning on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript-specific hooks for formatting, linting, and ast-grep scanning on edit.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-typescript",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "TypeScript-specific hooks — Prettier formatting, ESLint linting, ast-grep scanning, and error-suppression blocking on edit",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "Distributable LLM Wiki kernel — ingest, query, lint, and maintain a git-native markdown knowledge base across Claude and Codex.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-wiki",
3
- "version": "3.1.0",
3
+ "version": "3.2.0",
4
4
  "description": "LLM Wiki — a distributable, git-native markdown knowledge base for Claude Code and Codex",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -10,6 +10,30 @@
10
10
  * File families the ratchet watches. `kind` selects the extractor;
11
11
  * `direction` applies to numeric-leaf kinds ("min" values may only rise,
12
12
  * "max" values may only fall).
13
+ *
14
+ * PER-FAMILY POLICY, reviewed 2026-08-12. A family keeps its ratchet unless a
15
+ * NAMED deterministic non-regression invariant replaces the property the
16
+ * ratchet was providing — deleting a ratchet deletes that property with it, so
17
+ * "this generates churn" is a reason to find a replacement, never a reason to
18
+ * delete on its own:
19
+ *
20
+ * coverage / simplecov / e2e KEPT. Here the ratchet IS the non-regression
21
+ * invariant, and no per-item equivalent exists short of committing a
22
+ * per-file coverage baseline — an artifact that only ever accumulates.
23
+ * Revisit if one appears.
24
+ * stryker KEPT. `thresholds.break` is an absolute floor,
25
+ * kept outright; the mutate-list comparison detects EXEMPTION ADDITIONS
26
+ * rather than creep, and has no number for a pull request to nudge.
27
+ * eslint / rubocop / k6 / lisa-config KEPT, unchanged.
28
+ *
29
+ * Exactly one family was replaced, and it is not watched here and never was:
30
+ * the BDD traceability floor, whose numeric ratchet gave way to per-obligation
31
+ * checks. See `expo/copy-overwrite/scripts/bdd/baseline.mjs`.
32
+ *
33
+ * Retiring a family is SEQUENCED: the replacement invariant lands, then a
34
+ * `thresholdRatchet.allow` entry merges from the base side, then the mechanism
35
+ * changes. Never the reverse — this checker is precisely what stops a change
36
+ * granting itself the exception that permits it.
13
37
  */
14
38
  export const FAMILIES = [
15
39
  {
@@ -0,0 +1,52 @@
1
+ # Credential-Substrate Precedence (load-bearing)
2
+
3
+ **When more than one substrate can reach an external system, the configured credentials
4
+ provider's token/CLI path goes first and the interactive MCP is the fallback — and
5
+ identity-match verification is mandatory on every substrate, at every tier.**
6
+
7
+ **One shared, vendor-neutral contract cited by every `*-access` skill** (the
8
+ `leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug, never divergent
9
+ per-skill prose). An access skill states its per-vendor mechanics — which token, which
10
+ CLI, which identity anchor — and cites this rule for the ordering. It never restates,
11
+ narrows, or locally overrides that ordering.
12
+
13
+ Settled by decision record `2026-08-12-credential-substrate-precedence` (D6), and
14
+ settled in the `settled-decisions` sense: re-arguing MCP-first inside a skill is out of
15
+ scope for that skill's work.
16
+
17
+ ## The ladder
18
+
19
+ 1. **Tier 1 — configured-provider substrate.** The token or CLI path fed by
20
+ `lisa-secrets-access`, chosen whenever its bootstrap credential is available **and**
21
+ the resolved substrate identity-matches the configured tenant/workspace/site.
22
+ `lisa-secrets-access` is the single chokepoint — never read an OS keychain a second
23
+ time.
24
+ 2. **Tier 2 — interactive MCP**, used only when tier 1 is *genuinely* unavailable:
25
+ no bootstrap, no adapter for the operation (per-operation, not per-session), or a
26
+ provider outage. "The MCP happens to be authenticated" and "tier 1 is slower" are
27
+ **not** qualifying reasons.
28
+ 3. **Tier 3 — loud, actionable failure** naming the exact credential to set and the exact
29
+ remediation. Never silently no-op, never blind-retry a failed or absent substrate,
30
+ never fall through to one that failed identity-match.
31
+
32
+ ## Identity-match is mandatory on every substrate
33
+
34
+ Verified **in both directions** before any operation: the substrate must claim the
35
+ configured tenant, and the configured tenant must be one the substrate can reach. A
36
+ substrate authenticated as a different account is **skipped, never used — including at
37
+ tier 1**. A credential is not an identity claim; the identity claim is what the provider
38
+ says when asked. Skipping the check because "the user obviously meant this workspace" is
39
+ forbidden.
40
+
41
+ ## Mutating operations: fallback is guarded, never routine
42
+
43
+ Falling back to an ambient-bound substrate for a **write** requires: switch profile and
44
+ assert identity immediately before the write → write → re-read the affected objects →
45
+ assert the tenant **from the response** (self URL host, cloudId in the path, response
46
+ metadata), not from the pre-flight check → on mismatch, stop, report a cross-tenant
47
+ hazard, and best-effort roll back. A successful pre-flight switch is not sufficient:
48
+ another process can mutate global state between the check and the write.
49
+
50
+ Full contract (per-vendor identity anchors and probes, the provider-first rationale, MCP's
51
+ first-class fallback role, consequences, and the checklist for adding or editing an access
52
+ skill): [reference/credential-substrate-precedence.md](../reference/credential-substrate-precedence.md).
@@ -48,7 +48,7 @@ platforms it requires and that each named platform has a configured runner.
48
48
  "schemaVersion": 1,
49
49
  "asOf": "<ISO date>",
50
50
  "runnerPlatforms": { "<runner>": ["<platform>", "..."] },
51
- "coverageFloor": { "<platform>": "<0-100, the committed floormay rise, never falls>" },
51
+ "coverageFloor": { "<platform>": "<0-100, an absolute bar checked in enforced mode not a ratchet, set once and left alone>" },
52
52
  "platformWaivers": [
53
53
  {
54
54
  "scenario": "BDD-DOMAIN-NNN",
@@ -123,7 +123,9 @@ Two commands, wired into the project's script surface and into CI:
123
123
  existing mapping);
124
124
  - a discovered test named by no mapping and no exclusion, or an exclusion that no longer excuses
125
125
  anything;
126
- - a regression against the project's committed `coverageFloor` per platform.
126
+ - a platform sitting below its committed `coverageFloor`;
127
+ - coverage given back: an obligation mapped at the base revision that nothing maps here;
128
+ - new behavior nobody mapped or waived.
127
129
 
128
130
  Regeneration is never blocked by the check: `--write` rewrites the report and burndown whenever a
129
131
  report can be built at all, so a stale evidence string can never hold hostage the paperwork that
@@ -134,12 +136,20 @@ that currently fails is a red CI check, a separate signal; the map only asserts
134
136
  exists and still says what it claimed. Both facts are required — a green gate over a red suite is
135
137
  not coverage.
136
138
 
137
- ### Coverage floor, not coverage target
139
+ ### Coverage floor, not coverage target — and not a ratchet
138
140
 
139
- Projects adopting this contract mid-life start below 100% and must not be blocked by that. The gate
140
- enforces a **ratchet**: the committed floor per platform may rise and may never fall. Recorded in
141
- `coverageFloor`, it is bumped by regenerating the matrix after a frontend work item raises it by the
142
- obligations it seals; it is never asked to clear the whole backlog.
141
+ Projects adopting this contract mid-life start below 100% and must not be blocked by that. The
142
+ committed `coverageFloor` per platform is an **absolute bar** answering "is this platform below it
143
+ right now". Set it once at adoption to the honest measured number (or `0`) and leave it: nothing
144
+ forces it upward, and lowering it needs no ceremony.
145
+
146
+ What stops coverage sliding backwards is checked directly, per obligation, against the base
147
+ revision — **an obligation that was mapped may not stop being mapped, and new behavior arrives
148
+ mapped or waived**. Giving coverage back is legitimate but takes two artifacts one author cannot
149
+ produce alone: a recorded route (a `retirements` record or a `platformWaivers` entry) plus the
150
+ maintainer-applied `bdd-floor-baseline` label. Gaps that predate the change are burndown, never a
151
+ gate failure — which is what lets a brownfield project adopt `enforced` without first backfilling
152
+ its whole history.
143
153
 
144
154
  ## Waivers versus `@blocked`
145
155
 
@@ -198,7 +208,8 @@ A repo with no contract yet, taking its first frontend work item:
198
208
  during this step, never left undisclosed.
199
209
  3. **Write only this item's scenarios.** The first item is not a backfill project. Pre-existing
200
210
  uncovered behavior becomes burndown in `docs/e2e-bdd-coverage.md`, and the floor starts where the
201
- repo actually is.
211
+ repo actually is. That is a one-time act, not a recurring one: from here on the floor stays put
212
+ and what protects each new obligation is the per-obligation check, not the number.
202
213
  4. **Seal this item's obligations** and commit the regenerated matrix and burndown with the change.
203
214
 
204
215
  If a required platform has **no** e2e runner at all, that obligation is never left as a bare `N/A` —
@@ -10,6 +10,30 @@
10
10
  * File families the ratchet watches. `kind` selects the extractor;
11
11
  * `direction` applies to numeric-leaf kinds ("min" values may only rise,
12
12
  * "max" values may only fall).
13
+ *
14
+ * PER-FAMILY POLICY, reviewed 2026-08-12. A family keeps its ratchet unless a
15
+ * NAMED deterministic non-regression invariant replaces the property the
16
+ * ratchet was providing — deleting a ratchet deletes that property with it, so
17
+ * "this generates churn" is a reason to find a replacement, never a reason to
18
+ * delete on its own:
19
+ *
20
+ * coverage / simplecov / e2e KEPT. Here the ratchet IS the non-regression
21
+ * invariant, and no per-item equivalent exists short of committing a
22
+ * per-file coverage baseline — an artifact that only ever accumulates.
23
+ * Revisit if one appears.
24
+ * stryker KEPT. `thresholds.break` is an absolute floor,
25
+ * kept outright; the mutate-list comparison detects EXEMPTION ADDITIONS
26
+ * rather than creep, and has no number for a pull request to nudge.
27
+ * eslint / rubocop / k6 / lisa-config KEPT, unchanged.
28
+ *
29
+ * Exactly one family was replaced, and it is not watched here and never was:
30
+ * the BDD traceability floor, whose numeric ratchet gave way to per-obligation
31
+ * checks. See `expo/copy-overwrite/scripts/bdd/baseline.mjs`.
32
+ *
33
+ * Retiring a family is SEQUENCED: the replacement invariant lands, then a
34
+ * `thresholdRatchet.allow` entry merges from the base side, then the mechanism
35
+ * changes. Never the reverse — this checker is precisely what stops a change
36
+ * granting itself the exception that permits it.
13
37
  */
14
38
  export const FAMILIES = [
15
39
  {
@@ -10,6 +10,30 @@
10
10
  * File families the ratchet watches. `kind` selects the extractor;
11
11
  * `direction` applies to numeric-leaf kinds ("min" values may only rise,
12
12
  * "max" values may only fall).
13
+ *
14
+ * PER-FAMILY POLICY, reviewed 2026-08-12. A family keeps its ratchet unless a
15
+ * NAMED deterministic non-regression invariant replaces the property the
16
+ * ratchet was providing — deleting a ratchet deletes that property with it, so
17
+ * "this generates churn" is a reason to find a replacement, never a reason to
18
+ * delete on its own:
19
+ *
20
+ * coverage / simplecov / e2e KEPT. Here the ratchet IS the non-regression
21
+ * invariant, and no per-item equivalent exists short of committing a
22
+ * per-file coverage baseline — an artifact that only ever accumulates.
23
+ * Revisit if one appears.
24
+ * stryker KEPT. `thresholds.break` is an absolute floor,
25
+ * kept outright; the mutate-list comparison detects EXEMPTION ADDITIONS
26
+ * rather than creep, and has no number for a pull request to nudge.
27
+ * eslint / rubocop / k6 / lisa-config KEPT, unchanged.
28
+ *
29
+ * Exactly one family was replaced, and it is not watched here and never was:
30
+ * the BDD traceability floor, whose numeric ratchet gave way to per-obligation
31
+ * checks. See `expo/copy-overwrite/scripts/bdd/baseline.mjs`.
32
+ *
33
+ * Retiring a family is SEQUENCED: the replacement invariant lands, then a
34
+ * `thresholdRatchet.allow` entry merges from the base side, then the mechanism
35
+ * changes. Never the reverse — this checker is precisely what stops a change
36
+ * granting itself the exception that permits it.
13
37
  */
14
38
  export const FAMILIES = [
15
39
  {