@codyswann/lisa 3.0.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 (139) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +50 -23
  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/.github/workflows/nightly-e2e-report.yml +71 -0
  11. package/expo/create-only/.maestro/flake-classification.json +21 -0
  12. package/expo/create-only/bdd/coverage-map.json +1 -2
  13. package/expo/package-lisa/package.lisa.json +1 -0
  14. package/package.json +1 -1
  15. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  16. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  17. package/plugins/lisa/.codex-plugin/skills/lisa-atlassian-access/SKILL.md +75 -64
  18. package/plugins/lisa/.codex-plugin/skills/lisa-jam-access/SKILL.md +13 -5
  19. package/plugins/lisa/.codex-plugin/skills/lisa-linear-access/SKILL.md +30 -10
  20. package/plugins/lisa/.codex-plugin/skills/lisa-notion-access/SKILL.md +36 -23
  21. package/plugins/lisa/.codex-plugin/skills/lisa-posthog-access/SKILL.md +16 -6
  22. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +8 -0
  23. package/plugins/lisa/.codex-plugin/skills/lisa-sentry-access/SKILL.md +16 -6
  24. package/plugins/lisa/.codex-plugin/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  25. package/plugins/lisa/hooks/threshold-ratchet-families.mjs +24 -0
  26. package/plugins/lisa/rules/eager/credential-substrate-precedence.md +52 -0
  27. package/plugins/lisa/rules/eager/integration-access-layer.md +7 -3
  28. package/plugins/lisa/rules/reference/bdd-e2e-coverage.md +19 -8
  29. package/plugins/lisa/rules/reference/credential-substrate-precedence.md +166 -0
  30. package/plugins/lisa/rules/reference/integration-access-layer.md +27 -15
  31. package/plugins/lisa/skills/lisa-atlassian-access/SKILL.md +76 -65
  32. package/plugins/lisa/skills/lisa-jam-access/SKILL.md +14 -6
  33. package/plugins/lisa/skills/lisa-linear-access/SKILL.md +31 -11
  34. package/plugins/lisa/skills/lisa-notion-access/SKILL.md +37 -24
  35. package/plugins/lisa/skills/lisa-posthog-access/SKILL.md +17 -7
  36. package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +8 -0
  37. package/plugins/lisa/skills/lisa-sentry-access/SKILL.md +17 -7
  38. package/plugins/lisa/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  39. package/plugins/lisa-agy/plugin.json +1 -1
  40. package/plugins/lisa-agy/skills/lisa-atlassian-access/SKILL.md +76 -65
  41. package/plugins/lisa-agy/skills/lisa-jam-access/SKILL.md +14 -6
  42. package/plugins/lisa-agy/skills/lisa-linear-access/SKILL.md +31 -11
  43. package/plugins/lisa-agy/skills/lisa-notion-access/SKILL.md +37 -24
  44. package/plugins/lisa-agy/skills/lisa-posthog-access/SKILL.md +17 -7
  45. package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +8 -0
  46. package/plugins/lisa-agy/skills/lisa-sentry-access/SKILL.md +17 -7
  47. package/plugins/lisa-agy/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  48. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  50. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  51. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-copilot/hooks/threshold-ratchet-families.mjs +24 -0
  55. package/plugins/lisa-copilot/rules/eager/credential-substrate-precedence.md +52 -0
  56. package/plugins/lisa-copilot/rules/eager/integration-access-layer.md +7 -3
  57. package/plugins/lisa-copilot/rules/reference/bdd-e2e-coverage.md +19 -8
  58. package/plugins/lisa-copilot/rules/reference/credential-substrate-precedence.md +166 -0
  59. package/plugins/lisa-copilot/rules/reference/integration-access-layer.md +27 -15
  60. package/plugins/lisa-copilot/skills/lisa-atlassian-access/SKILL.md +76 -65
  61. package/plugins/lisa-copilot/skills/lisa-jam-access/SKILL.md +14 -6
  62. package/plugins/lisa-copilot/skills/lisa-linear-access/SKILL.md +31 -11
  63. package/plugins/lisa-copilot/skills/lisa-notion-access/SKILL.md +37 -24
  64. package/plugins/lisa-copilot/skills/lisa-posthog-access/SKILL.md +17 -7
  65. package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +8 -0
  66. package/plugins/lisa-copilot/skills/lisa-sentry-access/SKILL.md +17 -7
  67. package/plugins/lisa-copilot/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  68. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-cursor/hooks/threshold-ratchet-families.mjs +24 -0
  70. package/plugins/lisa-cursor/rules/bdd-e2e-coverage-reference.mdc +19 -8
  71. package/plugins/lisa-cursor/rules/credential-substrate-precedence-reference.mdc +171 -0
  72. package/plugins/lisa-cursor/rules/credential-substrate-precedence.mdc +57 -0
  73. package/plugins/lisa-cursor/rules/integration-access-layer-reference.mdc +27 -15
  74. package/plugins/lisa-cursor/rules/integration-access-layer.mdc +7 -3
  75. package/plugins/lisa-cursor/skills/lisa-atlassian-access/SKILL.md +76 -65
  76. package/plugins/lisa-cursor/skills/lisa-jam-access/SKILL.md +14 -6
  77. package/plugins/lisa-cursor/skills/lisa-linear-access/SKILL.md +31 -11
  78. package/plugins/lisa-cursor/skills/lisa-notion-access/SKILL.md +37 -24
  79. package/plugins/lisa-cursor/skills/lisa-posthog-access/SKILL.md +17 -7
  80. package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +8 -0
  81. package/plugins/lisa-cursor/skills/lisa-sentry-access/SKILL.md +17 -7
  82. package/plugins/lisa-cursor/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  83. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  84. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  85. package/plugins/lisa-expo-agy/plugin.json +1 -1
  86. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  87. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  88. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  89. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  90. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  91. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  92. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  93. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  94. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  95. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  96. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  97. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  98. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  99. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  100. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  101. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  102. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  103. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  104. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  105. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  106. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  107. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  108. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  109. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  110. package/plugins/lisa-rails-agy/plugin.json +1 -1
  111. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  112. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  113. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  114. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  115. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  116. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  117. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  118. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  119. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  120. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  121. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  122. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  123. package/plugins/src/base/hooks/threshold-ratchet-families.mjs +24 -0
  124. package/plugins/src/base/rules/eager/credential-substrate-precedence.md +52 -0
  125. package/plugins/src/base/rules/eager/integration-access-layer.md +7 -3
  126. package/plugins/src/base/rules/reference/bdd-e2e-coverage.md +19 -8
  127. package/plugins/src/base/rules/reference/credential-substrate-precedence.md +166 -0
  128. package/plugins/src/base/rules/reference/integration-access-layer.md +27 -15
  129. package/plugins/src/base/skills/lisa-atlassian-access/SKILL.md +76 -65
  130. package/plugins/src/base/skills/lisa-jam-access/SKILL.md +14 -6
  131. package/plugins/src/base/skills/lisa-linear-access/SKILL.md +31 -11
  132. package/plugins/src/base/skills/lisa-notion-access/SKILL.md +37 -24
  133. package/plugins/src/base/skills/lisa-posthog-access/SKILL.md +17 -7
  134. package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +8 -0
  135. package/plugins/src/base/skills/lisa-sentry-access/SKILL.md +17 -7
  136. package/plugins/src/base/skills/lisa-sonarcloud-access/SKILL.md +7 -1
  137. package/rails/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
  138. package/typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs +631 -5
  139. package/typescript/copy-overwrite/scripts/threshold-ratchet-families.mjs +24 -0
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "AWS CDK-specific plugin",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "3.0.0",
3
+ "version": "3.2.0",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
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).
@@ -4,7 +4,11 @@ Skills and rules that use external integrations route through the matching
4
4
  `*-access` skill. Do not call vendor MCP tools or REST APIs directly from a
