@codyswann/lisa 2.317.2 → 2.317.3

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 (72) hide show
  1. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  2. package/dist/core/upstream-evidence-manifest.js +4 -3
  3. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  4. package/package.json +1 -1
  5. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  6. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  7. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +12 -0
  8. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  9. package/plugins/lisa/.codex-plugin/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
  10. package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +12 -0
  11. package/plugins/lisa/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  12. package/plugins/lisa/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
  13. package/plugins/lisa-agy/plugin.json +1 -1
  14. package/plugins/lisa-agy/skills/lisa-secrets-access/SKILL.md +12 -0
  15. package/plugins/lisa-agy/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  16. package/plugins/lisa-agy/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
  17. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  19. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  20. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  23. package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +12 -0
  24. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  25. package/plugins/lisa-copilot/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
  26. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  27. package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +12 -0
  28. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  29. package/plugins/lisa-cursor/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
  30. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  32. package/plugins/lisa-expo-agy/plugin.json +1 -1
  33. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  34. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  37. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  38. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  39. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  40. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  42. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  43. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  44. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  45. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  47. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  48. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  49. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  50. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  52. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  53. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  54. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  55. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  57. package/plugins/lisa-rails-agy/plugin.json +1 -1
  58. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  59. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  60. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  62. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  63. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  64. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  65. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  67. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  68. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  69. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  70. package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +12 -0
  71. package/plugins/src/base/skills/lisa-secrets-access/scripts/providers.mjs +52 -6
  72. package/plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs +15 -6
package/package.json CHANGED
@@ -115,7 +115,7 @@
115
115
  "brace-expansion": ">=5.0.8"
116
116
  },
117
117
  "name": "@codyswann/lisa",
118
- "version": "2.317.2",
118
+ "version": "2.317.3",
119
119
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
120
120
  "main": "dist/index.js",
121
121
  "exports": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.317.2",
3
+ "version": "2.317.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.317.2",
3
+ "version": "2.317.3",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -82,6 +82,18 @@ ${XDG_CONFIG_HOME:-$HOME/.config}/<secrets.namespace>/ # dir 0700
82
82
 
83
83
  **`bootstrap`** — how to obtain the one credential that unlocks the rest. `sources` is ordered, environment first. This is the **only** credential permitted in a keychain; it is a bootstrap, not a cache.
84
84
 
85
+ `bootstrap.key` answers exactly one question: **where do we find it** — the environment variable name and keychain service to look under. It is *not* the name the provider CLI reads. That name is fixed by the vendor (`bws` reads `BWS_ACCESS_TOKEN` and nothing else) and lives in `PROVIDER_BOOTSTRAP_ENV` in `providers.mjs`, which translates one into the other before invoking the CLI.
86
+
87
+ Keeping these separate is what makes **one workstation serve several tenants**. Give each its own name and each project points at its own:
88
+
89
+ ```json
90
+ "bootstrap": { "sources": ["env", "keychain"], "key": "BWS_ACCESS_TOKEN_acme" }
91
+ ```
92
+
93
+ Conflating them worked only while every project used the default, where the two coincide. The first project to slug its key handed the CLI a variable it had never heard of, and every read and rotation failed with `Missing access token`. If you add a provider, add its canonical variable to that table — do not reach for `bootstrap.key`.
94
+
95
+ On the GitHub Actions surface the repository secret and the exported environment variable both carry the **configured** name, and the generated workflow templates it. Translating to the CLI's own variable stays in `providers.mjs`.
96
+
85
97
  **`require`** — optional. Omit it and every secret the provider grants is available, which is correct when the provider already scopes access per project. Present, it narrows to exactly those names **and asserts them**: a listed name that does not resolve is a startup error, not a late surprise.
86
98
 
87
99
  **`narrow`** — may only *narrow* the provider's own grant. There is deliberately no way to widen access from config; that boundary belongs to the provider.
@@ -28,6 +28,56 @@ export const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
28
28
  */
29
29
  export const LEASE_KEY = "LISA_ROTATION_LEASES";
30
30
 
