@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.
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +28 -12
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/expo/copy-overwrite/scripts/bdd/baseline.mjs +211 -121
- package/expo/copy-overwrite/scripts/bdd/contract.mjs +10 -2
- package/expo/copy-overwrite/scripts/bdd/envelope.mjs +3 -2
- package/expo/copy-overwrite/scripts/bdd/render.mjs +2 -2
- package/expo/copy-overwrite/scripts/check-bdd-coverage.mjs +45 -8
- package/expo/copy-overwrite/scripts/classify-maestro-failures.mjs +775 -0
- package/expo/create-only/.maestro/flake-classification.json +21 -0
- package/expo/create-only/bdd/coverage-map.json +1 -2
- package/expo/package-lisa/package.lisa.json +1 -0
- package/package.json +1 -1
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +19 -8
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +19 -8
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +19 -8
- package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +57 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/hooks/threshold-ratchet-families.mjs +24 -0
- package/plugins/src/base/rules/eager/credential-substrate-precedence.md +52 -0
- package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +19 -8
- package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
- package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_comment": "Configuration for `bun run maestro:classify` (scripts/classify-maestro-failures.mjs) — a DIAGNOSTIC that scores a red Maestro run before anybody attributes a regression to it. It gates nothing and cannot change any check's conclusion. This file is create-only: Lisa writes it once and never overwrites your tuned values.",
|
|
3
|
+
|
|
4
|
+
"_signInMarkers": "Selectors that identify a subflow as performing sign-in. Preamble identity is DERIVED from these rather than from a list of filenames, so the classifier cannot drift when a gate is edited. Replace the shipped values with the selectors YOUR sign-in flow uses. Leaving them unmatched is safe: nothing qualifies as a preamble and every failure reads as a product failure, which is the direction this tool is required to err in — a false 'product' costs an investigation, a false 'preamble' hides a real regression.",
|
|
5
|
+
"signInMarkers": ["landing:sign-in", "signin:email-input"],
|
|
6
|
+
|
|
7
|
+
"_knownIntermittent": "Flows that fail SOMETIMES on an unchanged build. Every entry MUST carry a `measured` block — `failures`/`runs` from a real repeated run, the ISO date it was measured, and the methodology behind it. An entry missing any of those is reported as a registry defect and annotates nothing, on purpose: an unmeasured 'known flake' entry is exactly how a real regression gets dismissed. Keep the narrative — what was ruled out, the local repro recipe — in your BDD README and point at it from `notes`; this file holds only the claim a machine can check. Remeasure and update `measuredAt` rather than letting a stale rate justify today's failure.",
|
|
8
|
+
"_exampleEntry": {
|
|
9
|
+
"flow": "saved-insight-save-and-unsave.yaml",
|
|
10
|
+
"platforms": ["android"],
|
|
11
|
+
"measured": {
|
|
12
|
+
"failures": 2,
|
|
13
|
+
"runs": 7,
|
|
14
|
+
"measuredAt": "2026-08-10",
|
|
15
|
+
"method": "seven local runs on one emulator (1080x2400 @ 420dpi, matching the CI AVD pins) against build 0.0.327, arm-alternated against 0.0.319 to rule out the suspected commit"
|
|
16
|
+
},
|
|
17
|
+
"ticket": "TUN-560",
|
|
18
|
+
"notes": "bdd/README.md#known-intermittent-flows"
|
|
19
|
+
},
|
|
20
|
+
"knownIntermittent": []
|
|
21
|
+
}
|
|
@@ -29,12 +29,11 @@
|
|
|
29
29
|
}
|
|
30
30
|
},
|
|
31
31
|
"coverageFloor": {
|
|
32
|
-
"_comment": "Committed traceability floor per platform
|
|
32
|
+
"_comment": "Committed traceability floor per platform: an ABSOLUTE BAR answering `is this platform below it right now`, evaluated only in enforced mode. It is NOT a ratchet — set it once at adoption to the honest measured number (or leave it at 0) and stop touching it. Coverage already earned is protected separately and deterministically: an obligation covered at the base revision may not stop being covered, and new behavior arrives mapped or waived. Set a floor for EVERY platform above before flipping adoption.state to enforced.",
|
|
33
33
|
"web": 0,
|
|
34
34
|
"ios": 0,
|
|
35
35
|
"android": 0
|
|
36
36
|
},
|
|
37
|
-
"coverageFloorBaseline": [],
|
|
38
37
|
"retirements": [],
|
|
39
38
|
"trackers": {
|
|
40
39
|
"_comment": "Tracker-tag grammar is portfolio-wide; the allowed keys and repos are per-repo. Key style: @TUN-123, @SE-6833. Repo-issue style: @gh-2394 (this repo) or @gh-wiki-124 (a sibling repo). Tags are validated for syntax and membership only — the gate never contacts a tracker, so an unreachable issue can never block a merge.",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"maestro:test:android": "maestro test -p android .maestro/flows",
|
|
12
12
|
"maestro:test:smoke": "maestro test -p ios --include-tags=smoke .maestro/flows",
|
|
13
13
|
"maestro:studio": "maestro studio",
|
|
14
|
+
"maestro:classify": "node scripts/classify-maestro-failures.mjs",
|
|
14
15
|
"bdd:coverage": "node scripts/check-bdd-coverage.mjs",
|
|
15
16
|
"bdd:coverage:write": "node scripts/check-bdd-coverage.mjs --write",
|
|
16
17
|
"bdd:matrix": "node scripts/bdd-matrix.mjs --write",
|
package/package.json
CHANGED
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"name": "@codyswann/lisa",
|
|
123
|
-
"version": "3.
|
|
123
|
+
"version": "3.2.0",
|
|
124
124
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
125
125
|
"main": "dist/index.js",
|
|
126
126
|
"exports": {
|
|
@@ -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,
|
|
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
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
{
|
|
@@ -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,
|
|
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
|
|
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
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
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
|
{
|
|
@@ -53,7 +53,7 @@ platforms it requires and that each named platform has a configured runner.
|
|
|
53
53
|
"schemaVersion": 1,
|
|
54
54
|
"asOf": "<ISO date>",
|
|
55
55
|
"runnerPlatforms": { "<runner>": ["<platform>", "..."] },
|
|
56
|
-
"coverageFloor": { "<platform>": "<0-100,
|
|
56
|
+
"coverageFloor": { "<platform>": "<0-100, an absolute bar checked in enforced mode — not a ratchet, set once and left alone>" },
|
|
57
57
|
"platformWaivers": [
|
|
58
58
|
{
|
|
59
59
|
"scenario": "BDD-DOMAIN-NNN",
|
|
@@ -128,7 +128,9 @@ Two commands, wired into the project's script surface and into CI:
|
|
|
128
128
|
existing mapping);
|
|
129
129
|
- a discovered test named by no mapping and no exclusion, or an exclusion that no longer excuses
|
|
130
130
|
anything;
|
|
131
|
-
- a
|
|
131
|
+
- a platform sitting below its committed `coverageFloor`;
|
|
132
|
+
- coverage given back: an obligation mapped at the base revision that nothing maps here;
|
|
133
|
+
- new behavior nobody mapped or waived.
|
|
132
134
|
|
|
133
135
|
Regeneration is never blocked by the check: `--write` rewrites the report and burndown whenever a
|
|
134
136
|
report can be built at all, so a stale evidence string can never hold hostage the paperwork that
|
|
@@ -139,12 +141,20 @@ that currently fails is a red CI check, a separate signal; the map only asserts
|
|
|
139
141
|
exists and still says what it claimed. Both facts are required — a green gate over a red suite is
|
|
140
142
|
not coverage.
|
|
141
143
|
|
|
142
|
-
### Coverage floor, not coverage target
|
|
144
|
+
### Coverage floor, not coverage target — and not a ratchet
|
|
143
145
|
|
|
144
|
-
Projects adopting this contract mid-life start below 100% and must not be blocked by that. The
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
146
|
+
Projects adopting this contract mid-life start below 100% and must not be blocked by that. The
|
|
147
|
+
committed `coverageFloor` per platform is an **absolute bar** answering "is this platform below it
|
|
148
|
+
right now". Set it once at adoption to the honest measured number (or `0`) and leave it: nothing
|
|
149
|
+
forces it upward, and lowering it needs no ceremony.
|
|
150
|
+
|
|
151
|
+
What stops coverage sliding backwards is checked directly, per obligation, against the base
|
|
152
|
+
revision — **an obligation that was mapped may not stop being mapped, and new behavior arrives
|
|
153
|
+
mapped or waived**. Giving coverage back is legitimate but takes two artifacts one author cannot
|
|
154
|
+
produce alone: a recorded route (a `retirements` record or a `platformWaivers` entry) plus the
|
|
155
|
+
maintainer-applied `bdd-floor-baseline` label. Gaps that predate the change are burndown, never a
|
|
156
|
+
gate failure — which is what lets a brownfield project adopt `enforced` without first backfilling
|
|
157
|
+
its whole history.
|
|
148
158
|
|
|
149
159
|
## Waivers versus `@blocked`
|
|
150
160
|
|
|
@@ -203,7 +213,8 @@ A repo with no contract yet, taking its first frontend work item:
|
|
|
203
213
|
during this step, never left undisclosed.
|
|
204
214
|
3. **Write only this item's scenarios.** The first item is not a backfill project. Pre-existing
|
|
205
215
|
uncovered behavior becomes burndown in `docs/e2e-bdd-coverage.md`, and the floor starts where the
|
|
206
|
-
repo actually is.
|
|
216
|
+
repo actually is. That is a one-time act, not a recurring one: from here on the floor stays put
|
|
217
|
+
and what protects each new obligation is the per-obligation check, not the number.
|
|
207
218
|
4. **Seal this item's obligations** and commit the regenerated matrix and burndown with the change.
|
|
208
219
|
|
|
209
220
|
If a required platform has **no** e2e runner at all, that obligation is never left as a bare `N/A` —
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: "Credential-Substrate Precedence (load-bearing)"
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Credential-Substrate Precedence (load-bearing)
|
|
7
|
+
|
|
8
|
+
**When more than one substrate can reach an external system, the configured credentials
|
|
9
|
+
provider's token/CLI path goes first and the interactive MCP is the fallback — and
|
|
10
|
+
identity-match verification is mandatory on every substrate, at every tier.**
|
|
11
|
+
|
|
12
|
+
**One shared, vendor-neutral contract cited by every `*-access` skill** (the
|
|
13
|
+
`leaf-only-lifecycle` / `repo-scope-split` precedent: one shared slug, never divergent
|
|
14
|
+
per-skill prose). An access skill states its per-vendor mechanics — which token, which
|
|
15
|
+
CLI, which identity anchor — and cites this rule for the ordering. It never restates,
|
|
16
|
+
narrows, or locally overrides that ordering.
|
|
17
|
+
|
|
18
|
+
Settled by decision record `2026-08-12-credential-substrate-precedence` (D6), and
|
|
19
|
+
settled in the `settled-decisions` sense: re-arguing MCP-first inside a skill is out of
|
|
20
|
+
scope for that skill's work.
|
|
21
|
+
|
|
22
|
+
## The ladder
|
|
23
|
+
|
|
24
|
+
1. **Tier 1 — configured-provider substrate.** The token or CLI path fed by
|
|
25
|
+
`lisa-secrets-access`, chosen whenever its bootstrap credential is available **and**
|
|
26
|
+
the resolved substrate identity-matches the configured tenant/workspace/site.
|
|
27
|
+
`lisa-secrets-access` is the single chokepoint — never read an OS keychain a second
|
|
28
|
+
time.
|
|
29
|
+
2. **Tier 2 — interactive MCP**, used only when tier 1 is *genuinely* unavailable:
|
|
30
|
+
no bootstrap, no adapter for the operation (per-operation, not per-session), or a
|
|
31
|
+
provider outage. "The MCP happens to be authenticated" and "tier 1 is slower" are
|
|
32
|
+
**not** qualifying reasons.
|
|
33
|
+
3. **Tier 3 — loud, actionable failure** naming the exact credential to set and the exact
|
|
34
|
+
remediation. Never silently no-op, never blind-retry a failed or absent substrate,
|
|
35
|
+
never fall through to one that failed identity-match.
|
|
36
|
+
|
|
37
|
+
## Identity-match is mandatory on every substrate
|
|
38
|
+
|
|
39
|
+
Verified **in both directions** before any operation: the substrate must claim the
|
|
40
|
+
configured tenant, and the configured tenant must be one the substrate can reach. A
|
|
41
|
+
substrate authenticated as a different account is **skipped, never used — including at
|
|
42
|
+
tier 1**. A credential is not an identity claim; the identity claim is what the provider
|
|
43
|
+
says when asked. Skipping the check because "the user obviously meant this workspace" is
|
|
44
|
+
forbidden.
|
|
45
|
+
|
|
46
|
+
## Mutating operations: fallback is guarded, never routine
|
|
47
|
+
|
|
48
|
+
Falling back to an ambient-bound substrate for a **write** requires: switch profile and
|
|
49
|
+
assert identity immediately before the write → write → re-read the affected objects →
|
|
50
|
+
assert the tenant **from the response** (self URL host, cloudId in the path, response
|
|
51
|
+
metadata), not from the pre-flight check → on mismatch, stop, report a cross-tenant
|
|
52
|
+
hazard, and best-effort roll back. A successful pre-flight switch is not sufficient:
|
|
53
|
+
another process can mutate global state between the check and the write.
|
|
54
|
+
|
|
55
|
+
Full contract (per-vendor identity anchors and probes, the provider-first rationale, MCP's
|
|
56
|
+
first-class fallback role, consequences, and the checklist for adding or editing an access
|
|
57
|
+
skill): [reference/credential-substrate-precedence.md](credential-substrate-precedence-reference.mdc).
|