5
5
  consumer skill.
6
6
 
7
- Resolution order is MCP when available and authenticated, then documented
8
- token/REST substrate when the env var is set, then a loud error naming the env
9
- var. Full matrix and migration rules:
7
+ Resolution order is the configured-provider token/CLI substrate first when its
8
+ bootstrap credential is present and identity-matched, then the interactive MCP as
9
+ fallback, then a loud error naming the exact credential to set. Identity-match is
10
+ mandatory on every substrate — one authenticated as a different tenant is skipped,
11
+ never used. The ordering itself is settled in
12
+ [reference/credential-substrate-precedence.md](../reference/credential-substrate-precedence.md);
13
+ full matrix and migration rules:
10
14
  [reference/integration-access-layer.md](../reference/integration-access-layer.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` —
@@ -0,0 +1,166 @@
1
+ # Credential-Substrate Precedence
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
+ This is **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 the ordering.
12
+
13
+ Settled by the decision record `2026-08-12-credential-substrate-precedence` (D6). It is
14
+ a **settled decision** in the `settled-decisions` sense: re-arguing MCP-first inside a
15
+ skill is out of scope for that skill's work.
16
+
17
+ ## The ladder
18
+
19
+ ### Tier 1 — configured-provider substrate
20
+
21
+ The token or CLI path fed by `lisa-secrets-access`. Chosen **whenever its bootstrap
22
+ credential is available AND the resolved substrate identity-matches the configured
23
+ tenant/workspace/site.**
24
+
25
+ `lisa-secrets-access` is the single chokepoint that makes this tier actionable rather
26
+ than aspirational: it owns the one-store rule and the surface ladder, and its `tool:`
27
+ note line already declares which CLI a given credential is expected to drive. An access
28
+ skill resolves its credential through that chokepoint — never by reading an OS keychain
29
+ a second time.
30
+
31
+ Both halves of the gate are load-bearing. A present credential that identity-matches
32
+ nothing is **not** tier 1; it is a failed tier, and the ladder moves on.
33
+
34
+ ### Tier 2 — interactive MCP
35
+
36
+ Used when the tier 1 path is **genuinely unavailable**. The three genuine cases:
37
+
38
+ - **No bootstrap** — the provider credential is absent or the project has not adopted a
39
+ credentials provider at all.
40
+ - **No adapter for the operation** — the token/CLI substrate has no documented adapter
41
+ for the requested operation, and the MCP does (per-operation, not per-session: a skill
42
+ may resolve tier 1 for one operation and tier 2 for the next).
43
+ - **Provider outage** — the provider path is present but failing for reasons the caller
44
+ cannot fix in-session.
45
+
46
+ "The MCP happens to be authenticated" is not one of them. Neither is "tier 1 is slower."
47
+
48
+ ### Tier 3 — loud, actionable failure
49
+
50
+ When no substrate is both available and identity-matched, fail with a message naming the
51
+ exact credential to set and the exact remediation path. Never silently no-op, never
52
+ blind-retry a failed or absent substrate, and never fall through to a substrate that
53
+ failed identity-match.
54
+
55
+ ## Identity-match is mandatory on every substrate
56
+
57
+ Verification runs **in both directions** before any operation: the substrate must claim
58
+ the configured tenant, and the configured tenant must be one the substrate can actually
59
+ reach. A substrate authenticated as a **different** account is **skipped, never used**,
60
+ regardless of tier — including tier 1. A credential is not an identity claim; the
61
+ identity claim is what the provider says when asked.
62
+
63
+ | Vendor | Identity anchor | Probe |
64
+ |---|---|---|
65
+ | Atlassian | `atlassian.cloudId` / `atlassian.site` | `/rest/api/3/myself` email, acli `auth status` site, MCP accessible-resources contains the cloudId |
66
+ | Notion | `notion.workspaceId` (+ `prdDatabaseId` reachability) | `GET /v1/users/me` → `bot.workspace_name`/`workspace_id` |
67
+ | Linear | `linear.workspace` / `linear.teamKey` | `viewer`/`organization` on GraphQL; team list through the MCP |
68
+ | Sentry / PostHog / Jam / Sonar | configured org + project | the substrate's own whoami/org listing |
69
+
70
+ Skipping the check because "the user obviously meant this workspace" is forbidden. Silent
71
+ cross-tenant operations are precisely the hazard this contract exists to prevent.
72
+
73
+ ## Why provider-first (and why it overturns a working default)
74
+
75
+ MCP-first was defensible and is being overturned deliberately, not corrected as an
76
+ oversight. Three reasons outweigh it.
77
+
78
+ **Headless parity.** Cron runs, cloud sessions, CI, and subagent sessions have no
79
+ browser. Under MCP-first an interactive session and a headless session resolve through
80
+ *different* substrates and can therefore fail differently — and the failure surfaces only
81
+ in the environment nobody is watching. Provider-first makes the primary path the same one
82
+ everywhere, with MCP as the enhancement rather than the default.
83
+
84
+ **Tenant safety — the generalized Atlassian write rule.** Substrates differ in *where
85
+ their target comes from*:
86
+
87
+ - **Per-invocation-bound.** The target is part of the call. A cloudId-scoped REST URL
88
+ (`https://api.atlassian.com/ex/jira/<CLOUDID>/…`) or a workspace-scoped API token
89
+ carries its tenant in the request itself, so nothing outside the call can redirect it.
90
+ - **Ambient-bound.** The target comes from machine-global or session-global state a skill
91
+ does not own: acli's single active account, an MCP's browser OAuth session bound to
92
+ whatever account the human last used. Any other process — or the human — can change it
93
+ between the check and the call. That is a **TOCTOU** window, and a successful
94
+ pre-flight `auth status` does not close it.
95
+
96
+ Prefer the per-invocation-bound substrate. This is exactly why Atlassian JIRA *writes*
97
+ were already forced onto the cloudId-scoped curl adapter; the hazard is not specific to
98
+ Atlassian and not specific to writes. A misrouted write is loud and often reversible; a
99
+ **read through the wrong tenant silently returns wrong data**, which then propagates into
100
+ tickets, PRDs, and verification claims — harder to detect and harder to unwind. Reads get
101
+ the same ordering as writes.
102
+
103
+ **Determinism.** A token path either has its bootstrap or does not, and says so. An MCP's
104
+ readiness depends on session state a skill cannot inspect reliably — the same server
105
+ registers under different prefixes depending on install path, and its data tools register
106
+ only after OAuth completes.
107
+
108
+ ## MCP stays a first-class fallback
109
+
110
+ This is a **re-ordering, not a removal.** The strongest argument for MCP-first — that an
111
+ already-authenticated MCP is zero-setup and identity-verified — is preserved by keeping
112
+ MCP as a genuine, fully supported tier rather than deleting it:
113
+
114
+ - Every access skill keeps its MCP adapters in the dispatch table.
115
+ - An operation with no tier 1 adapter routes to MCP **as the normal path**, not as an
116
+ error.
117
+ - A project with no credentials provider is fully functional on MCP alone.
118
+ - MCP failure messages stay actionable (how to enable and authenticate the plugin).
119
+
120
+ Removing an MCP adapter is a separate decision requiring its own justification. Do not
121
+ treat this contract as license to delete one.
122
+
123
+ ## Guarded fallback for ambient-bound substrates
124
+
125
+ When the ladder does fall back to an ambient-bound substrate for a **mutating**
126
+ operation, the fallback is guarded — never the normal path:
127
+
128
+ 1. Switch to the configured profile and assert the active identity matches config
129
+ immediately before the write.
130
+ 2. Execute the write.
131
+ 3. Re-read the affected object(s) immediately afterward.
132
+ 4. Perform a **post-write tenant assertion** on the response — the tenant is proven from
133
+ the response (self URL host, cloudId in the path, or response metadata), not assumed
134
+ from the pre-flight check.
135
+ 5. On mismatch: stop, report a cross-tenant hazard, and best-effort **roll back** the
136
+ write when a safe reversal exists (delete the created object, remove the created
137
+ comment/link, revert a reversible field edit). Never continue as if it succeeded.
138
+
139
+ A successful pre-flight switch is not sufficient for tenant safety — another process can
140
+ mutate global state between the check and the write.
141
+
142
+ ## Consequences to expect
143
+
144
+ - **A stale or wrong token now fails identity-match instead of silently succeeding
145
+ through an authenticated MCP.** That is intended: it is the exact class of bug this
146
+ contract exists to surface. Fix the credential (`/lisa:setup:<vendor>`); do not
147
+ re-order the ladder to route around it.
148
+ - Headless and interactive sessions take the same primary path, so a credential problem
149
+ reproduces on a laptop instead of only at 3am in cron.
150
+ - The ordering is **not configurable per project**. A knob would let a project
151
+ reintroduce the headless divergence this contract removes. Revisit only with a concrete
152
+ need and a new decision record.
153
+
154
+ ## Adding or editing an access skill
155
+
156
+ 1. Cite this rule by name; do not restate the ordering.
157
+ 2. Document the tier 1 credential and the `lisa-secrets-access` resolution path.
158
+ 3. Document the identity anchor and its probe, and state that mismatch means skip.
159
+ 4. Keep MCP adapters and name the operations for which MCP is the only substrate.
160
+ 5. Make the terminal failure name the exact credential and remediation command.
161
+
162
+ Single-substrate access skills are conformant when their one substrate is
163
+ provider-credential-authenticated (`lisa-sonarcloud-access`: the official SonarQube MCP
164
+ authenticates headlessly from `SONARQUBE_CLI_TOKEN`, so it *is* the tier 1 substrate and
165
+ needs no separate REST tier). Reserve a multi-tier ladder for vendors whose MCP is
166
+ browser-OAuth or keychain-bound and therefore dead headless.
@@ -4,24 +4,35 @@ Every Lisa skill or rule that consumes an external integration MUST route throug
4
4
  the integration's `*-access` skill instead of calling that vendor's MCP tools or
5
5
  REST API directly.
6
6
 
7
- The access skill owns substrate resolution:
7
+ The access skill owns substrate resolution. **The ordering is not this rule's to
8
+ define** — it is the single shared contract in `credential-substrate-precedence`,
9
+ cited identically by every `*-access` skill:
8
10
 
9
- 1. MCP, when the tool is available and already authenticated to the configured
10
- workspace/account.
11
- 2. Token/REST substrate, only when the documented env var is present.
12
- 3. Loud failure naming the exact env var to set.
11
+ 1. **Configured-provider token/CLI substrate** the path fed by
12
+ `lisa-secrets-access` — when its bootstrap credential is present AND the
13
+ resolved substrate identity-matches the configured tenant/workspace/account.
14
+ 2. **Interactive MCP**, as a first-class fallback, when the provider path is
15
+ genuinely unavailable (no bootstrap, no adapter for the operation, provider
16
+ outage).
17
+ 3. Loud failure naming the exact credential to set.
13
18
 
14
- Do not blind-retry a failed or absent MCP. Fall back only after checking the
15
- documented env var for that vendor, and never silently no-op when neither tier is
16
- available.
19
+ Identity-match verification is mandatory on **every** substrate, in both
20
+ directions; one authenticated as a different tenant is skipped, never used. See the
21
+ `credential-substrate-precedence` rule for the rationale (headless parity, tenant
22
+ safety, determinism), the guarded-fallback protocol, and what "genuinely
23
+ unavailable" means.
24
+
25
+ Do not blind-retry a failed or absent substrate, and never silently no-op when no
26
+ tier is available.
17
27
 
18
28
  Some MCPs authenticate headlessly from an env token and need no separate REST
19
- tier — the MCP **is** the headless substrate on both developer machines and cloud
20
- routines. The official SonarQube MCP is one such case (`SONARQUBE_CLI_TOKEN`
29
+ tier — such an MCP **is** the configured-provider substrate on both developer
30
+ machines and cloud routines, so a single-substrate access skill is conformant. The
31
+ official SonarQube MCP is one such case (`SONARQUBE_CLI_TOKEN`
21
32
  [+ `SONARQUBE_CLI_ORG`/`SONARQUBE_CLI_SERVER`]): `lisa-sonarcloud-access` resolves it as a
22
- single substrate with no hand-rolled REST fallback. Reserve the two-tier
23
- MCP-then-REST shape for vendors whose MCP is browser-OAuth or keychain-bound and
24
- therefore dead headless.
33
+ single substrate with no hand-rolled REST fallback. Reserve the multi-tier ladder
34
+ for vendors whose MCP is browser-OAuth or keychain-bound and therefore dead
35
+ headless.
25
36
 
26
37
  ## Access Skills
27
38
 
@@ -60,7 +71,8 @@ When editing any skill listed in the matrix:
60
71
  delegate to the matching access skill.
61
72
  - Keep operation names coarse and vendor-native. Add new operation rows to the
62
73
  access skill instead of embedding REST details in the consumer.
63
- - Preserve existing MCP behavior as the first tier where Claude/Codex can expose
64
- the MCP, but make headless token fallback explicit and gated by the env var.
74
+ - Put the documented token/CLI substrate first and keep the MCP as an explicit,
75
+ fully supported fallback tier `credential-substrate-precedence`. Preserving
76
+ the MCP adapters is required; re-ordering them is not the same as removing them.
65
77
  - If a vendor has no documented token substrate, keep the MCP-only behavior and
66
78
  fail with a clear "no documented headless substrate" message.