31
+ /**
32
+ * The environment variable each provider's CLI reads its own bootstrap from.
33
+ *
34
+ * This is deliberately *not* configurable, and separating it from
35
+ * `bootstrap.key` is the whole point. Two different questions were previously
36
+ * answered by one value:
37
+ *
38
+ * - **Where do we find the bootstrap?** A keychain service or environment
39
+ * variable name. That must be configurable, because one workstation serves
40
+ * several tenants and each needs its own token stored under its own name.
41
+ * - **What does the provider CLI call it?** Fixed by the vendor. `bws` reads
42
+ * `BWS_ACCESS_TOKEN` and nothing else.
43
+ *
44
+ * Conflating them worked only while every project used the default name, where
45
+ * the two happen to coincide. The moment a project set
46
+ * `bootstrap.key: "BWS_ACCESS_TOKEN_<tenant>"` the CLI was handed a variable it
47
+ * has never heard of and failed with "Missing access token".
48
+ */
49
+ const PROVIDER_BOOTSTRAP_ENV = {
50
+ bitwarden: "BWS_ACCESS_TOKEN",
51
+ doppler: "DOPPLER_TOKEN",
52
+ };
53
+
54
+ /**
55
+ * Build the child environment for a provider CLI, injecting the bootstrap under
56
+ * the name that CLI actually reads.
57
+ *
58
+ * Both the read path and the rotation write path need this, and they previously
59
+ * carried separate copies of the same line — which is how they would eventually
60
+ * have drifted. One helper, one place to be wrong.
61
+ * @param {object} cfg Resolved configuration.
62
+ * @returns {NodeJS.ProcessEnv} Environment for the child process.
63
+ */
64
+ export function providerEnv(cfg) {
65
+ const env = { ...process.env };
66
+ const canonical = PROVIDER_BOOTSTRAP_ENV[cfg.provider];
67
+ if (!canonical) return env;
68
+ const token = bootstrapToken(cfg.bootstrap);
69
+ if (token) env[canonical] = token;
70
+ // Drop the tenant-scoped name once its value has been placed under the one
71
+ // the CLI reads. Inheriting it would leave two variables holding the same
72
+ // bootstrap in the child, which is how a tool that probes for a
73
+ // similarly-named credential binds to the wrong tenant on a workstation
74
+ // serving several. The child needs exactly one.
75
+ if (cfg.bootstrap.key && cfg.bootstrap.key !== canonical) {
76
+ delete env[cfg.bootstrap.key];
77
+ }
78
+ return env;
79
+ }
80
+
31
81
  /**
32
82
  * Obtain the one credential that unlocks the provider.
33
83
  *
@@ -83,9 +133,7 @@ function fromKeychain(key) {
83
133
  * @returns {Array<{key: string, value: string, note: string, projectId: string|null}>} Rows.
84
134
  */
