@codyswann/lisa 3.62.0 → 3.63.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/all/copy-overwrite/scripts/lisa-gates.mjs +167 -5
- package/all/copy-overwrite/scripts/lisa-reconcile-policy.mjs +325 -32
- package/dist/cli/gate-report-templates.js +1 -1
- package/dist/core/lisa-owned-hash-ledger.d.ts.map +1 -1
- package/dist/core/lisa-owned-hash-ledger.js +6 -0
- package/dist/core/lisa-owned-hash-ledger.js.map +1 -1
- package/dist/core/upstream-evidence-manifest.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +14 -9
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/dist/health/ruleset-inspection.d.ts.map +1 -1
- package/dist/health/ruleset-inspection.js +73 -6
- package/dist/health/ruleset-inspection.js.map +1 -1
- package/package.json +2 -1
- 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-doctor/SKILL.md +46 -7
- package/plugins/lisa/.codex-plugin/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa-agy/skills/lisa-setup-github-repo/SKILL.md +7 -3
- 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-doctor/SKILL.md +46 -7
- package/plugins/lisa-copilot/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/skills/lisa-doctor/SKILL.md +46 -7
- package/plugins/lisa-cursor/skills/lisa-setup-github-repo/SKILL.md +7 -3
- 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-doctor/SKILL.md +46 -7
- package/plugins/src/base/skills/lisa-setup-github-repo/SKILL.md +7 -3
- package/scripts/check-required-check-promotions.mjs +69 -4
- package/scripts/lisa-github-repo-setup.sh +64 -5
- package/scripts/lisa-github-rulesets.sh +201 -21
- package/scripts/lisa-ruleset-payload.mjs +376 -0
- package/all/github-rulesets/base.json +0 -64
|
@@ -501,7 +501,11 @@ remedy in that vocabulary removes a required context — the type has no such me
|
|
|
501
501
|
guarantee holds for every caller rather than for the careful ones. A surface this run could not
|
|
502
502
|
read is reported as unproven and is never a match.
|
|
503
503
|
|
|
504
|
-
|
|
504
|
+
Reconciliation is **wired**, not something an agent has to remember to run: it is step 5 of
|
|
505
|
+
`scripts/lisa-github-repo-setup.sh`, alongside the settings baseline and the rulesets themselves,
|
|
506
|
+
and it is exposed as `npm run policy:reconcile`. Until #2917 its only callers were the code blocks
|
|
507
|
+
below — prose in this very file — so the declared configuration converged when somebody happened to
|
|
508
|
+
read a document. Run it by hand for the flags:
|
|
505
509
|
|
|
506
510
|
```sh
|
|
507
511
|
node scripts/lisa-reconcile-policy.mjs --dry-run # read-only: what would change
|
|
@@ -529,12 +533,41 @@ drift that was *measured*, and here nothing was. Map it to doctor's `WARN`/`FAIL
|
|
|
529
533
|
as any other unavailable check surface: never `PASS`.
|
|
530
534
|
|
|
531
535
|
**2. An `EXTRA` context is reported, never removed.** Lisa does not own the whole required list.
|
|
532
|
-
`
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
536
|
+
Under `repair` the script ADDS what is missing and leaves what is extra alone, naming each one.
|
|
537
|
+
Removing them requires `--prune`, and the right way to clear the list is one at a time: each EXTRA
|
|
538
|
+
context is either an app to keep, or a check that belongs in `.lisa.config.json` — decide which
|
|
539
|
+
before pruning anything.
|
|
540
|
+
|
|
541
|
+
A context posted by an external app no longer HAS to be EXTRA. `SonarCloud Code Analysis`,
|
|
542
|
+
`GitGuardian Security Checks`, and `CodeRabbit` are declarable two ways, and either one makes the
|
|
543
|
+
reconciler treat them as MATCHED rather than as a permanent false alarm:
|
|
544
|
+
|
|
545
|
+
```json
|
|
546
|
+
"gates": {
|
|
547
|
+
"credential-leakage": {
|
|
548
|
+
"pull-request": {
|
|
549
|
+
"level": "required",
|
|
550
|
+
"await": "GitGuardian Security Checks",
|
|
551
|
+
"posted_by": 46505
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
},
|
|
555
|
+
"github": {
|
|
556
|
+
"rulesets": {
|
|
557
|
+
"requiredChecks": {
|
|
558
|
+
"quality checks": [{ "context": "🧩 Plugin artifacts match source" }]
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
An `await` names the gate the signal PROVES, so declaring nothing requires nothing — which is how a
|
|
565
|
+
project that proves credential leakage with a different scanner says so. `posted_by` is the GitHub
|
|
566
|
+
App id allowed to post that context; omit it and the context is required unpinned, GitHub's "any
|
|
567
|
+
source". `requiredChecks` is for a repository-specific check that no gate models; it replaces the
|
|
568
|
+
additive-only `addRequiredChecks`, which is still read but can never express "stop requiring this".
|
|
569
|
+
Both surfaces name the ruleset a repair writes to, so `--ruleset` is needed only for a
|
|
570
|
+
gate-DERIVED context on a repository whose rulesets are ambiguous.
|
|
538
571
|
|
|
539
572
|
**3. Keep both names during a rename.** `--previous=<old label>` requires the old and the new
|
|
540
573
|
context simultaneously for one release. Without it the reconciliation reports the still-live old
|
|
@@ -549,6 +582,12 @@ names that script rather than reshaping rules it did not build. When more than o
|
|
|
549
582
|
status checks it refuses to guess which owns the derived contexts and asks for `--ruleset=<name>`,
|
|
550
583
|
because writing to the wrong one enforces a context under a different ref-name condition.
|
|
551
584
|
|
|
585
|
+
That rule construction is itself config-driven now. `all/github-rulesets/base.json` is deleted; the
|
|
586
|
+
`base` ruleset is generated per project from `policy` — including `policy.ruleset.enforcement`, the
|
|
587
|
+
`policy.ruleset.include_refs` / `exclude_refs` conditions, `policy.ruleset.bypass_actors`, and
|
|
588
|
+
`policy.review.required_approving_review_count`, none of which config could express while the
|
|
589
|
+
template owned them.
|
|
590
|
+
|
|
552
591
|
## Secrets configuration
|
|
553
592
|
|
|
554
593
|
Run the secrets health checks through the skill that owns the contract, rather than reimplementing
|
|
@@ -23,12 +23,13 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
23
23
|
- Default branch set to the lowest-tier environment branch that exists (dev → staging → main); override with `github.settings.default_branch`
|
|
24
24
|
- GitHub wiki tab off (Lisa projects use in-repo `wiki/`)
|
|
25
25
|
- Secret scanning + push protection enabled where the plan supports it
|
|
26
|
-
2. **Rulesets** (`scripts/lisa-github-rulesets.sh`) from Lisa's `<type>/github-rulesets/` templates
|
|
27
|
-
- `base` —
|
|
26
|
+
2. **Rulesets** (`scripts/lisa-github-rulesets.sh`) — one generated from config, the rest from Lisa's `<type>/github-rulesets/` templates matched by project type:
|
|
27
|
+
- `base` — **generated from `.lisa.config.json`**, not shipped as a template. Deletion/force-push protection from `policy.protect`, the branch list from `policy.ruleset.include_refs`, bypass actors from `policy.ruleset.bypass_actors`, approvals from `policy.review`, merge methods from `policy.merge`, and required checks from whatever gates the project `await`s. A project that declares nothing gets exactly the ruleset the retired `all/github-rulesets/base.json` described, minus its two vendor checks — those are now `await` declarations a project can choose not to make
|
|
28
28
|
- `quality checks` — the stack's CI checks (TypeScript emoji names or Rails names)
|
|
29
29
|
- `prevent delete`, `protect tags` (`v*`), plus stack overlays (`cdk validation`, staging-only `playwright`)
|
|
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
|
-
- **A template change never reaches an already-configured repo on its own** — re-running this step is the migration.
|
|
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
|
+
- **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
|
|
32
33
|
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.
|
|
33
34
|
4. **Deployment environments** (`scripts/lisa-github-environments.sh`) — entirely optional; only runs when `.lisa.config.json` declares environments:
|
|
34
35
|
```json
|
|
@@ -52,6 +53,9 @@ Apply the fleet-standard GitHub repository configuration to this project's repo.
|
|
|
52
53
|
- Every declared environment also gets a **deployment branch policy** pinned to its branch, so only that branch can deploy to it.
|
|
53
54
|
- Provisioning matters: GitHub silently auto-creates an environment **without** protection rules the first time a workflow references it, so an unprovisioned approval gate gates on nothing.
|
|
54
55
|
- The stack `deploy.yml` templates read this same config at runtime and pass `require_approval`/`approval_environment` to `release.yml`, whose `release_approval` job is where the run pauses. Requires a public repo or a paid plan for private repos (the script skips gracefully otherwise).
|
|
56
|
+
5. **Policy reconciliation** (`scripts/lisa-reconcile-policy.mjs`, also `npm run policy:reconcile`) — compares the DECLARED gates and `policy` block against what GitHub actually has, and converges it when `policy.on_drift` is `repair`. It runs here rather than only from a skill file, because a declaration nothing applies is not governance.
|
|
57
|
+
- The installed `scripts/lisa-reconcile-policy.mjs` is preferred; the shipped template is the fallback; finding neither is a hard failure, not a skip.
|
|
58
|
+
- Exit `2` is `UNPROVEN` — `gh` refused, is missing, or answered something unparseable. A private repository on a plan without rulesets answers `403`. Setup **warns and continues**: failing for a plan limitation punishes a repository that has done nothing wrong. The warning names the state, because a blind gate that says nothing is indistinguishable from a clean one.
|
|
55
59
|
|
|
56
60
|
## Workflow
|
|
57
61
|
|
|
@@ -139,7 +139,9 @@ import fs from "node:fs";
|
|
|
139
139
|
import path from "node:path";
|
|
140
140
|
import yaml from "js-yaml";
|
|
141
141
|
|
|
142
|
+
import { readGates } from "../all/copy-overwrite/scripts/lisa-gates.mjs";
|
|
142
143
|
import { invokedAsScript } from "./lib/invoked-as-script.mjs";
|
|
144
|
+
import { buildRulesetPayload } from "./lisa-ruleset-payload.mjs";
|
|
143
145
|
|
|
144
146
|
// Literals named once — each was repeated enough times that a typo in one
|
|
145
147
|
// copy would diverge silently.
|
|
@@ -226,13 +228,19 @@ function contextsFromTemplate(absolute, relative) {
|
|
|
226
228
|
}
|
|
227
229
|
|
|
228
230
|
/**
|
|
229
|
-
* Read the per-repository
|
|
231
|
+
* Read the per-repository required-check opt-in.
|
|
230
232
|
*
|
|
231
233
|
* This surface is NOT templated and exists precisely because a Lisa-only
|
|
232
234
|
* context must never ship in a shared template — host projects would inherit a
|
|
233
235
|
* context they never report (the #2476 defect). A guard blind to it would clear
|
|
234
236
|
* every context added this way.
|
|
235
237
|
*
|
|
238
|
+
* Both key spellings are read: `requiredChecks` is the declarative one that
|
|
239
|
+
* can also STOP requiring a context, and `addRequiredChecks` is the additive
|
|
240
|
+
* one it replaced. A guard that read only the new name would go blind to every
|
|
241
|
+
* repository that has not renamed the key yet — the very blindness described
|
|
242
|
+
* above, reintroduced by the rename that was supposed to improve things.
|
|
243
|
+
*
|
|
236
244
|
* @param {string} root - absolute repository root.
|
|
237
245
|
* @returns {{ context: string, integrationId: number, source: string }[]} declarations.
|
|
238
246
|
*/
|
|
@@ -245,9 +253,18 @@ function contextsFromConfig(root) {
|
|
|
245
253
|
} catch {
|
|
246
254
|
return [];
|
|
247
255
|
}
|
|
248
|
-
const
|
|
256
|
+
const rulesets = parsed?.github?.rulesets ?? {};
|
|
257
|
+
// Both maps are read INDEPENDENTLY, never merged by key. A spread would let
|
|
258
|
+
// `requiredChecks["quality checks"]` overwrite `addRequiredChecks["quality
|
|
259
|
+
// checks"]`, and a repository carrying both during a migration would lose the
|
|
260
|
+
// legacy contexts from promotion checking — a guard going quiet about exactly
|
|
261
|
+
// the checks a half-finished rename leaves behind.
|
|
262
|
+
const declared = [
|
|
263
|
+
...Object.values(rulesets.addRequiredChecks ?? {}),
|
|
264
|
+
...Object.values(rulesets.requiredChecks ?? {}),
|
|
265
|
+
];
|
|
249
266
|
const found = [];
|
|
250
|
-
for (const entries of
|
|
267
|
+
for (const entries of declared) {
|
|
251
268
|
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
252
269
|
if (typeof entry?.context !== "string") continue;
|
|
253
270
|
found.push({
|
|
@@ -264,7 +281,54 @@ function contextsFromConfig(root) {
|
|
|
264
281
|
}
|
|
265
282
|
|
|
266
283
|
/**
|
|
267
|
-
*
|
|
284
|
+
* Read the required contexts the generated `base` ruleset will carry.
|
|
285
|
+
*
|
|
286
|
+
* `all/github-rulesets/base.json` used to be a template this function found by
|
|
287
|
+
* walking each project type's `github-rulesets` directory, and it named two
|
|
288
|
+
* vendor contexts. It is now
|
|
289
|
+
* generated from `.lisa.config.json`'s `await` gate declarations, so a guard
|
|
290
|
+
* that only walked directories would have gone blind to two contexts that are
|
|
291
|
+
* still required — and worse, gone blind SILENTLY, reporting a clean promotion
|
|
292
|
+
* ledger for a surface it had stopped reading.
|
|
293
|
+
*
|
|
294
|
+
* @param {string} root - absolute repository root.
|
|
295
|
+
* @returns {{ context: string, integrationId: number, source: string }[]} declarations.
|
|
296
|
+
*/
|
|
297
|
+
function contextsFromGeneratedBase(root) {
|
|
298
|
+
let payload;
|
|
299
|
+
try {
|
|
300
|
+
const { gates, policy } = readGates(root);
|
|
301
|
+
payload = buildRulesetPayload({ gates, policy });
|
|
302
|
+
} catch {
|
|
303
|
+
return [];
|
|
304
|
+
}
|
|
305
|
+
const found = [];
|
|
306
|
+
for (const rule of payload?.rules ?? []) {
|
|
307
|
+
if (rule?.type !== "required_status_checks") continue;
|
|
308
|
+
for (const check of rule?.parameters?.required_status_checks ?? []) {
|
|
309
|
+
if (typeof check?.context !== "string") continue;
|
|
310
|
+
found.push({
|
|
311
|
+
context: check.context,
|
|
312
|
+
// An absent id here means UNPINNED — GitHub's "any source" — not
|
|
313
|
+
// "GitHub Actions". The generated ruleset emits an awaited vendor check
|
|
314
|
+
// without an id on purpose, and defaulting it to Actions would send
|
|
315
|
+
// this guard looking for a workflow job named after the vendor,
|
|
316
|
+
// reporting a wiring violation against a context Actions never posts.
|
|
317
|
+
// A template check with no id genuinely IS an Actions check, which is
|
|
318
|
+
// why the template reader above still defaults; this one must not.
|
|
319
|
+
integrationId:
|
|
320
|
+
typeof check.integration_id === "number"
|
|
321
|
+
? check.integration_id
|
|
322
|
+
: null,
|
|
323
|
+
source: ".lisa.config.json (generated base ruleset)",
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
return found;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
/**
|
|
331
|
+
* Collect every required context this repository declares, from every surface.
|
|
268
332
|
*
|
|
269
333
|
* @param {string} root - absolute repository root.
|
|
270
334
|
* @returns {{ context: string, integrationId: number, source: string }[]}
|
|
@@ -287,6 +351,7 @@ export function collectDeclaredContexts(root) {
|
|
|
287
351
|
}
|
|
288
352
|
}
|
|
289
353
|
found.push(...contextsFromConfig(root));
|
|
354
|
+
found.push(...contextsFromGeneratedBase(root));
|
|
290
355
|
return found.sort(
|
|
291
356
|
(a, b) =>
|
|
292
357
|
a.context.localeCompare(b.context) || a.source.localeCompare(b.source)
|
|
@@ -11,6 +11,9 @@
|
|
|
11
11
|
# 4. Deployment environments with required-reviewer approval gates
|
|
12
12
|
# (lisa-github-environments.sh), from the optional
|
|
13
13
|
# github.environments block in .lisa.config.json.
|
|
14
|
+
# 5. Policy reconciliation (lisa-reconcile-policy.mjs): compares the DECLARED
|
|
15
|
+
# gate and policy configuration against what GitHub actually has, and
|
|
16
|
+
# converges it when policy.on_drift is `repair`.
|
|
14
17
|
#
|
|
15
18
|
# Usage:
|
|
16
19
|
# lisa-github-repo-setup.sh [options] [project-path]
|
|
@@ -38,7 +41,7 @@ while [[ $# -gt 0 ]]; do
|
|
|
38
41
|
case $1 in
|
|
39
42
|
-n|--dry-run) DRY_RUN=true; PASSTHROUGH+=("--dry-run"); shift ;;
|
|
40
43
|
-v|--verbose) VERBOSE=true; PASSTHROUGH+=("--verbose"); shift ;;
|
|
41
|
-
-h|--help) sed -n '2,
|
|
44
|
+
-h|--help) sed -n '2,30p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
|
|
42
45
|
-*) echo "Unknown option: $1" >&2; exit 1 ;;
|
|
43
46
|
*) PROJECT_PATH="$1"; shift ;;
|
|
44
47
|
esac
|
|
@@ -47,15 +50,34 @@ done
|
|
|
47
50
|
PROJECT_PATH="${PROJECT_PATH:-.}"
|
|
48
51
|
PROJECT_PATH="$(cd "$PROJECT_PATH" && pwd)"
|
|
49
52
|
|
|
50
|
-
|
|
53
|
+
# The reconciler ships as a template AND is installed into host projects. The
|
|
54
|
+
# installed copy is preferred so a project pinned to an older Lisa reconciles
|
|
55
|
+
# with the script it actually has; the template is the fallback so a repository
|
|
56
|
+
# that has not run `lisa apply` yet still gets reconciled. Failing closed when
|
|
57
|
+
# NEITHER resolves is the point — a missing script that exits 0 is how a control
|
|
58
|
+
# reports success while examining nothing.
|
|
59
|
+
resolve_reconciler() {
|
|
60
|
+
local installed="$PROJECT_PATH/scripts/lisa-reconcile-policy.mjs"
|
|
61
|
+
local shipped="$SCRIPT_DIR/../all/copy-overwrite/scripts/lisa-reconcile-policy.mjs"
|
|
62
|
+
|
|
63
|
+
if [[ -f "$installed" ]]; then
|
|
64
|
+
echo "$installed"
|
|
65
|
+
elif [[ -f "$shipped" ]]; then
|
|
66
|
+
echo "$shipped"
|
|
67
|
+
else
|
|
68
|
+
return 1
|
|
69
|
+
fi
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
echo "==> Step 1/5: repository settings"
|
|
51
73
|
bash "$SCRIPT_DIR/lisa-github-repo-settings.sh" "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
52
74
|
|
|
53
75
|
echo ""
|
|
54
|
-
echo "==> Step 2/
|
|
76
|
+
echo "==> Step 2/5: rulesets"
|
|
55
77
|
bash "$SCRIPT_DIR/lisa-github-rulesets.sh" --yes "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
56
78
|
|
|
57
79
|
echo ""
|
|
58
|
-
echo "==> Step 3/
|
|
80
|
+
echo "==> Step 3/5: deploy key"
|
|
59
81
|
if [[ "$DRY_RUN" == "true" ]]; then
|
|
60
82
|
echo "[DRY RUN] Would ensure a write-access deploy key + DEPLOY_KEY secret exist"
|
|
61
83
|
else
|
|
@@ -73,8 +95,45 @@ else
|
|
|
73
95
|
fi
|
|
74
96
|
|
|
75
97
|
echo ""
|
|
76
|
-
echo "==> Step 4/
|
|
98
|
+
echo "==> Step 4/5: deployment environments"
|
|
77
99
|
bash "$SCRIPT_DIR/lisa-github-environments.sh" "${PASSTHROUGH[@]}" "$PROJECT_PATH"
|
|
78
100
|
|
|
101
|
+
echo ""
|
|
102
|
+
echo "==> Step 5/5: policy reconciliation"
|
|
103
|
+
if ! RECONCILER="$(resolve_reconciler)"; then
|
|
104
|
+
echo "✗ Could not find lisa-reconcile-policy.mjs in $PROJECT_PATH/scripts/ or in this Lisa install" >&2
|
|
105
|
+
exit 1
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
# Not `[[ ... ]] && cmd`: under `set -e` a false test at top level is a failing
|
|
109
|
+
# command, so on a non-dry run the script would exit right here.
|
|
110
|
+
RECONCILE_ARGS=()
|
|
111
|
+
if [[ "$DRY_RUN" == "true" ]]; then
|
|
112
|
+
RECONCILE_ARGS+=("--dry-run")
|
|
113
|
+
fi
|
|
114
|
+
|
|
115
|
+
# Exit 2 is UNPROVEN: `gh` refused, is missing, or answered something
|
|
116
|
+
# unparseable. A private repository on a plan without rulesets answers 403, and
|
|
117
|
+
# 13 rulesets across the portfolio do exactly that — failing setup for a plan
|
|
118
|
+
# limitation punishes a repository that has done nothing wrong. But a blind gate
|
|
119
|
+
# has to SAY it is blind, which is why this is a named warning and not silence,
|
|
120
|
+
# and why the reconciler keeps 2 as its own code rather than collapsing it into
|
|
121
|
+
# either 0 or 1.
|
|
122
|
+
set +e
|
|
123
|
+
(cd "$PROJECT_PATH" && node "$RECONCILER" "${RECONCILE_ARGS[@]}")
|
|
124
|
+
RECONCILE_STATUS=$?
|
|
125
|
+
set -e
|
|
126
|
+
|
|
127
|
+
case "$RECONCILE_STATUS" in
|
|
128
|
+
0) ;;
|
|
129
|
+
2)
|
|
130
|
+
echo "⚠ Policy was NOT checked (UNPROVEN) — see the reason above. Setup continues; nothing about the declared policy has been verified." >&2
|
|
131
|
+
;;
|
|
132
|
+
*)
|
|
133
|
+
echo "✗ Policy reconciliation failed (exit $RECONCILE_STATUS)" >&2
|
|
134
|
+
exit "$RECONCILE_STATUS"
|
|
135
|
+
;;
|
|
136
|
+
esac
|
|
137
|
+
|
|
79
138
|
echo ""
|
|
80
139
|
echo "✓ GitHub repository governance setup complete"
|