@codyswann/lisa 4.59.2 → 4.59.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 (71) hide show
  1. package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
  2. package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
  3. package/dist/core/upstream-evidence-manifest.js +15 -5
  4. package/dist/core/upstream-evidence-manifest.js.map +1 -1
  5. package/expo/create-only/.github/workflows/deploy.yml +31 -9
  6. package/package.json +4 -4
  7. package/plugins/lisa/.claude-plugin/plugin.json +1 -1
  8. package/plugins/lisa/.codex-plugin/plugin.json +1 -1
  9. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/SKILL.md +12 -0
  10. package/plugins/lisa/.codex-plugin/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  11. package/plugins/lisa/skills/lisa-secrets-access/SKILL.md +12 -0
  12. package/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  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/resolve-expo-token.mjs +90 -0
  16. package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
  17. package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
  18. package/plugins/lisa-cdk-agy/plugin.json +1 -1
  19. package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
  20. package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
  21. package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
  22. package/plugins/lisa-copilot/skills/lisa-secrets-access/SKILL.md +12 -0
  23. package/plugins/lisa-copilot/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  24. package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
  25. package/plugins/lisa-cursor/skills/lisa-secrets-access/SKILL.md +12 -0
  26. package/plugins/lisa-cursor/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  27. package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
  28. package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
  29. package/plugins/lisa-expo-agy/plugin.json +1 -1
  30. package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
  31. package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
  32. package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
  33. package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
  34. package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
  35. package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
  36. package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
  37. package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
  38. package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
  39. package/plugins/lisa-nestjs-agy/plugin.json +1 -1
  40. package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
  41. package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
  42. package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
  43. package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
  44. package/plugins/lisa-openclaw-agy/plugin.json +1 -1
  45. package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
  46. package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
  47. package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
  48. package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
  49. package/plugins/lisa-phaser-agy/plugin.json +1 -1
  50. package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
  51. package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
  52. package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
  53. package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
  54. package/plugins/lisa-rails-agy/plugin.json +1 -1
  55. package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
  56. package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
  57. package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
  58. package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
  59. package/plugins/lisa-typescript-agy/plugin.json +1 -1
  60. package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
  61. package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
  62. package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
  63. package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
  64. package/plugins/lisa-wiki-agy/plugin.json +1 -1
  65. package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
  66. package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
  67. package/plugins/src/base/skills/lisa-secrets-access/SKILL.md +12 -0
  68. package/plugins/src/base/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs +90 -0
  69. package/scripts/check-workflow-package-paths.mjs +37 -3
  70. package/scripts/lib/workflow-contract-probe.mjs +11 -0
  71. package/scripts/two-channel-couplings.json +1 -1
@@ -165,15 +165,23 @@ jobs:
165
165
  outputs:
166
166
  has_eas_setup: ${{ steps.check.outputs.has_eas_setup }}
167
167
  steps:
168
+ - uses: actions/checkout@v6
168
169
  - id: check
169
170
  run: |
170
- if [[ -z "${EXPO_TOKEN}" ]]; then
171
- echo "has_eas_setup=false" >> $GITHUB_OUTPUT
172
- else
173
- echo "has_eas_setup=true" >> $GITHUB_OUTPUT
171
+ set -eu
172
+ ready=false
173
+ if [ -n "$EXPO_TOKEN" ]; then
174
+ ready=true
175
+ elif [ -n "$LISA_SECRETS_BOOTSTRAP" ] && [ -f .lisa.config.json ]; then
176
+ provider=$(jq -r '.secrets.provider // "env"' .lisa.config.json)
177
+ if [ "$provider" != env ]; then ready=true; fi
174
178
  fi
179
+ # Eligibility is not authentication; each consuming job resolves its own token.
180
+ echo "has_eas_setup=$ready" >> "$GITHUB_OUTPUT"
175
181
  env:
176
182
  EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
183
+ # Map this to the existing tenant-scoped bootstrap secret if named differently.
184
+ LISA_SECRETS_BOOTSTRAP: ${{ secrets.BWS_ACCESS_TOKEN }}
177
185
 
178
186
  # Check if native app config has changed
179
187
  check_app_config_changes:
@@ -236,6 +244,7 @@ jobs:
236
244
  environment: ${{ needs.determine_environment.outputs.environment }}
237
245
  secrets:
238
246
  EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
247
+ LISA_SECRETS_BOOTSTRAP: ${{ secrets.BWS_ACCESS_TOKEN }}
239
248
 
240
249
  # EAS Deploy/Publish
241
250
  deploy:
@@ -316,15 +325,28 @@ jobs:
316
325
  uses: oven-sh/setup-bun@0c5077e51419868618aeaa5fe8019c62421857d6 # v2.2.0
