@codyswann/lisa 4.74.5 → 4.74.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/core/nightly-e2e-guard-behavior-certificate.js +2 -2
- package/dist/core/upstream-evidence-manifest.js +2 -2
- package/package.json +4 -4
- package/plugins/lisa/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-cdk/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-agy/plugin.json +1 -1
- package/plugins/lisa-cdk-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cdk-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/plugins/lisa-expo/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-agy/plugin.json +1 -1
- package/plugins/lisa-expo-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-expo-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-agy/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-harper-fabric-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-agy/plugin.json +1 -1
- package/plugins/lisa-nestjs-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-nestjs-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-agy/plugin.json +1 -1
- package/plugins/lisa-openclaw-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-openclaw-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-agy/plugin.json +1 -1
- package/plugins/lisa-phaser-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-phaser-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-agy/plugin.json +1 -1
- package/plugins/lisa-rails-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-rails-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-agy/plugin.json +1 -1
- package/plugins/lisa-typescript-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-typescript-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki/.codex-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-agy/plugin.json +1 -1
- package/plugins/lisa-wiki-copilot/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-wiki-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +1 -0
- package/scripts/lisa-github-rulesets.sh +219 -0
|
@@ -41,9 +41,9 @@ export const NIGHTLY_E2E_GUARD_BEHAVIOR_CERTIFICATES = Object.freeze({
|
|
|
41
41
|
}),
|
|
42
42
|
d1b26caeaf65950e3b60ff41964d234c032c50470072e7643310b1f6722e34e6: Object.freeze({
|
|
43
43
|
contractVersion: "1.9.0",
|
|
44
|
-
packageVersions: Object.freeze(["4.74.
|
|
44
|
+
packageVersions: Object.freeze(["4.74.6"]),
|
|
45
45
|
provenances: Object.freeze([
|
|
46
|
-
"workspace package @codyswann/lisa@4.74.
|
|
46
|
+
"workspace package @codyswann/lisa@4.74.6 (typescript/copy-overwrite/scripts/check-nightly-e2e-health.mjs)",
|
|
47
47
|
]),
|
|
48
48
|
}),
|
|
49
49
|
dfd2fef680de5ef03e63075e8a61c62ca4d95cdfdff6201939b79c748d3397f6: Object.freeze({
|
|
@@ -815,7 +815,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
815
815
|
"plugins/src/base/skills/lisa-setup-automations/SKILL.md": "520dbf5c1735c038ef6607eafefb6914e4e24a434c6abb2b057c6065efbf6bb6",
|
|
816
816
|
"plugins/src/base/skills/lisa-setup-automations/scripts/generate-workflow.mjs": "c13e287c8beb6f847b03c93298b8a3fb211e65ce897e63692933377f8c4303f1",
|
|
817
817
|
"plugins/src/base/skills/lisa-setup-confluence/SKILL.md": "e92d762dbdaeae671c3e52dfe8e10d40a10b606b5e050589181a3898649faedc",
|
|
818
|
-
"plugins/src/base/skills/lisa-setup-github-repo/SKILL.md": "
|
|
818
|
+
"plugins/src/base/skills/lisa-setup-github-repo/SKILL.md": "bc9232d0609a27c66a1267221f6da33b50850145532da760f5501d37addd2cb0",
|
|
819
819
|
"plugins/src/base/skills/lisa-setup-github/SKILL.md": "75435e81130abd21c678d302b7a220da95a19868d122ca93168278628b200666",
|
|
820
820
|
"plugins/src/base/skills/lisa-setup-jira/SKILL.md": "261acaeb2923cc9238f468dcf986a0e1323a67e1e51d175b2c1b700dacab2fb8",
|
|
821
821
|
"plugins/src/base/skills/lisa-setup-kane/SKILL.md": "fe50024a4f896af01716f2435f57227b1f8de61803e80bf3e3c3247ec179b103",
|
|
@@ -1327,7 +1327,7 @@ export const UPSTREAM_EVIDENCE_MANIFEST = Object.freeze({
|
|
|
1327
1327
|
"scripts/lisa-github-environments.sh": "0a76e92f108519abaf3e29991299ec3b0db20ea533e69e6d2a53d802dba9c370",
|
|
1328
1328
|
"scripts/lisa-github-repo-settings.sh": "4afbccdfb08c62ba13afee42ec8561a28facf87c61f6f4b80000115328364d54",
|
|
1329
1329
|
"scripts/lisa-github-repo-setup.sh": "4717873e339b3e615d0bf22b4cb290b3575df5aba8c3639dc0d3bebc23abf651",
|
|
1330
|
-
"scripts/lisa-github-rulesets.sh": "
|
|
1330
|
+
"scripts/lisa-github-rulesets.sh": "183e3f80b09c3189c131e5b31b21d9349c17d1eded5d42742f49e91f058c75de",
|
|
1331
1331
|
"scripts/lisa-mutation.mjs": "74603e61f493d700318feb862f61ee1653430700b5edf232e70241ccdedac587",
|
|
1332
1332
|
"scripts/lisa-remote-env/materialized-env-authority.mjs": "2844475d7b407c4dccce4e0f500a3aaf5412f9f315c9173dd0ca22dd24497049",
|
|
1333
1333
|
"scripts/lisa-remote-env/session-start.sh": "2b40873f39b38e00b50285ca2c4c705ea018285fa457c3e1a985adbeaad6c26b",
|
package/package.json
CHANGED
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"qs": "^6.16.0"
|
|
190
190
|
},
|
|
191
191
|
"name": "@codyswann/lisa",
|
|
192
|
-
"version": "4.74.
|
|
192
|
+
"version": "4.74.6",
|
|
193
193
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
194
194
|
"main": "dist/index.js",
|
|
195
195
|
"exports": {
|
|
@@ -345,7 +345,7 @@
|
|
|
345
345
|
"test": "tests"
|
|
346
346
|
},
|
|
347
347
|
"types": "./dist/index.d.ts",
|
|
348
|
-
"lisaReleaseCommit": "
|
|
349
|
-
"gitHead": "
|
|
350
|
-
"lisaReleaseTag": "v4.74.
|
|
348
|
+
"lisaReleaseCommit": "00b41544bab8ef052550f94becac99518ccecb32",
|
|
349
|
+
"gitHead": "00b41544bab8ef052550f94becac99518ccecb32",
|
|
350
|
+
"lisaReleaseTag": "v4.74.6"
|
|
351
351
|
}
|
|
@@ -32,6 +32,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
32
32
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
33
33
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
34
34
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
35
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
35
36
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
36
37
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
37
38
|
```json
|
|
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
32
32
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
33
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
33
34
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
34
35
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
35
36
|
```json
|
|
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
32
32
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
33
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
33
34
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
34
35
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
35
36
|
```json
|
|
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
32
32
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
33
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
33
34
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
34
35
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
35
36
|
```json
|
|
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
32
32
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
33
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
33
34
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
34
35
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
35
36
|
```json
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.74.
|
|
3
|
+
"version": "4.74.6",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.74.
|
|
3
|
+
"version": "4.74.6",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, across Claude and Codex.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.74.
|
|
3
|
+
"version": "4.74.6",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.74.
|
|
3
|
+
"version": "4.74.6",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "lisa-openclaw",
|
|
3
|
-
"version": "4.74.
|
|
3
|
+
"version": "4.74.6",
|
|
4
4
|
"description": "Connect staff roles to Telegram or Slack via OpenClaw — facilitator/specialist hub-and-spoke routing and repo-coding topics, for Claude Code and Codex",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Cody Swann"
|
|
@@ -30,6 +30,7 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
30
30
|
- Repos without `.github/workflows/` get only app-based required checks — an Actions check that can never report would block every PR forever
|
|
31
31
|
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration. Each context the run newly makes blocking is printed as `+ now required: <context>`, and a ruleset that already satisfies its template reports `nothing to do` and is not sent at all, so a second run is visibly a no-op
|
|
32
32
|
- **Declaring a ruleset's required list makes it declarative.** By default a live-only required context is preserved, never dropped — including on the generated `base`, which gets no exemption: removing a protection by default, on a script invoked with `--yes`, is not something an operator ever opted into. Name a ruleset in `github.rulesets.requiredChecks` and the live list stops being unioned in, so removing an entry actually stops requiring it — printed as `- no longer required: <context>`. Dropping an `await` without that opt-in leaves the live context in place; `lisa-reconcile-policy.mjs` then reports it as EXTRA, and `--prune` is where the removal is asked for. The retired `addRequiredChecks` is still read and could only ever add
|
|
33
|
+
- **Bypass actors follow the same contract.** By default a live bypass actor the template does not name (e.g. an admin `RepositoryRole` someone added on purpose) is kept, the template's actors are added beside it, and a live actor's `bypass_mode` is never widened back to the template's. Declaring a ruleset's list — `github.rulesets.bypassActors.<ruleset name>` for a template ruleset, `policy.ruleset.bypass_actors` for the generated `base` — makes it exactly that list, which is the only way to remove one. Every change is printed by name, dry run included: `+ bypass actor added: <type> [<id>] (<mode>)`, a warning `- bypass actor removed: …`, and `~ bypass actor mode changed: <type> [<id>] (<old> -> <new>)`
|
|
33
34
|
3. **Deploy key** (`scripts/setup-deploy-key.sh --yes`) — write-access deploy key + `DEPLOY_KEY` secret, skipped when already configured. The `base` ruleset's `DeployKey: always` bypass is what lets CI version-bump pushes through protected branches.
|
|
34
35
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
35
36
|
```json
|
|
@@ -724,6 +724,209 @@ report_removed_contexts() {
|
|
|
724
724
|
done < <(ruleset_removed_contexts "$live" "$payload")
|
|
725
725
|
}
|
|
726
726
|
|
|
727
|
+
##############################################################################
|
|
728
|
+
# Bypass Actors
|
|
729
|
+
##############################################################################
|
|
730
|
+
|
|
731
|
+
# Bypass actors follow the same contract as required checks (#4297).
|
|
732
|
+
#
|
|
733
|
+
# The applier used to send each template's `bypass_actors` as the whole list,
|
|
734
|
+
# and GitHub REPLACES the list on update. So a bypass a consumer added on
|
|
735
|
+
# purpose — measured: an admin RepositoryRole on `nightly e2e health` — was
|
|
736
|
+
# deleted by every apply, reported as a routine "Applied", and no config key
|
|
737
|
+
# could keep it. Losing a bypass is not a tightening anyone asked for either:
|
|
738
|
+
# it can strand the only actor allowed to land an emergency fix.
|
|
739
|
+
#
|
|
740
|
+
# So, exactly like the required list:
|
|
741
|
+
# - by default the LIVE bypass list is preserved and the template's entries
|
|
742
|
+
# are added to it;
|
|
743
|
+
# - a ruleset whose bypass list is DECLARED gets precisely that list, which
|
|
744
|
+
# is the only way to remove an actor;
|
|
745
|
+
# - every actor added or removed, and every bypass_mode changed, is printed
|
|
746
|
+
# by name, on a dry run too; an undeclared live entry keeps its own mode.
|
|
747
|
+
#
|
|
748
|
+
# Declared per ruleset under `github.rulesets.bypassActors`:
|
|
749
|
+
# { "github": { "rulesets": { "bypassActors": {
|
|
750
|
+
# "nightly e2e health": [
|
|
751
|
+
# { "actor_type": "DeployKey", "actor_id": null, "bypass_mode": "always" }
|
|
752
|
+
# ] } } } }
|
|
753
|
+
#
|
|
754
|
+
# The generated `base` ruleset already has a declaration surface —
|
|
755
|
+
# `policy.ruleset.bypass_actors`, which lisa-ruleset-payload.mjs builds it
|
|
756
|
+
# from — so that is what makes base declarative. A second key for the same
|
|
757
|
+
# field would recreate the two-writers problem the generator exists to end.
|
|
758
|
+
|
|
759
|
+
# The declared bypass list for one ruleset, or "null" when none is declared.
|
|
760
|
+
config_bypass_actors() {
|
|
761
|
+
local project_path="$1"
|
|
762
|
+
local ruleset_name="$2"
|
|
763
|
+
local config="$project_path/.lisa.config.json"
|
|
764
|
+
|
|
765
|
+
if [[ ! -f "$config" ]]; then
|
|
766
|
+
echo "null"
|
|
767
|
+
return 0
|
|
768
|
+
fi
|
|
769
|
+
|
|
770
|
+
local filter='.github.rulesets.bypassActors[$name] // null'
|
|
771
|
+
if [[ "$ruleset_name" == "base" ]]; then
|
|
772
|
+
if jq -e '.github.rulesets.bypassActors.base != null' "$config" &> /dev/null; then
|
|
773
|
+
log_warning "github.rulesets.bypassActors.base is ignored — declare base's bypass list as policy.ruleset.bypass_actors" >&2
|
|
774
|
+
fi
|
|
775
|
+
filter='.policy.ruleset.bypass_actors // null'
|
|
776
|
+
fi
|
|
777
|
+
|
|
778
|
+
# An omitted bypass_mode is GitHub's default, `always`, and an omitted
|
|
779
|
+
# actor_id is null (DeployKey). Filled in here so a declaration that leaves
|
|
780
|
+
# them out compares equal to what GitHub stores, rather than reporting a
|
|
781
|
+
# change that never happens.
|
|
782
|
+
local declared
|
|
783
|
+
if ! declared=$(jq -c --arg name "$ruleset_name" \
|
|
784
|
+
"($filter) | if type == \"array\" then map(. + {actor_id: (.actor_id // null), bypass_mode: (.bypass_mode // \"always\")}) else null end" \
|
|
785
|
+
"$config" 2>/dev/null); then
|
|
786
|
+
log_warning ".lisa.config.json could not be parsed — ignoring github.rulesets overrides" >&2
|
|
787
|
+
echo "null"
|
|
788
|
+
return 0
|
|
789
|
+
fi
|
|
790
|
+
echo "$declared"
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
# Print why this ruleset's bypass declaration is unusable, or nothing when it
|
|
794
|
+
# is absent or well-formed. A malformed declaration must stop the ruleset: read
|
|
795
|
+
# as "nothing declared" it would quietly PRESERVE the actors the operator tried
|
|
796
|
+
# to remove, and the run would still report success.
|
|
797
|
+
bypass_declaration_error() {
|
|
798
|
+
local project_path="$1"
|
|
799
|
+
local ruleset_name="$2"
|
|
800
|
+
local config="$project_path/.lisa.config.json"
|
|
801
|
+
|
|
802
|
+
[[ -f "$config" ]] || return 0
|
|
803
|
+
|
|
804
|
+
local key="github.rulesets.bypassActors[\"$ruleset_name\"]"
|
|
805
|
+
local filter='.github.rulesets.bypassActors[$name]?'
|
|
806
|
+
if [[ "$ruleset_name" == "base" ]]; then
|
|
807
|
+
key="policy.ruleset.bypass_actors"
|
|
808
|
+
filter='.policy.ruleset.bypass_actors?'
|
|
809
|
+
fi
|
|
810
|
+
|
|
811
|
+
jq -r --arg name "$ruleset_name" --arg key "$key" "
|
|
812
|
+
($filter) as \$declared
|
|
813
|
+
| if \$declared == null then empty
|
|
814
|
+
elif (\$declared | type) != \"array\" then
|
|
815
|
+
\"\(\$key) must be a list of bypass actors, got \(\$declared | type)\"
|
|
816
|
+
elif any(\$declared[]; type != \"object\" or (.actor_type | type) != \"string\") then
|
|
817
|
+
\"\(\$key) has an entry without a string actor_type\"
|
|
818
|
+
elif any(\$declared[]; .actor_id != null and ((.actor_id | type) != \"number\" or .actor_id != (.actor_id | floor))) then
|
|
819
|
+
\"\(\$key) has an actor_id that is neither an integer nor null\"
|
|
820
|
+
elif any(\$declared[]; .bypass_mode as \$mode | \$mode != null and ([\"always\", \"pull_request\", \"exempt\"] | index(\$mode)) == null) then
|
|
821
|
+
\"\(\$key) has a bypass_mode other than always, pull_request or exempt\"
|
|
822
|
+
else empty end" "$config" 2>/dev/null \
|
|
823
|
+
|| echo "$key could not be validated — refusing to apply a declaration nobody checked"
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
# Replace a template's bypass list with the declared one, when there is one.
|
|
827
|
+
# `base` is skipped: its payload is already built from its declaration.
|
|
828
|
+
apply_config_bypass_actors() {
|
|
829
|
+
local json="$1"
|
|
830
|
+
local project_path="$2"
|
|
831
|
+
local ruleset_name="$3"
|
|
832
|
+
|
|
833
|
+
local declared
|
|
834
|
+
declared=$(config_bypass_actors "$project_path" "$ruleset_name")
|
|
835
|
+
if [[ "$declared" == "null" || "$ruleset_name" == "base" ]]; then
|
|
836
|
+
echo "$json"
|
|
837
|
+
return 0
|
|
838
|
+
fi
|
|
839
|
+
echo "$json" | jq --argjson declared "$declared" '.bypass_actors = $declared'
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
# Union the live bypass list back into the payload. Live entries are kept
|
|
843
|
+
# verbatim and in live order, so a ruleset that needs no change compares equal
|
|
844
|
+
# and nothing is sent; template-only actors are appended. A live entry the
|
|
845
|
+
# template also names keeps its LIVE bypass_mode: an operator who narrowed the
|
|
846
|
+
# admin bypass from `always` to `pull_request` made the same deliberate choice
|
|
847
|
+
# as one who added it, and taking the template's mode would widen it back
|
|
848
|
+
# without a word. Changing a mode, like removing an actor, needs a declaration.
|
|
849
|
+
preserve_live_bypass_actors() {
|
|
850
|
+
local live="$1"
|
|
851
|
+
local json="$2"
|
|
852
|
+
|
|
853
|
+
if [[ -z "$live" ]] || ! echo "$live" | jq -e 'has("bypass_actors")' &> /dev/null; then
|
|
854
|
+
echo "$json"
|
|
855
|
+
return 0
|
|
856
|
+
fi
|
|
857
|
+
|
|
858
|
+
echo "$json" | jq --argjson live "$live" '
|
|
859
|
+
def actor_key: [.actor_type, .actor_id];
|
|
860
|
+
(.bypass_actors // []) as $want
|
|
861
|
+
| ($live.bypass_actors // []) as $have
|
|
862
|
+
| .bypass_actors = (
|
|
863
|
+
$have
|
|
864
|
+
+ [ $want[]
|
|
865
|
+
| . as $entry
|
|
866
|
+
| select([ $have[] | actor_key ] | index([$entry | actor_key]) | not) ]
|
|
867
|
+
)'
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
# One line per bypass actor in $1's list that $2's list lacks, keyed by
|
|
871
|
+
# actor type and id. Formatted `<type> [<id>] (<bypass_mode>)`.
|
|
872
|
+
bypass_actors_missing_from() {
|
|
873
|
+
local from="$1"
|
|
874
|
+
local against="$2"
|
|
875
|
+
|
|
876
|
+
jq -r -n --argjson from "${from:-null}" --argjson against "${against:-null}" '
|
|
877
|
+
def actor_key: [.actor_type, .actor_id];
|
|
878
|
+
def actors: if . == null then [] else (.bypass_actors // []) end;
|
|
879
|
+
($against | actors | map(actor_key)) as $keys
|
|
880
|
+
| ($from | actors)[]
|
|
881
|
+
| select(actor_key as $k | ($keys | index([$k])) | not)
|
|
882
|
+
| "\(.actor_type)\(if .actor_id == null then "" else " \(.actor_id)" end) (\(.bypass_mode))"'
|
|
883
|
+
}
|
|
884
|
+
|
|
885
|
+
# One line per actor present in both lists whose bypass_mode differs,
|
|
886
|
+
# formatted `<type> [<id>] (<live mode> -> <payload mode>)`.
|
|
887
|
+
bypass_actor_mode_changes() {
|
|
888
|
+
local live="$1"
|
|
889
|
+
local payload="$2"
|
|
890
|
+
|
|
891
|
+
jq -r -n --argjson live "${live:-null}" --argjson want "${payload:-null}" '
|
|
892
|
+
def actor_key: [.actor_type, .actor_id];
|
|
893
|
+
def actors: if . == null then [] else (.bypass_actors // []) end;
|
|
894
|
+
($live | actors) as $have
|
|
895
|
+
| ($want | actors)[]
|
|
896
|
+
| . as $entry
|
|
897
|
+
| ([ $have[] | select(actor_key == ($entry | actor_key)) ] | first) as $old
|
|
898
|
+
| select($old != null and $old.bypass_mode != $entry.bypass_mode)
|
|
899
|
+
| "\(.actor_type)\(if .actor_id == null then "" else " \(.actor_id)" end) (\($old.bypass_mode) -> \(.bypass_mode))"'
|
|
900
|
+
}
|
|
901
|
+
|
|
902
|
+
# Name every bypass actor this run adds and removes. A removal is a warning:
|
|
903
|
+
# it can only happen when config declared the list, and it should still never
|
|
904
|
+
# read in the audit log as a routine reconciliation.
|
|
905
|
+
report_bypass_actor_changes() {
|
|
906
|
+
local live="$1"
|
|
907
|
+
local payload="$2"
|
|
908
|
+
|
|
909
|
+
# A live ruleset that does not disclose its bypass list cannot be preserved,
|
|
910
|
+
# and the payload's list will replace whatever is there.
|
|
911
|
+
if [[ -n "$live" ]] \
|
|
912
|
+
&& ! echo "$live" | jq -e 'has("bypass_actors")' &> /dev/null \
|
|
913
|
+
&& echo "$payload" | jq -e 'has("bypass_actors")' &> /dev/null; then
|
|
914
|
+
log_warning " GitHub did not disclose this ruleset's bypass actors — any it has will be replaced by the list sent (the token needs repository admin to read them)"
|
|
915
|
+
return 0
|
|
916
|
+
fi
|
|
917
|
+
|
|
918
|
+
local actor
|
|
919
|
+
while IFS= read -r actor; do
|
|
920
|
+
[[ -n "$actor" ]] && log_info " + bypass actor added: $actor"
|
|
921
|
+
done < <(bypass_actors_missing_from "$payload" "$live")
|
|
922
|
+
while IFS= read -r actor; do
|
|
923
|
+
[[ -n "$actor" ]] && log_warning " - bypass actor removed: $actor (the declared bypass list does not name it)"
|
|
924
|
+
done < <(bypass_actors_missing_from "$live" "$payload")
|
|
925
|
+
while IFS= read -r actor; do
|
|
926
|
+
[[ -n "$actor" ]] && log_warning " ~ bypass actor mode changed: $actor (the declared bypass list sets it)"
|
|
927
|
+
done < <(bypass_actor_mode_changes "$live" "$payload")
|
|
928
|
+
}
|
|
929
|
+
|
|
727
930
|
apply_ruleset() {
|
|
728
931
|
local repo="$1"
|
|
729
932
|
local template_file="$2"
|
|
@@ -736,12 +939,20 @@ apply_ruleset() {
|
|
|
736
939
|
local ruleset_name
|
|
737
940
|
ruleset_name=$(echo "$template_content" | jq -r '.name')
|
|
738
941
|
|
|
942
|
+
local bypass_error
|
|
943
|
+
bypass_error=$(bypass_declaration_error "$project_path" "$ruleset_name")
|
|
944
|
+
if [[ -n "$bypass_error" ]]; then
|
|
945
|
+
log_error "Not applying ruleset '$ruleset_name': $bypass_error"
|
|
946
|
+
return 1
|
|
947
|
+
fi
|
|
948
|
+
|
|
739
949
|
local clean_template
|
|
740
950
|
clean_template=$(strip_readonly_fields "$template_content")
|
|
741
951
|
clean_template=$(add_derived_run_gate_checks "$clean_template" "$project_path" "$ruleset_name")
|
|
742
952
|
clean_template=$(add_config_required_checks "$clean_template" "$project_path" "$ruleset_name")
|
|
743
953
|
clean_template=$(strip_actions_checks_if_no_workflows "$clean_template" "$project_path")
|
|
744
954
|
clean_template=$(strip_config_dropped_checks "$clean_template" "$project_path")
|
|
955
|
+
clean_template=$(apply_config_bypass_actors "$clean_template" "$project_path" "$ruleset_name")
|
|
745
956
|
|
|
746
957
|
# A template whose rules were entirely stripped has nothing to enforce here.
|
|
747
958
|
if [[ $(echo "$clean_template" | jq '(.rules // [1]) | length') -eq 0 ]]; then
|
|
@@ -779,6 +990,12 @@ apply_ruleset() {
|
|
|
779
990
|
else
|
|
780
991
|
clean_template=$(preserve_live_required_checks "$live" "$clean_template")
|
|
781
992
|
fi
|
|
993
|
+
# The same opt-in, for the bypass list: preserved unless declared.
|
|
994
|
+
if [[ "$(config_bypass_actors "$project_path" "$ruleset_name")" == "null" ]]; then
|
|
995
|
+
clean_template=$(preserve_live_bypass_actors "$live" "$clean_template")
|
|
996
|
+
else
|
|
997
|
+
log_verbose "Bypass actors for '$ruleset_name' are declared in .lisa.config.json — the live list is not preserved"
|
|
998
|
+
fi
|
|
782
999
|
fi
|
|
783
1000
|
|
|
784
1001
|
# Idempotence: a run that would change nothing must say so and send nothing,
|
|
@@ -797,6 +1014,7 @@ apply_ruleset() {
|
|
|
797
1014
|
fi
|
|
798
1015
|
report_added_contexts "$live" "$clean_template"
|
|
799
1016
|
report_removed_contexts "$live" "$clean_template"
|
|
1017
|
+
report_bypass_actor_changes "$live" "$clean_template"
|
|
800
1018
|
log_verbose "Template: $template_file"
|
|
801
1019
|
return 0
|
|
802
1020
|
fi
|
|
@@ -811,6 +1029,7 @@ apply_ruleset() {
|
|
|
811
1029
|
if apply_with_integration_fallback "$repo" "$ruleset_name" "$clean_template" "$existing_id"; then
|
|
812
1030
|
report_added_contexts "$live" "${APPLIED_PAYLOAD:-$clean_template}"
|
|
813
1031
|
report_removed_contexts "$live" "${APPLIED_PAYLOAD:-$clean_template}"
|
|
1032
|
+
report_bypass_actor_changes "$live" "${APPLIED_PAYLOAD:-$clean_template}"
|
|
814
1033
|
return 0
|
|
815
1034
|
fi
|
|
816
1035
|
log_error "Failed to apply ruleset '$ruleset_name'"
|