85
135
  export function fetchRaw(cfg) {
86
- const env = { ...process.env };
87
- const token = bootstrapToken(cfg.bootstrap);
88
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
136
+ const env = providerEnv(cfg);
89
137
 
90
138
  if (cfg.provider === "env") {
91
139
  return Object.entries(process.env)
@@ -225,9 +273,7 @@ export function rawByKey(cfg, key) {
225
273
  * @param {string} value Replacement value.
226
274
  */
227
275
  export function writeSecret(cfg, id, value) {
228
- const env = { ...process.env };
229
- const token = bootstrapToken(cfg.bootstrap);
230
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
276
+ const env = providerEnv(cfg);
231
277
 
232
278
  if (cfg.provider === "bitwarden") {
233
279
  run("bws", ["secret", "edit", id, "--value", value], env);
@@ -44,6 +44,10 @@ export function readAutomation(name, cwd = process.cwd()) {
44
44
  return {
45
45
  ...loop,
46
46
  repository: cfg.remoteEnv?.surfaces?.[loop.executionEnv]?.repository,
47
+ // Where this project keeps its bootstrap. A workstation serving several
48
+ // tenants gives each its own name, so the generated workflow must template
49
+ // it rather than assume the default.
50
+ bootstrapKey: cfg.secrets?.bootstrap?.key ?? "BWS_ACCESS_TOKEN",
47
51
  };
48
52
  }
49
53
 
@@ -70,6 +74,11 @@ export function readAutomation(name, cwd = process.cwd()) {
70
74
  */
71
75
  export function renderWorkflow(name, loop) {
72
76
  if (!loop.schedule) throw new Error(`automations["${name}"] has no schedule`);
77
+ // The repository secret and the environment variable carry the same name, so
78
+ // the value the workflow exports is the one `bootstrap.key` tells the resolver
79
+ // to look for. Translating it to the provider CLI's own variable is
80
+ // providers.mjs's job, not this workflow's.
81
+ const bootstrap = loop.bootstrapKey ?? "BWS_ACCESS_TOKEN";
73
82
  if (!loop.repository) {
74
83
  throw new Error(
75
84
  `automations["${name}"] targets executionEnv "${loop.executionEnv}", ` +
@@ -118,22 +127,22 @@ jobs:
118
127
  # seconds rather than after a toolchain download.
119
128
  - name: Check the bootstrap credential is configured
120
129
  env:
121
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
130
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
122
131
  run: |
123
132
  set -euo pipefail
124
- test -n "\${BWS_ACCESS_TOKEN}" || {
125
- echo "BWS_ACCESS_TOKEN is not configured for this repository" >&2
133
+ test -n "\${${bootstrap}}" || {
134
+ echo "${bootstrap} is not configured for this repository" >&2
126
135
  exit 1
127
136
  }
128
137
 
129
138
  - name: Prepare the toolchain and secrets
130
139
  env:
131
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
140
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
132
141
  run: bash scripts/lisa-remote-env/setup.sh
133
142
 
134
143
  - name: Run one ${name} cycle
135
144
  env:
136
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
145
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
137
146
  run: |
138
147
  set -euo pipefail
139
148
  node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
@@ -145,7 +154,7 @@ jobs:
145
154
  - name: Persist any credential rotation
146
155
  if: \${{ always() }}
147
156
  env:
148
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
157
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
149
158
  run: |
150
159
  set -euo pipefail
151
160
  node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases
@@ -82,6 +82,18 @@ ${XDG_CONFIG_HOME:-$HOME/.config}/<secrets.namespace>/ # dir 0700
82
82
 
83
83
  **`bootstrap`** — how to obtain the one credential that unlocks the rest. `sources` is ordered, environment first. This is the **only** credential permitted in a keychain; it is a bootstrap, not a cache.
84
84
 
85
+ `bootstrap.key` answers exactly one question: **where do we find it** — the environment variable name and keychain service to look under. It is *not* the name the provider CLI reads. That name is fixed by the vendor (`bws` reads `BWS_ACCESS_TOKEN` and nothing else) and lives in `PROVIDER_BOOTSTRAP_ENV` in `providers.mjs`, which translates one into the other before invoking the CLI.
86
+
87
+ Keeping these separate is what makes **one workstation serve several tenants**. Give each its own name and each project points at its own:
88
+
89
+ ```json
90
+ "bootstrap": { "sources": ["env", "keychain"], "key": "BWS_ACCESS_TOKEN_acme" }
91
+ ```
92
+
93
+ Conflating them worked only while every project used the default, where the two coincide. The first project to slug its key handed the CLI a variable it had never heard of, and every read and rotation failed with `Missing access token`. If you add a provider, add its canonical variable to that table — do not reach for `bootstrap.key`.
94
+
95
+ On the GitHub Actions surface the repository secret and the exported environment variable both carry the **configured** name, and the generated workflow templates it. Translating to the CLI's own variable stays in `providers.mjs`.
96
+
85
97
  **`require`** — optional. Omit it and every secret the provider grants is available, which is correct when the provider already scopes access per project. Present, it narrows to exactly those names **and asserts them**: a listed name that does not resolve is a startup error, not a late surprise.
86
98
 
87
99
  **`narrow`** — may only *narrow* the provider's own grant. There is deliberately no way to widen access from config; that boundary belongs to the provider.
@@ -28,6 +28,56 @@ export const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
28
28
  */
29
29
  export const LEASE_KEY = "LISA_ROTATION_LEASES";
30
30
 
31
+ /**
32
+ * The environment variable each provider's CLI reads its own bootstrap from.
33
+ *
34
+ * This is deliberately *not* configurable, and separating it from
35
+ * `bootstrap.key` is the whole point. Two different questions were previously
36
+ * answered by one value:
37
+ *
38
+ * - **Where do we find the bootstrap?** A keychain service or environment
39
+ * variable name. That must be configurable, because one workstation serves
40
+ * several tenants and each needs its own token stored under its own name.
41
+ * - **What does the provider CLI call it?** Fixed by the vendor. `bws` reads
42
+ * `BWS_ACCESS_TOKEN` and nothing else.
43
+ *
44
+ * Conflating them worked only while every project used the default name, where
45
+ * the two happen to coincide. The moment a project set
46
+ * `bootstrap.key: "BWS_ACCESS_TOKEN_<tenant>"` the CLI was handed a variable it
47
+ * has never heard of and failed with "Missing access token".
48
+ */
49
+ const PROVIDER_BOOTSTRAP_ENV = {
50
+ bitwarden: "BWS_ACCESS_TOKEN",
51
+ doppler: "DOPPLER_TOKEN",
52
+ };
53
+
54
+ /**
55
+ * Build the child environment for a provider CLI, injecting the bootstrap under
56
+ * the name that CLI actually reads.
57
+ *
58
+ * Both the read path and the rotation write path need this, and they previously
59
+ * carried separate copies of the same line — which is how they would eventually
60
+ * have drifted. One helper, one place to be wrong.
61
+ * @param {object} cfg Resolved configuration.
62
+ * @returns {NodeJS.ProcessEnv} Environment for the child process.
63
+ */
64
+ export function providerEnv(cfg) {
65
+ const env = { ...process.env };
66
+ const canonical = PROVIDER_BOOTSTRAP_ENV[cfg.provider];
67
+ if (!canonical) return env;
68
+ const token = bootstrapToken(cfg.bootstrap);
69
+ if (token) env[canonical] = token;
70
+ // Drop the tenant-scoped name once its value has been placed under the one
71
+ // the CLI reads. Inheriting it would leave two variables holding the same
72
+ // bootstrap in the child, which is how a tool that probes for a
73
+ // similarly-named credential binds to the wrong tenant on a workstation
74
+ // serving several. The child needs exactly one.
75
+ if (cfg.bootstrap.key && cfg.bootstrap.key !== canonical) {
76
+ delete env[cfg.bootstrap.key];
77
+ }
78
+ return env;
79
+ }
80
+
31
81
  /**
32
82
  * Obtain the one credential that unlocks the provider.
33
83
  *
@@ -83,9 +133,7 @@ function fromKeychain(key) {
83
133
  * @returns {Array<{key: string, value: string, note: string, projectId: string|null}>} Rows.
84
134
  */
85
135
  export function fetchRaw(cfg) {
86
- const env = { ...process.env };
87
- const token = bootstrapToken(cfg.bootstrap);
88
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
136
+ const env = providerEnv(cfg);
89
137
 
90
138
  if (cfg.provider === "env") {
91
139
  return Object.entries(process.env)
@@ -225,9 +273,7 @@ export function rawByKey(cfg, key) {
225
273
  * @param {string} value Replacement value.
226
274
  */
227
275
  export function writeSecret(cfg, id, value) {
228
- const env = { ...process.env };
229
- const token = bootstrapToken(cfg.bootstrap);
230
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
276
+ const env = providerEnv(cfg);
231
277
 
232
278
  if (cfg.provider === "bitwarden") {
233
279
  run("bws", ["secret", "edit", id, "--value", value], env);
@@ -44,6 +44,10 @@ export function readAutomation(name, cwd = process.cwd()) {
44
44
  return {
45
45
  ...loop,
46
46
  repository: cfg.remoteEnv?.surfaces?.[loop.executionEnv]?.repository,
47
+ // Where this project keeps its bootstrap. A workstation serving several
48
+ // tenants gives each its own name, so the generated workflow must template
49
+ // it rather than assume the default.
50
+ bootstrapKey: cfg.secrets?.bootstrap?.key ?? "BWS_ACCESS_TOKEN",
47
51
  };
48
52
  }
49
53
 
@@ -70,6 +74,11 @@ export function readAutomation(name, cwd = process.cwd()) {
70
74
  */
71
75
  export function renderWorkflow(name, loop) {
72
76
  if (!loop.schedule) throw new Error(`automations["${name}"] has no schedule`);
77
+ // The repository secret and the environment variable carry the same name, so
78
+ // the value the workflow exports is the one `bootstrap.key` tells the resolver
79
+ // to look for. Translating it to the provider CLI's own variable is
80
+ // providers.mjs's job, not this workflow's.
81
+ const bootstrap = loop.bootstrapKey ?? "BWS_ACCESS_TOKEN";
73
82
  if (!loop.repository) {
74
83
  throw new Error(
75
84
  `automations["${name}"] targets executionEnv "${loop.executionEnv}", ` +
@@ -118,22 +127,22 @@ jobs:
118
127
  # seconds rather than after a toolchain download.
119
128
  - name: Check the bootstrap credential is configured
120
129
  env:
121
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
130
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
122
131
  run: |
123
132
  set -euo pipefail
124
- test -n "\${BWS_ACCESS_TOKEN}" || {
125
- echo "BWS_ACCESS_TOKEN is not configured for this repository" >&2
133
+ test -n "\${${bootstrap}}" || {
134
+ echo "${bootstrap} is not configured for this repository" >&2
126
135
  exit 1
127
136
  }
128
137
 
129
138
  - name: Prepare the toolchain and secrets
130
139
  env:
131
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
140
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
132
141
  run: bash scripts/lisa-remote-env/setup.sh
133
142
 
134
143
  - name: Run one ${name} cycle
135
144
  env:
136
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
145
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
137
146
  run: |
138
147
  set -euo pipefail
139
148
  node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
@@ -145,7 +154,7 @@ jobs:
145
154
  - name: Persist any credential rotation
146
155
  if: \${{ always() }}
147
156
  env:
148
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
157
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
149
158
  run: |
150
159
  set -euo pipefail
151
160
  node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "2.317.2",
3
+ "version": "2.317.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -82,6 +82,18 @@ ${XDG_CONFIG_HOME:-$HOME/.config}/<secrets.namespace>/ # dir 0700
82
82
 
83
83
  **`bootstrap`** — how to obtain the one credential that unlocks the rest. `sources` is ordered, environment first. This is the **only** credential permitted in a keychain; it is a bootstrap, not a cache.
84
84
 
85
+ `bootstrap.key` answers exactly one question: **where do we find it** — the environment variable name and keychain service to look under. It is *not* the name the provider CLI reads. That name is fixed by the vendor (`bws` reads `BWS_ACCESS_TOKEN` and nothing else) and lives in `PROVIDER_BOOTSTRAP_ENV` in `providers.mjs`, which translates one into the other before invoking the CLI.
86
+
87
+ Keeping these separate is what makes **one workstation serve several tenants**. Give each its own name and each project points at its own:
88
+
89
+ ```json
90
+ "bootstrap": { "sources": ["env", "keychain"], "key": "BWS_ACCESS_TOKEN_acme" }
91
+ ```
92
+
93
+ Conflating them worked only while every project used the default, where the two coincide. The first project to slug its key handed the CLI a variable it had never heard of, and every read and rotation failed with `Missing access token`. If you add a provider, add its canonical variable to that table — do not reach for `bootstrap.key`.
94
+
95
+ On the GitHub Actions surface the repository secret and the exported environment variable both carry the **configured** name, and the generated workflow templates it. Translating to the CLI's own variable stays in `providers.mjs`.
96
+
85
97
  **`require`** — optional. Omit it and every secret the provider grants is available, which is correct when the provider already scopes access per project. Present, it narrows to exactly those names **and asserts them**: a listed name that does not resolve is a startup error, not a late surprise.
86
98
 
87
99
  **`narrow`** — may only *narrow* the provider's own grant. There is deliberately no way to widen access from config; that boundary belongs to the provider.
@@ -28,6 +28,56 @@ export const ENV_KEY = /^[A-Za-z_][A-Za-z0-9_]*$/;
28
28
  */
29
29
  export const LEASE_KEY = "LISA_ROTATION_LEASES";
30
30
 
31
+ /**
32
+ * The environment variable each provider's CLI reads its own bootstrap from.
33
+ *
34
+ * This is deliberately *not* configurable, and separating it from
35
+ * `bootstrap.key` is the whole point. Two different questions were previously
36
+ * answered by one value:
37
+ *
38
+ * - **Where do we find the bootstrap?** A keychain service or environment
39
+ * variable name. That must be configurable, because one workstation serves
40
+ * several tenants and each needs its own token stored under its own name.
41
+ * - **What does the provider CLI call it?** Fixed by the vendor. `bws` reads
42
+ * `BWS_ACCESS_TOKEN` and nothing else.
43
+ *
44
+ * Conflating them worked only while every project used the default name, where
45
+ * the two happen to coincide. The moment a project set
46
+ * `bootstrap.key: "BWS_ACCESS_TOKEN_<tenant>"` the CLI was handed a variable it
47
+ * has never heard of and failed with "Missing access token".
48
+ */
49
+ const PROVIDER_BOOTSTRAP_ENV = {
50
+ bitwarden: "BWS_ACCESS_TOKEN",
51
+ doppler: "DOPPLER_TOKEN",
52
+ };
53
+
54
+ /**
55
+ * Build the child environment for a provider CLI, injecting the bootstrap under
56
+ * the name that CLI actually reads.
57
+ *
58
+ * Both the read path and the rotation write path need this, and they previously
59
+ * carried separate copies of the same line — which is how they would eventually
60
+ * have drifted. One helper, one place to be wrong.
61
+ * @param {object} cfg Resolved configuration.
62
+ * @returns {NodeJS.ProcessEnv} Environment for the child process.
63
+ */
64
+ export function providerEnv(cfg) {
65
+ const env = { ...process.env };
66
+ const canonical = PROVIDER_BOOTSTRAP_ENV[cfg.provider];
67
+ if (!canonical) return env;
68
+ const token = bootstrapToken(cfg.bootstrap);
69
+ if (token) env[canonical] = token;
70
+ // Drop the tenant-scoped name once its value has been placed under the one
71
+ // the CLI reads. Inheriting it would leave two variables holding the same
72
+ // bootstrap in the child, which is how a tool that probes for a
73
+ // similarly-named credential binds to the wrong tenant on a workstation
74
+ // serving several. The child needs exactly one.
75
+ if (cfg.bootstrap.key && cfg.bootstrap.key !== canonical) {
76
+ delete env[cfg.bootstrap.key];
77
+ }
78
+ return env;
79
+ }
80
+
31
81
  /**
32
82
  * Obtain the one credential that unlocks the provider.
33
83
  *
@@ -83,9 +133,7 @@ function fromKeychain(key) {
83
133
  * @returns {Array<{key: string, value: string, note: string, projectId: string|null}>} Rows.
84
134
  */
85
135
  export function fetchRaw(cfg) {
86
- const env = { ...process.env };
87
- const token = bootstrapToken(cfg.bootstrap);
88
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
136
+ const env = providerEnv(cfg);
89
137
 
90
138
  if (cfg.provider === "env") {
91
139
  return Object.entries(process.env)
@@ -225,9 +273,7 @@ export function rawByKey(cfg, key) {
225
273
  * @param {string} value Replacement value.
226
274
  */
227
275
  export function writeSecret(cfg, id, value) {
228
- const env = { ...process.env };
229
- const token = bootstrapToken(cfg.bootstrap);
230
- if (cfg.bootstrap.key && token) env[cfg.bootstrap.key] = token;
276
+ const env = providerEnv(cfg);
231
277
 
232
278
  if (cfg.provider === "bitwarden") {
233
279
  run("bws", ["secret", "edit", id, "--value", value], env);
@@ -44,6 +44,10 @@ export function readAutomation(name, cwd = process.cwd()) {
44
44
  return {
45
45
  ...loop,
46
46
  repository: cfg.remoteEnv?.surfaces?.[loop.executionEnv]?.repository,
47
+ // Where this project keeps its bootstrap. A workstation serving several
48
+ // tenants gives each its own name, so the generated workflow must template
49
+ // it rather than assume the default.
50
+ bootstrapKey: cfg.secrets?.bootstrap?.key ?? "BWS_ACCESS_TOKEN",
47
51
  };
48
52
  }
49
53
 
@@ -70,6 +74,11 @@ export function readAutomation(name, cwd = process.cwd()) {
70
74
  */
71
75
  export function renderWorkflow(name, loop) {
72
76
  if (!loop.schedule) throw new Error(`automations["${name}"] has no schedule`);
77
+ // The repository secret and the environment variable carry the same name, so
78
+ // the value the workflow exports is the one `bootstrap.key` tells the resolver
79
+ // to look for. Translating it to the provider CLI's own variable is
80
+ // providers.mjs's job, not this workflow's.
81
+ const bootstrap = loop.bootstrapKey ?? "BWS_ACCESS_TOKEN";
73
82
  if (!loop.repository) {
74
83
  throw new Error(
75
84
  `automations["${name}"] targets executionEnv "${loop.executionEnv}", ` +
@@ -118,22 +127,22 @@ jobs:
118
127
  # seconds rather than after a toolchain download.
119
128
  - name: Check the bootstrap credential is configured
120
129
  env:
121
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
130
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
122
131
  run: |
123
132
  set -euo pipefail
124
- test -n "\${BWS_ACCESS_TOKEN}" || {
125
- echo "BWS_ACCESS_TOKEN is not configured for this repository" >&2
133
+ test -n "\${${bootstrap}}" || {
134
+ echo "${bootstrap} is not configured for this repository" >&2
126
135
  exit 1
127
136
  }
128
137
 
129
138
  - name: Prepare the toolchain and secrets
130
139
  env:
131
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
140
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
132
141
  run: bash scripts/lisa-remote-env/setup.sh
133
142
 
134
143
  - name: Run one ${name} cycle
135
144
  env:
136
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
145
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
137
146
  run: |
138
147
  set -euo pipefail
139
148
  node .claude/skills/lisa-remote-dispatch/scripts/dispatch.mjs \\
@@ -145,7 +154,7 @@ jobs:
145
154
  - name: Persist any credential rotation
146
155
  if: \${{ always() }}
147
156
  env:
148
- BWS_ACCESS_TOKEN: \${{ secrets.BWS_ACCESS_TOKEN }}
157
+ ${bootstrap}: \${{ secrets.${bootstrap} }}
149
158
  run: |
150
159
  set -euo pipefail
151
160
  node .claude/skills/lisa-secrets-access/scripts/rotate-secret.mjs leases
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.317.2",
3
+ "version": "2.317.3",
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": "2.317.2",
3
+ "version": "2.317.3",
4
4
  "description": "AWS CDK-specific Lisa plugin.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "2.317.2",
3
+ "version": "2.317.3",
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": "2.317.2",
3
+ "version": "2.317.3",
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": "2.317.2",
3
+ "version": "2.317.3",
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": "2.317.2",
3
+ "version": "2.317.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -82,6 +82,18 @@ ${XDG_CONFIG_HOME:-$HOME/.config}/<secrets.namespace>/ # dir 0700
82
82
 
83
83
  **`bootstrap`** — how to obtain the one credential that unlocks the rest. `sources` is ordered, environment first. This is the **only** credential permitted in a keychain; it is a bootstrap, not a cache.
84
84
 
85
+ `bootstrap.key` answers exactly one question: **where do we find it** — the environment variable name and keychain service to look under. It is *not* the name the provider CLI reads. That name is fixed by the vendor (`bws` reads `BWS_ACCESS_TOKEN` and nothing else) and lives in `PROVIDER_BOOTSTRAP_ENV` in `providers.mjs`, which translates one into the other before invoking the CLI.
86
+
87
+ Keeping these separate is what makes **one workstation serve several tenants**. Give each its own name and each project points at its own:
88
+
89
+ ```json
90
+ "bootstrap": { "sources": ["env", "keychain"], "key": "BWS_ACCESS_TOKEN_acme" }
91
+ ```
92
+
93
+ Conflating them worked only while every project used the default, where the two coincide. The first project to slug its key handed the CLI a variable it had never heard of, and every read and rotation failed with `Missing access token`. If you add a provider, add its canonical variable to that table — do not reach for `bootstrap.key`.
94
+
95
+ On the GitHub Actions surface the repository secret and the exported environment variable both carry the **configured** name, and the generated workflow templates it. Translating to the CLI's own variable stays in `providers.mjs`.
96
+
85
97
  **`require`** — optional. Omit it and every secret the provider grants is available, which is correct when the provider already scopes access per project. Present, it narrows to exactly those names **and asserts them**: a listed name that does not resolve is a startup error, not a late surprise.
86
98
 
87
99
  **`narrow`** — may only *narrow* the provider's own grant. There is deliberately no way to widen access from config; that boundary belongs to the provider.