317
326
  with:
318
327
  bun-version: '1.3.8'
319
- - name: Setup EAS
320
- uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
321
- with:
322
- eas-version: latest
323
- token: ${{ secrets.EXPO_TOKEN }}
324
328
  - name: Set Env
325
329
  run: echo "PUPPETEER_SKIP_DOWNLOAD=true" >> $GITHUB_ENV
326
330
  - name: Install dependencies
327
331
  run: bun install
332
+ - name: Resolve Expo token
333
+ env:
334
+ EXPO_TOKEN: ${{ secrets.EXPO_TOKEN }}
335
+ LISA_SECRETS_BOOTSTRAP: ${{ secrets.BWS_ACCESS_TOKEN }}
336
+ run: |
337
+ set -eu
338
+ if [ -n "$EXPO_TOKEN" ] || [ -z "$LISA_SECRETS_BOOTSTRAP" ]; then exit 0; fi
339
+ resolver=node_modules/@codyswann/lisa/plugins/lisa/skills/lisa-secrets-access/scripts/resolve-expo-token.mjs
340
+ if [ ! -f "$resolver" ]; then
341
+ echo "::error::Upgrade the project's @codyswann/lisa dependency before enabling provider-backed Expo authentication."
342
+ exit 1
343
+ fi
344
+ node "$resolver"
345
+ - name: Setup EAS
346
+ uses: expo/expo-github-action@c7b66a9c327a43a8fa7c0158e7f30d6040d2481e # v8.2.1
347
+ with:
348
+ eas-version: latest
349
+ token: ${{ secrets.EXPO_TOKEN || env.EXPO_TOKEN }}
328
350
  - name: Copy environment file
329
351
  uses: canastro/copy-file-action@ae66602ce7d214dbd2e298c1db67a81388755a0a # master (untagged, 2022-10-17)
330
352
  with:
package/package.json CHANGED
@@ -187,7 +187,7 @@
187
187
  "zod-validation-error": "^4.0.0"
188
188
  },
189
189
  "name": "@codyswann/lisa",
190
- "version": "4.59.2",
190
+ "version": "4.59.3",
191
191
  "description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
192
192
  "main": "dist/index.js",
193
193
  "exports": {
@@ -336,7 +336,7 @@
336
336
  "test": "tests"
337
337
  },
338
338
  "types": "./dist/index.d.ts",
339
- "lisaReleaseCommit": "e407fcde40b7a3d28b183f328b047d0ef0c422bd",
340
- "gitHead": "e407fcde40b7a3d28b183f328b047d0ef0c422bd",
341
- "lisaReleaseTag": "v4.59.2"
339
+ "lisaReleaseCommit": "f5a23dafc5d3db6f0cea17f755e8536a1229c67c",
340
+ "gitHead": "f5a23dafc5d3db6f0cea17f755e8536a1229c67c",
341
+ "lisaReleaseTag": "v4.59.3"
342
342
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.3",
4
4
  "description": "Universal governance: agents, skills, commands, hooks, and rules for all projects.",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,6 +188,18 @@ On the GitHub Actions surface the repository secret and the exported environment
188
188
 
189
189
  There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
190
190
 
191
+ ### Expo workflow pilot
192
+
193
+ The reusable Expo build workflow and the Expo deploy template can resolve `EXPO_TOKEN` from the project's configured provider. An explicit `EXPO_TOKEN` repository secret still takes precedence. Projects without a provider bootstrap keep their existing behavior.
194
+
195
+ For provider-backed Expo authentication, install a Lisa package containing `resolve-expo-token.mjs`, declare the provider and bootstrap in `.lisa.config.json`, and include `EXPO_TOKEN` if `secrets.require` is present. The workflow prepares only that provider's CLI through Lisa's existing installer, which uses checksum-pinned downloads for Bitwarden. Providers requiring manual setup must already be installed on the runner. The resolver respects the provider grant and `secrets.narrow`, masks the selected token, and writes only `EXPO_TOKEN` to the current job's environment. The bootstrap stays inside the resolver subprocess; the vault is not materialized into the job environment.
196
+
197
+ The build workflow's optional `LISA_SECRETS_BOOTSTRAP` secret input accepts the existing repository bootstrap secret. This input is an alias, not another stored credential: the resolver maps it to `bootstrap.key` inside its process. The Expo caller template forwards `secrets.BWS_ACCESS_TOKEN` by default. If your stored bootstrap has a different name, update its three references: the eligibility check, the reusable build call, and the deploy job's resolution step.
198
+
199
+ The eligibility check only establishes that an explicit token or a configured provider plus bootstrap is available. Each build or deploy job resolves its own token and must authenticate with Expo. A provider-backed caller using an older Lisa package fails with an upgrade instruction before invoking Expo.
200
+
201
+ The deploy caller is a **create-only** template. Existing projects must deliberately apply these caller changes after upgrading Lisa; a normal sync does not overwrite their workflow. This pilot does not convert other workflow credentials. `DEPLOY_KEY` remains available before dependency installation for checkout. Do not remove an existing Expo repository secret until a provider-only run has authenticated successfully.
202
+
191
203
  ## The exposure boundary
192
204
 
193
205
  **The provider's own scoping is the default allowlist.** The machine account's project grants *are* the permitted set; restating that as a list in config would duplicate a boundary the provider already enforces.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /** Resolve the Expo pilot's credential inside its GitHub Actions job. */
3
+ import { randomUUID } from "node:crypto";
4
+ import { appendFileSync } from "node:fs";
5
+
6
+ import { get, readConfig } from "./resolve-secret.mjs";
7
+ import { ENV_KEY } from "./providers.mjs";
8
+ import {
9
+ BIN_DIR,
10
+ ensureOnPath,
11
+ loadPinnedInstaller,
12
+ } from "../../lisa-setup-workstation/scripts/cli.mjs";
13
+ import {
14
+ installEntry,
15
+ planProvider,
16
+ resolveProvider,
17
+ } from "../../lisa-setup-workstation/scripts/workstation.mjs";
18
+
19
+ /** Provision only the selected provider, using the existing pinned installer. */
20
+ async function prepareProvider(name) {
21
+ const provider = resolveProvider(name);
22
+ let plan = planProvider(provider);
23
+ if (plan.action === "present") return;
24
+ ensureOnPath(BIN_DIR);
25
+ plan = planProvider(provider);
26
+ if (plan.action === "present") return;
27
+ if (plan.action !== "install") throw new Error("Provider needs setup");
28
+ const installed = installEntry({ ...provider, name: provider.binary }, plan, {
29
+ installPinned: await loadPinnedInstaller(),
30
+ });
31
+ if (installed.action !== "installed")
32
+ throw new Error("Provider setup failed");
33
+ }
34
+
35
+ /** Keep the bootstrap inside this subprocess; export only the selected value. */
36
+ async function resolveToken() {
37
+ const explicit = process.env.EXPO_TOKEN;
38
+ const bootstrap = process.env.LISA_SECRETS_BOOTSTRAP;
39
+ delete process.env.LISA_SECRETS_BOOTSTRAP;
40
+ if (explicit) return explicit;
41
+
42
+ process.env.LISA_SECRETS_SURFACE = "github-actions";
43
+ const cfg = readConfig();
44
+ if (cfg.provider === "env") return "";
45
+ if (
46
+ bootstrap &&
47
+ (!ENV_KEY.test(cfg.bootstrap.key ?? "") ||
48
+ cfg.bootstrap.key === "EXPO_TOKEN")
49
+ ) {
50
+ throw new Error("Invalid bootstrap key");
51
+ }
52
+ await prepareProvider(cfg.provider);
53
+ if (bootstrap) process.env[cfg.bootstrap.key] = bootstrap;
54
+ return get("EXPO_TOKEN", cfg);
55
+ }
56
+
57
+ if (process.argv.includes("--help")) {
58
+ // Safe to inspect without preparing a provider or reading/exporting a token.
59
+ console.log("Environment: EXPO_TOKEN LISA_SECRETS_BOOTSTRAP GITHUB_ENV");
60
+ console.log(
61
+ "An explicit EXPO_TOKEN takes precedence; otherwise resolve through the configured provider and export only EXPO_TOKEN to GITHUB_ENV."
62
+ );
63
+ } else {
64
+ let failureMessage = "GITHUB_ENV is required to export EXPO_TOKEN.";
65
+ try {
66
+ const destination = process.env.GITHUB_ENV;
67
+ if (!destination) throw new Error("GITHUB_ENV is required");
68
+ failureMessage =
69
+ "Unable to resolve EXPO_TOKEN. Check the configured provider, bootstrap input, and secrets.require; ensure the runner can install its provider CLI.";
70
+ const token = await resolveToken();
71
+ if (token) {
72
+ const masked = token
73
+ .replaceAll("%", "%25")
74
+ .replaceAll("\r", "%0D")
75
+ .replaceAll("\n", "%0A");
76
+ console.log(`::add-mask::${masked}`);
77
+ const delimiter = randomUUID();
78
+ failureMessage =
79
+ "Unable to write EXPO_TOKEN to GITHUB_ENV. Check the runner's environment file.";
80
+ appendFileSync(
81
+ destination,
82
+ `EXPO_TOKEN<<${delimiter}\n${token}\n${delimiter}\n`
83
+ );
84
+ }
85
+ } catch {
86
+ // Provider errors can contain captured subprocess output. Never echo it.
87
+ console.error(failureMessage);
88
+ process.exitCode = 1;
89
+ }
90
+ }
@@ -188,6 +188,18 @@ On the GitHub Actions surface the repository secret and the exported environment
188
188
 
189
189
  There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
190
190
 
191
+ ### Expo workflow pilot
192
+
193
+ The reusable Expo build workflow and the Expo deploy template can resolve `EXPO_TOKEN` from the project's configured provider. An explicit `EXPO_TOKEN` repository secret still takes precedence. Projects without a provider bootstrap keep their existing behavior.
194
+
195
+ For provider-backed Expo authentication, install a Lisa package containing `resolve-expo-token.mjs`, declare the provider and bootstrap in `.lisa.config.json`, and include `EXPO_TOKEN` if `secrets.require` is present. The workflow prepares only that provider's CLI through Lisa's existing installer, which uses checksum-pinned downloads for Bitwarden. Providers requiring manual setup must already be installed on the runner. The resolver respects the provider grant and `secrets.narrow`, masks the selected token, and writes only `EXPO_TOKEN` to the current job's environment. The bootstrap stays inside the resolver subprocess; the vault is not materialized into the job environment.
196
+
197
+ The build workflow's optional `LISA_SECRETS_BOOTSTRAP` secret input accepts the existing repository bootstrap secret. This input is an alias, not another stored credential: the resolver maps it to `bootstrap.key` inside its process. The Expo caller template forwards `secrets.BWS_ACCESS_TOKEN` by default. If your stored bootstrap has a different name, update its three references: the eligibility check, the reusable build call, and the deploy job's resolution step.
198
+
199
+ The eligibility check only establishes that an explicit token or a configured provider plus bootstrap is available. Each build or deploy job resolves its own token and must authenticate with Expo. A provider-backed caller using an older Lisa package fails with an upgrade instruction before invoking Expo.
200
+
201
+ The deploy caller is a **create-only** template. Existing projects must deliberately apply these caller changes after upgrading Lisa; a normal sync does not overwrite their workflow. This pilot does not convert other workflow credentials. `DEPLOY_KEY` remains available before dependency installation for checkout. Do not remove an existing Expo repository secret until a provider-only run has authenticated successfully.
202
+
191
203
  ## The exposure boundary
192
204
 
193
205
  **The provider's own scoping is the default allowlist.** The machine account's project grants *are* the permitted set; restating that as a list in config would duplicate a boundary the provider already enforces.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /** Resolve the Expo pilot's credential inside its GitHub Actions job. */
3
+ import { randomUUID } from "node:crypto";
4
+ import { appendFileSync } from "node:fs";
5
+
6
+ import { get, readConfig } from "./resolve-secret.mjs";
7
+ import { ENV_KEY } from "./providers.mjs";
8
+ import {
9
+ BIN_DIR,
10
+ ensureOnPath,
11
+ loadPinnedInstaller,
12
+ } from "../../lisa-setup-workstation/scripts/cli.mjs";
13
+ import {
14
+ installEntry,
15
+ planProvider,
16
+ resolveProvider,
17
+ } from "../../lisa-setup-workstation/scripts/workstation.mjs";
18
+
19
+ /** Provision only the selected provider, using the existing pinned installer. */
20
+ async function prepareProvider(name) {
21
+ const provider = resolveProvider(name);
22
+ let plan = planProvider(provider);
23
+ if (plan.action === "present") return;
24
+ ensureOnPath(BIN_DIR);
25
+ plan = planProvider(provider);
26
+ if (plan.action === "present") return;
27
+ if (plan.action !== "install") throw new Error("Provider needs setup");
28
+ const installed = installEntry({ ...provider, name: provider.binary }, plan, {
29
+ installPinned: await loadPinnedInstaller(),
30
+ });
31
+ if (installed.action !== "installed")
32
+ throw new Error("Provider setup failed");
33
+ }
34
+
35
+ /** Keep the bootstrap inside this subprocess; export only the selected value. */
36
+ async function resolveToken() {
37
+ const explicit = process.env.EXPO_TOKEN;
38
+ const bootstrap = process.env.LISA_SECRETS_BOOTSTRAP;
39
+ delete process.env.LISA_SECRETS_BOOTSTRAP;
40
+ if (explicit) return explicit;
41
+
42
+ process.env.LISA_SECRETS_SURFACE = "github-actions";
43
+ const cfg = readConfig();
44
+ if (cfg.provider === "env") return "";
45
+ if (
46
+ bootstrap &&
47
+ (!ENV_KEY.test(cfg.bootstrap.key ?? "") ||
48
+ cfg.bootstrap.key === "EXPO_TOKEN")
49
+ ) {
50
+ throw new Error("Invalid bootstrap key");
51
+ }
52
+ await prepareProvider(cfg.provider);
53
+ if (bootstrap) process.env[cfg.bootstrap.key] = bootstrap;
54
+ return get("EXPO_TOKEN", cfg);
55
+ }
56
+
57
+ if (process.argv.includes("--help")) {
58
+ // Safe to inspect without preparing a provider or reading/exporting a token.
59
+ console.log("Environment: EXPO_TOKEN LISA_SECRETS_BOOTSTRAP GITHUB_ENV");
60
+ console.log(
61
+ "An explicit EXPO_TOKEN takes precedence; otherwise resolve through the configured provider and export only EXPO_TOKEN to GITHUB_ENV."
62
+ );
63
+ } else {
64
+ let failureMessage = "GITHUB_ENV is required to export EXPO_TOKEN.";
65
+ try {
66
+ const destination = process.env.GITHUB_ENV;
67
+ if (!destination) throw new Error("GITHUB_ENV is required");
68
+ failureMessage =
69
+ "Unable to resolve EXPO_TOKEN. Check the configured provider, bootstrap input, and secrets.require; ensure the runner can install its provider CLI.";
70
+ const token = await resolveToken();
71
+ if (token) {
72
+ const masked = token
73
+ .replaceAll("%", "%25")
74
+ .replaceAll("\r", "%0D")
75
+ .replaceAll("\n", "%0A");
76
+ console.log(`::add-mask::${masked}`);
77
+ const delimiter = randomUUID();
78
+ failureMessage =
79
+ "Unable to write EXPO_TOKEN to GITHUB_ENV. Check the runner's environment file.";
80
+ appendFileSync(
81
+ destination,
82
+ `EXPO_TOKEN<<${delimiter}\n${token}\n${delimiter}\n`
83
+ );
84
+ }
85
+ } catch {
86
+ // Provider errors can contain captured subprocess output. Never echo it.
87
+ console.error(failureMessage);
88
+ process.exitCode = 1;
89
+ }
90
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.2",
3
+ "version": "4.59.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,6 +188,18 @@ On the GitHub Actions surface the repository secret and the exported environment
188
188
 
189
189
  There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
190
190
 
191
+ ### Expo workflow pilot
192
+
193
+ The reusable Expo build workflow and the Expo deploy template can resolve `EXPO_TOKEN` from the project's configured provider. An explicit `EXPO_TOKEN` repository secret still takes precedence. Projects without a provider bootstrap keep their existing behavior.
194
+
195
+ For provider-backed Expo authentication, install a Lisa package containing `resolve-expo-token.mjs`, declare the provider and bootstrap in `.lisa.config.json`, and include `EXPO_TOKEN` if `secrets.require` is present. The workflow prepares only that provider's CLI through Lisa's existing installer, which uses checksum-pinned downloads for Bitwarden. Providers requiring manual setup must already be installed on the runner. The resolver respects the provider grant and `secrets.narrow`, masks the selected token, and writes only `EXPO_TOKEN` to the current job's environment. The bootstrap stays inside the resolver subprocess; the vault is not materialized into the job environment.
196
+
197
+ The build workflow's optional `LISA_SECRETS_BOOTSTRAP` secret input accepts the existing repository bootstrap secret. This input is an alias, not another stored credential: the resolver maps it to `bootstrap.key` inside its process. The Expo caller template forwards `secrets.BWS_ACCESS_TOKEN` by default. If your stored bootstrap has a different name, update its three references: the eligibility check, the reusable build call, and the deploy job's resolution step.
198
+
199
+ The eligibility check only establishes that an explicit token or a configured provider plus bootstrap is available. Each build or deploy job resolves its own token and must authenticate with Expo. A provider-backed caller using an older Lisa package fails with an upgrade instruction before invoking Expo.
200
+
201
+ The deploy caller is a **create-only** template. Existing projects must deliberately apply these caller changes after upgrading Lisa; a normal sync does not overwrite their workflow. This pilot does not convert other workflow credentials. `DEPLOY_KEY` remains available before dependency installation for checkout. Do not remove an existing Expo repository secret until a provider-only run has authenticated successfully.
202
+
191
203
  ## The exposure boundary
192
204
 
193
205
  **The provider's own scoping is the default allowlist.** The machine account's project grants *are* the permitted set; restating that as a list in config would duplicate a boundary the provider already enforces.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /** Resolve the Expo pilot's credential inside its GitHub Actions job. */
3
+ import { randomUUID } from "node:crypto";
4
+ import { appendFileSync } from "node:fs";
5
+
6
+ import { get, readConfig } from "./resolve-secret.mjs";
7
+ import { ENV_KEY } from "./providers.mjs";
8
+ import {
9
+ BIN_DIR,
10
+ ensureOnPath,
11
+ loadPinnedInstaller,
12
+ } from "../../lisa-setup-workstation/scripts/cli.mjs";
13
+ import {
14
+ installEntry,
15
+ planProvider,
16
+ resolveProvider,
17
+ } from "../../lisa-setup-workstation/scripts/workstation.mjs";
18
+
19
+ /** Provision only the selected provider, using the existing pinned installer. */
20
+ async function prepareProvider(name) {
21
+ const provider = resolveProvider(name);
22
+ let plan = planProvider(provider);
23
+ if (plan.action === "present") return;
24
+ ensureOnPath(BIN_DIR);
25
+ plan = planProvider(provider);
26
+ if (plan.action === "present") return;
27
+ if (plan.action !== "install") throw new Error("Provider needs setup");
28
+ const installed = installEntry({ ...provider, name: provider.binary }, plan, {
29
+ installPinned: await loadPinnedInstaller(),
30
+ });
31
+ if (installed.action !== "installed")
32
+ throw new Error("Provider setup failed");
33
+ }
34
+
35
+ /** Keep the bootstrap inside this subprocess; export only the selected value. */
36
+ async function resolveToken() {
37
+ const explicit = process.env.EXPO_TOKEN;
38
+ const bootstrap = process.env.LISA_SECRETS_BOOTSTRAP;
39
+ delete process.env.LISA_SECRETS_BOOTSTRAP;
40
+ if (explicit) return explicit;
41
+
42
+ process.env.LISA_SECRETS_SURFACE = "github-actions";
43
+ const cfg = readConfig();
44
+ if (cfg.provider === "env") return "";
45
+ if (
46
+ bootstrap &&
47
+ (!ENV_KEY.test(cfg.bootstrap.key ?? "") ||
48
+ cfg.bootstrap.key === "EXPO_TOKEN")
49
+ ) {
50
+ throw new Error("Invalid bootstrap key");
51
+ }
52
+ await prepareProvider(cfg.provider);
53
+ if (bootstrap) process.env[cfg.bootstrap.key] = bootstrap;
54
+ return get("EXPO_TOKEN", cfg);
55
+ }
56
+
57
+ if (process.argv.includes("--help")) {
58
+ // Safe to inspect without preparing a provider or reading/exporting a token.
59
+ console.log("Environment: EXPO_TOKEN LISA_SECRETS_BOOTSTRAP GITHUB_ENV");
60
+ console.log(
61
+ "An explicit EXPO_TOKEN takes precedence; otherwise resolve through the configured provider and export only EXPO_TOKEN to GITHUB_ENV."
62
+ );
63
+ } else {
64
+ let failureMessage = "GITHUB_ENV is required to export EXPO_TOKEN.";
65
+ try {
66
+ const destination = process.env.GITHUB_ENV;
67
+ if (!destination) throw new Error("GITHUB_ENV is required");
68
+ failureMessage =
69
+ "Unable to resolve EXPO_TOKEN. Check the configured provider, bootstrap input, and secrets.require; ensure the runner can install its provider CLI.";
70
+ const token = await resolveToken();
71
+ if (token) {
72
+ const masked = token
73
+ .replaceAll("%", "%25")
74
+ .replaceAll("\r", "%0D")
75
+ .replaceAll("\n", "%0A");
76
+ console.log(`::add-mask::${masked}`);
77
+ const delimiter = randomUUID();
78
+ failureMessage =
79
+ "Unable to write EXPO_TOKEN to GITHUB_ENV. Check the runner's environment file.";
80
+ appendFileSync(
81
+ destination,
82
+ `EXPO_TOKEN<<${delimiter}\n${token}\n${delimiter}\n`
83
+ );
84
+ }
85
+ } catch {
86
+ // Provider errors can contain captured subprocess output. Never echo it.
87
+ console.error(failureMessage);
88
+ process.exitCode = 1;
89
+ }
90
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa-cdk",
3
- "version": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.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": "4.59.2",
3
+ "version": "4.59.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,6 +188,18 @@ On the GitHub Actions surface the repository secret and the exported environment
188
188
 
189
189
  There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
190
190
 
191
+ ### Expo workflow pilot
192
+
193
+ The reusable Expo build workflow and the Expo deploy template can resolve `EXPO_TOKEN` from the project's configured provider. An explicit `EXPO_TOKEN` repository secret still takes precedence. Projects without a provider bootstrap keep their existing behavior.
194
+
195
+ For provider-backed Expo authentication, install a Lisa package containing `resolve-expo-token.mjs`, declare the provider and bootstrap in `.lisa.config.json`, and include `EXPO_TOKEN` if `secrets.require` is present. The workflow prepares only that provider's CLI through Lisa's existing installer, which uses checksum-pinned downloads for Bitwarden. Providers requiring manual setup must already be installed on the runner. The resolver respects the provider grant and `secrets.narrow`, masks the selected token, and writes only `EXPO_TOKEN` to the current job's environment. The bootstrap stays inside the resolver subprocess; the vault is not materialized into the job environment.
196
+
197
+ The build workflow's optional `LISA_SECRETS_BOOTSTRAP` secret input accepts the existing repository bootstrap secret. This input is an alias, not another stored credential: the resolver maps it to `bootstrap.key` inside its process. The Expo caller template forwards `secrets.BWS_ACCESS_TOKEN` by default. If your stored bootstrap has a different name, update its three references: the eligibility check, the reusable build call, and the deploy job's resolution step.
198
+
199
+ The eligibility check only establishes that an explicit token or a configured provider plus bootstrap is available. Each build or deploy job resolves its own token and must authenticate with Expo. A provider-backed caller using an older Lisa package fails with an upgrade instruction before invoking Expo.
200
+
201
+ The deploy caller is a **create-only** template. Existing projects must deliberately apply these caller changes after upgrading Lisa; a normal sync does not overwrite their workflow. This pilot does not convert other workflow credentials. `DEPLOY_KEY` remains available before dependency installation for checkout. Do not remove an existing Expo repository secret until a provider-only run has authenticated successfully.
202
+
191
203
  ## The exposure boundary
192
204
 
193
205
  **The provider's own scoping is the default allowlist.** The machine account's project grants *are* the permitted set; restating that as a list in config would duplicate a boundary the provider already enforces.
@@ -0,0 +1,90 @@
1
+ #!/usr/bin/env node
2
+ /** Resolve the Expo pilot's credential inside its GitHub Actions job. */
3
+ import { randomUUID } from "node:crypto";
4
+ import { appendFileSync } from "node:fs";
5
+
6
+ import { get, readConfig } from "./resolve-secret.mjs";
7
+ import { ENV_KEY } from "./providers.mjs";
8
+ import {
9
+ BIN_DIR,
10
+ ensureOnPath,
11
+ loadPinnedInstaller,
12
+ } from "../../lisa-setup-workstation/scripts/cli.mjs";
13
+ import {
14
+ installEntry,
15
+ planProvider,
16
+ resolveProvider,
17
+ } from "../../lisa-setup-workstation/scripts/workstation.mjs";
18
+
19
+ /** Provision only the selected provider, using the existing pinned installer. */
20
+ async function prepareProvider(name) {
21
+ const provider = resolveProvider(name);
22
+ let plan = planProvider(provider);
23
+ if (plan.action === "present") return;
24
+ ensureOnPath(BIN_DIR);
25
+ plan = planProvider(provider);
26
+ if (plan.action === "present") return;
27
+ if (plan.action !== "install") throw new Error("Provider needs setup");
28
+ const installed = installEntry({ ...provider, name: provider.binary }, plan, {
29
+ installPinned: await loadPinnedInstaller(),
30
+ });
31
+ if (installed.action !== "installed")
32
+ throw new Error("Provider setup failed");
33
+ }
34
+
35
+ /** Keep the bootstrap inside this subprocess; export only the selected value. */
36
+ async function resolveToken() {
37
+ const explicit = process.env.EXPO_TOKEN;
38
+ const bootstrap = process.env.LISA_SECRETS_BOOTSTRAP;
39
+ delete process.env.LISA_SECRETS_BOOTSTRAP;
40
+ if (explicit) return explicit;
41
+
42
+ process.env.LISA_SECRETS_SURFACE = "github-actions";
43
+ const cfg = readConfig();
44
+ if (cfg.provider === "env") return "";
45
+ if (
46
+ bootstrap &&
47
+ (!ENV_KEY.test(cfg.bootstrap.key ?? "") ||
48
+ cfg.bootstrap.key === "EXPO_TOKEN")
49
+ ) {
50
+ throw new Error("Invalid bootstrap key");
51
+ }
52
+ await prepareProvider(cfg.provider);
53
+ if (bootstrap) process.env[cfg.bootstrap.key] = bootstrap;
54
+ return get("EXPO_TOKEN", cfg);
55
+ }
56
+
57
+ if (process.argv.includes("--help")) {
58
+ // Safe to inspect without preparing a provider or reading/exporting a token.
59
+ console.log("Environment: EXPO_TOKEN LISA_SECRETS_BOOTSTRAP GITHUB_ENV");
60
+ console.log(
61
+ "An explicit EXPO_TOKEN takes precedence; otherwise resolve through the configured provider and export only EXPO_TOKEN to GITHUB_ENV."
62
+ );
63
+ } else {
64
+ let failureMessage = "GITHUB_ENV is required to export EXPO_TOKEN.";
65
+ try {
66
+ const destination = process.env.GITHUB_ENV;
67
+ if (!destination) throw new Error("GITHUB_ENV is required");
68
+ failureMessage =
69
+ "Unable to resolve EXPO_TOKEN. Check the configured provider, bootstrap input, and secrets.require; ensure the runner can install its provider CLI.";
70
+ const token = await resolveToken();
71
+ if (token) {
72
+ const masked = token
73
+ .replaceAll("%", "%25")
74
+ .replaceAll("\r", "%0D")
75
+ .replaceAll("\n", "%0A");
76
+ console.log(`::add-mask::${masked}`);
77
+ const delimiter = randomUUID();
78
+ failureMessage =
79
+ "Unable to write EXPO_TOKEN to GITHUB_ENV. Check the runner's environment file.";
80
+ appendFileSync(
81
+ destination,
82
+ `EXPO_TOKEN<<${delimiter}\n${token}\n${delimiter}\n`
83
+ );
84
+ }
85
+ } catch {
86
+ // Provider errors can contain captured subprocess output. Never echo it.
87
+ console.error(failureMessage);
88
+ process.exitCode = 1;
89
+ }
90
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "lisa",
3
- "version": "4.59.2",
3
+ "version": "4.59.3",
4
4
  "description": "Universal governance — agents, skills, commands, hooks, and rules for all projects",
5
5
  "author": {
6
6
  "name": "Cody Swann"
@@ -188,6 +188,18 @@ On the GitHub Actions surface the repository secret and the exported environment
188
188
 
189
189
  There is no map of secret IDs, deliberately. Copying an ID per secret is the same duplication in a smaller costume, and lookup is by name.
190
190
 
191
+ ### Expo workflow pilot
192
+
193
+ The reusable Expo build workflow and the Expo deploy template can resolve `EXPO_TOKEN` from the project's configured provider. An explicit `EXPO_TOKEN` repository secret still takes precedence. Projects without a provider bootstrap keep their existing behavior.
194
+
195
+ For provider-backed Expo authentication, install a Lisa package containing `resolve-expo-token.mjs`, declare the provider and bootstrap in `.lisa.config.json`, and include `EXPO_TOKEN` if `secrets.require` is present. The workflow prepares only that provider's CLI through Lisa's existing installer, which uses checksum-pinned downloads for Bitwarden. Providers requiring manual setup must already be installed on the runner. The resolver respects the provider grant and `secrets.narrow`, masks the selected token, and writes only `EXPO_TOKEN` to the current job's environment. The bootstrap stays inside the resolver subprocess; the vault is not materialized into the job environment.
196
+
197
+ The build workflow's optional `LISA_SECRETS_BOOTSTRAP` secret input accepts the existing repository bootstrap secret. This input is an alias, not another stored credential: the resolver maps it to `bootstrap.key` inside its process. The Expo caller template forwards `secrets.BWS_ACCESS_TOKEN` by default. If your stored bootstrap has a different name, update its three references: the eligibility check, the reusable build call, and the deploy job's resolution step.
198
+
199
+ The eligibility check only establishes that an explicit token or a configured provider plus bootstrap is available. Each build or deploy job resolves its own token and must authenticate with Expo. A provider-backed caller using an older Lisa package fails with an upgrade instruction before invoking Expo.
200
+
201
+ The deploy caller is a **create-only** template. Existing projects must deliberately apply these caller changes after upgrading Lisa; a normal sync does not overwrite their workflow. This pilot does not convert other workflow credentials. `DEPLOY_KEY` remains available before dependency installation for checkout. Do not remove an existing Expo repository secret until a provider-only run has authenticated successfully.
202
+
191
203
  ## The exposure boundary
192
204
 
193
205
  **The provider's own scoping is the default allowlist.** The machine account's project grants *are* the permitted set; restating that as a list in config would duplicate a boundary the provider already enforces.