@codyswann/lisa 4.28.0 → 4.28.2
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.d.ts.map +1 -1
- package/dist/core/upstream-evidence-manifest.js +7 -6
- package/dist/core/upstream-evidence-manifest.js.map +1 -1
- package/package.json +3 -3
- 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-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa/.codex-plugin/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa/rules/eager/config-resolution.md +23 -0
- package/plugins/lisa/rules/reference/config-resolution.md +65 -0
- package/plugins/lisa/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa-agy/plugin.json +1 -1
- package/plugins/lisa-agy/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-agy/skills/lisa-repair-intake/SKILL.md +65 -5
- 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/rules/eager/config-resolution.md +23 -0
- package/plugins/lisa-copilot/rules/reference/config-resolution.md +65 -0
- package/plugins/lisa-copilot/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-copilot/skills/lisa-repair-intake/SKILL.md +65 -5
- package/plugins/lisa-cursor/.claude-plugin/plugin.json +1 -1
- package/plugins/lisa-cursor/rules/config-resolution-reference.mdc +65 -0
- package/plugins/lisa-cursor/rules/config-resolution.mdc +23 -0
- package/plugins/lisa-cursor/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/lisa-cursor/skills/lisa-repair-intake/SKILL.md +65 -5
- 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/rules/eager/config-resolution.md +23 -0
- package/plugins/src/base/rules/reference/config-resolution.md +65 -0
- package/plugins/src/base/skills/lisa-github-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-jira-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-linear-build-intake/SKILL.md +21 -2
- package/plugins/src/base/skills/lisa-repair-intake/SKILL.md +65 -5
package/package.json
CHANGED
|
@@ -162,7 +162,7 @@
|
|
|
162
162
|
"zod-validation-error": "^4.0.0"
|
|
163
163
|
},
|
|
164
164
|
"name": "@codyswann/lisa",
|
|
165
|
-
"version": "4.28.
|
|
165
|
+
"version": "4.28.2",
|
|
166
166
|
"description": "Claude Code governance framework that applies guardrails, guidance, and automated enforcement to projects",
|
|
167
167
|
"main": "dist/index.js",
|
|
168
168
|
"exports": {
|
|
@@ -307,6 +307,6 @@
|
|
|
307
307
|
"test": "tests"
|
|
308
308
|
},
|
|
309
309
|
"types": "./dist/index.d.ts",
|
|
310
|
-
"lisaReleaseCommit": "
|
|
311
|
-
"gitHead": "
|
|
310
|
+
"lisaReleaseCommit": "c29ccdefd340be9823edcb4ccd85add8e4a62693",
|
|
311
|
+
"gitHead": "c29ccdefd340be9823edcb4ccd85add8e4a62693"
|
|
312
312
|
}
|
|
@@ -56,6 +56,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
56
56
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup).
|
|
57
57
|
3. If `done` is a **string** in config, use it directly regardless of env.
|
|
58
58
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
59
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
60
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
61
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
62
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
63
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
64
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
65
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
66
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
67
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
68
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
69
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
70
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
71
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
72
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
73
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
74
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
75
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
76
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
77
|
+
→ "Promotion completeness".
|
|
59
78
|
|
|
60
79
|
```bash
|
|
61
80
|
TARGET_ENV="${target_env:-}"
|
|
@@ -478,10 +497,10 @@ A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) ass
|
|
|
478
497
|
If the lifecycle run returned Success:
|
|
479
498
|
|
|
480
499
|
1. **Confirm the PR merged.** Read the live state of the issue's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
481
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
500
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave in `$CLAIMED`), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
482
501
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the issue under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it in `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once merged, applies this same env transition. Do **not** comment "Build complete" or close anything.
|
|
483
502
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the issue in `$CLAIMED`.
|
|
484
|
-
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
503
|
+
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
485
504
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
486
505
|
- If `github.labels.build.done` is a string, that string is terminal.
|
|
487
506
|
- If `github.labels.build.done` is an object, only the production/final environment value is terminal (default: `status:done`). Intermediate env values such as `status:on-dev` and `status:on-stg` are not terminal and must stay open.
|
|
@@ -39,6 +39,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
39
39
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup: if base is `staging`, env is `staging`).
|
|
40
40
|
3. If `done` in config is a **string** (not a map), use it directly regardless of env.
|
|
41
41
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
42
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
43
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
44
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
45
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
46
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
47
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
48
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
49
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
50
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
51
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
52
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
53
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
54
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
55
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
56
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
57
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
58
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
59
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
60
|
+
→ "Promotion completeness".
|
|
42
61
|
|
|
43
62
|
```bash
|
|
44
63
|
# Resolve env, then DONE.
|
|
@@ -334,10 +353,10 @@ A `done` env status (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
334
353
|
|
|
335
354
|
If the lifecycle run returned Success:
|
|
336
355
|
1. **Confirm the PR merged.** Read the live state of the ticket's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
337
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
356
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave in `$CLAIMED` for a later cycle), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
338
357
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the ticket under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it in `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once it is merged, applies this same env transition. Do **not** comment "Build complete" or file anything: the work is in-flight, not done.
|
|
339
358
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the ticket in `$CLAIMED` for human investigation.
|
|
340
|
-
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
359
|
+
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
341
360
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
342
361
|
- If `jira.workflow.done` is a string, that status is terminal.
|
|
343
362
|
- If `jira.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env statuses such as `On Dev` and `On Stg` are not terminal and must remain unresolved / open.
|
|
@@ -42,6 +42,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
42
42
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup).
|
|
43
43
|
3. If `done` is a **string** in config, use it directly regardless of env.
|
|
44
44
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
45
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
46
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
47
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
48
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
49
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
50
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
51
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
52
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
53
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
54
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
55
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
56
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
57
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
58
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
59
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
60
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
61
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
62
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
63
|
+
→ "Promotion completeness".
|
|
45
64
|
|
|
46
65
|
```bash
|
|
47
66
|
TARGET_ENV="${target_env:-}"
|
|
@@ -333,10 +352,10 @@ A `done` env state (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
333
352
|
|
|
334
353
|
If the lifecycle run returned Success:
|
|
335
354
|
1. **Confirm the PR merged.** Read the live state of the Issue's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
336
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
355
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave at `$CLAIMED`), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
337
356
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the Issue under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it at `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once merged, applies this same env transition. Do **not** comment "Build complete" or change the native state.
|
|
338
357
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the Issue at `$CLAIMED`.
|
|
339
|
-
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
358
|
+
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
340
359
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
341
360
|
- If `linear.workflow.done` is a string, that state is terminal.
|
|
342
361
|
- If `linear.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env states such as `On Dev` and `On Stg` are not terminal — they are typed `started`, so the Issue correctly stays open and on the board.
|
|
@@ -352,8 +352,9 @@ the vendor build-intake runs**, skipping the claim transition (the item is alrea
|
|
|
352
352
|
2. **On agent success**, apply the scanner's post-agent transition yourself: `claimed → done`,
|
|
353
353
|
where `done` is **env-resolved** exactly as `lisa:<tracker>-build-intake` resolves it (per
|
|
354
354
|
`config-resolution` env-keyed `done`: explicit `target_env` arg wins; else reverse-lookup the
|
|
355
|
-
env from the resulting PR's base branch via `deploy.branches`;
|
|
356
|
-
|
|
355
|
+
env from the resulting PR's base branch via `deploy.branches`; then **cap the resolved env by
|
|
356
|
+
the promotion-completeness gate** — see "Promotion completeness" below; if `done` is a map and
|
|
357
|
+
env is unresolvable, fail loudly — never guess). repair-intake owns this transition because it is
|
|
357
358
|
standing in for the scanner that never got to finish it.
|
|
358
359
|
3. **On a surfaced blocker** (agent reports it cannot proceed), leave/move the item to `blocked`
|
|
359
360
|
with a `[lisa-repair-intake]` note (see Loop prevention). When the surfaced blocker is something
|
|
@@ -393,9 +394,14 @@ same reviewer. Use the same paginated REST reduction documented by
|
|
|
393
394
|
complete and the only thing missing is the env transition the build-intake never applied (its merge
|
|
394
395
|
gate left the item `claimed` because the merge landed after its agent returned). Do **not** re-dispatch
|
|
395
396
|
or file anything: apply the scanner's post-agent env-resolved `claimed → done` transition (the
|
|
396
|
-
resume-sequence step 2, env-resolved from the merged PR's base branch
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
resume-sequence step 2, env-resolved from the merged PR's base branch **and capped by the
|
|
398
|
+
promotion-completeness gate below**) and record it as a repair write.
|
|
399
|
+
|
|
400
|
+
**Run the gate before the write, never after.** The blocker path (step 4) assumes the item is still
|
|
401
|
+
`claimed`, so a deploy failure discovered *after* a lifecycle write has nowhere to go. Resolve every
|
|
402
|
+
rung first: if the rung's deploy concluded anything but `success`, write **nothing**, leave the item
|
|
403
|
+
`claimed`, and fall through to the blocker path; if it has not concluded, write nothing and leave it
|
|
404
|
+
`claimed` for a later cycle. Only a fully resolved gate result is ever written.
|
|
399
405
|
|
|
400
406
|
**3. PR only behind its base → re-sync in place (mechanical, not a blocker).** If the PR is clean but
|
|
401
407
|
behind its base — `mergeStateStatus == BEHIND` while `mergeable != CONFLICTING` and no required check
|
|
@@ -495,6 +501,48 @@ recurred (<date>, <ref>), so we're raising it once more for your review.`); a ma
|
|
|
495
501
|
_completed_ is a regression path, not a decline. Honor the backoff window and state fingerprint
|
|
496
502
|
(Loop prevention) so re-runs over the same unchanged blocker are no-ops.
|
|
497
503
|
|
|
504
|
+
#### Promotion completeness: the base branch is not the delivery
|
|
505
|
+
|
|
506
|
+
A merged PR's base branch names the environment the change **entered**, not the environments it has
|
|
507
|
+
**reached**. A hotfix merged straight to `main` enters production out of order: the reverse-lookup
|
|
508
|
+
resolves `production`, `production` is terminal, and the item closes while `staging` has none of the
|
|
509
|
+
fix and cannot deploy. The evidence is real — merged PR, merge-commit ancestry in `main`, a green
|
|
510
|
+
production deploy — and the inference from it is still wrong.
|
|
511
|
+
|
|
512
|
+
So before writing any env-resolved `done`, walk `deploy.order` from its **lowest** rung and keep
|
|
513
|
+
rungs while they are **reached**, stopping at the first that is not. A rung is reached when both
|
|
514
|
+
hold for its `deploy.branches` branch:
|
|
515
|
+
|
|
516
|
+
- **Ancestry** — `git fetch origin <branch> && git merge-base --is-ancestor <merge-sha> origin/<branch>`,
|
|
517
|
+
asserted for **every** env branch at or below the resolved env — never only the PR's base.
|
|
518
|
+
- **Deploy health** — that branch's most recent deploy **concluded `success`**. Read the
|
|
519
|
+
`conclusion`, never the `status`: an in-flight deploy has a null conclusion and is
|
|
520
|
+
indistinguishable from a pass on the status field alone. Only `success` promotes — a null
|
|
521
|
+
conclusion and every other conclusion (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`,
|
|
522
|
+
`stale`, `action_required`) leave the rung unreached. An unconcluded deploy is unknown, not green
|
|
523
|
+
— do not write that rung this cycle; a later cycle reads a concluded run. Where a project exposes
|
|
524
|
+
no deploy surface for a branch at all, ancestry alone decides that rung.
|
|
525
|
+
|
|
526
|
+
Write the highest **contiguously reached** rung at or below the resolved env — never a higher rung,
|
|
527
|
+
even when the higher rung is reached and a lower one is not. A merge present in `dev` and `main` but
|
|
528
|
+
absent from `staging` writes `dev` (`On Dev`), which is intermediate: the item stays natively open
|
|
529
|
+
and the promotion gap is what holds it open. When no rung is reached, write nothing and leave the
|
|
530
|
+
item in its current role.
|
|
531
|
+
|
|
532
|
+
The refusal is named, never silent. When the gate caps the env below the resolved one, the
|
|
533
|
+
`[lisa-repair-intake]` note MUST carry all three fields —
|
|
534
|
+
`<first unreached env> (<its branch>) — <condition>` — where the condition is exactly one of
|
|
535
|
+
`missing ancestry`, `deploy unknown: <run URL, or "no concluded run">` (an in-flight run, or a
|
|
536
|
+
branch with a deploy surface but nothing concluded), or
|
|
537
|
+
`deploy concluded <conclusion>: <run URL>`. A failing run named without its environment and branch,
|
|
538
|
+
or an environment named without its condition, is an incomplete note.
|
|
539
|
+
|
|
540
|
+
**Do not classify the gap away.** An open back-fill PR against a skipped environment branch is
|
|
541
|
+
*outstanding delivery*, not branch hygiene: it is the evidence that a rung was skipped, so it holds
|
|
542
|
+
the item open. Recording "the fix reached dev and production but skipped staging, so that PR is a
|
|
543
|
+
branch back-fill, not outstanding delivery" and then closing anyway is this defect exactly — the
|
|
544
|
+
condition observed, filed under the wrong heading, and overridden.
|
|
545
|
+
|
|
498
546
|
### Build `blocked` → re-evaluate, unblock if cleared
|
|
499
547
|
|
|
500
548
|
1. Read the block reason and classify the blocker (see Blocker classification & clearing). An item
|
|
@@ -556,6 +604,18 @@ native-open / active / unresolved:
|
|
|
556
604
|
2. Verify the terminal `done` role is the true final value per `leaf-only-lifecycle` and
|
|
557
605
|
`config-resolution` env-keyed `done`. Intermediate env labels (for example `status:on-dev` or
|
|
558
606
|
`status:on-stg`) are not terminal and must stay open.
|
|
607
|
+
|
|
608
|
+
Verify too that the terminal role was **earned**, not merely worn: re-run the
|
|
609
|
+
promotion-completeness gate (above) against the item's merge commit, and close **only** when the
|
|
610
|
+
gate reports the terminal rung itself as reached. On a branch with a deploy surface that means
|
|
611
|
+
`conclusion == success`; an in-flight run, or no concluded run at all, does **not** reach the
|
|
612
|
+
rung and does **not** authorize closure. Where a branch exposes no deploy surface, ancestry alone
|
|
613
|
+
decides it, exactly as the gate documents. If any env branch at or below the terminal one lacks
|
|
614
|
+
the commit or fails its deploy-health test, do **not** close: demote the item to the highest
|
|
615
|
+
contiguously reached rung, keep it open, and refresh a `[lisa-repair-intake]` note carrying the
|
|
616
|
+
full refusal reason (env, branch, condition). A terminal label applied before the gate existed is
|
|
617
|
+
exactly how an out-of-order hotfix reaches native closure — so the label alone is never
|
|
618
|
+
sufficient evidence to close.
|
|
559
619
|
3. Perform the provider-native terminal action idempotently:
|
|
560
620
|
- GitHub: `gh issue close <number> --repo <org>/<repo> --reason completed`.
|
|
561
621
|
- Linear: move the issue to the configured Done / Completed native workflow state if available;
|
|
@@ -89,4 +89,27 @@ A non-integration environment bug is fixed, merged, and verified on that
|
|
|
89
89
|
environment branch first, then
|
|
90
90
|
forward cherry-picked down to the integration branch via a linked follow-up.
|
|
91
91
|
|
|
92
|
+
## Env-keyed `done` — promotion completeness
|
|
93
|
+
|
|
94
|
+
A merged PR's base branch names the environment a change **entered**, never the environments it has
|
|
95
|
+
**reached**. Before writing an env-keyed `done`, walk `deploy.order` from its lowest rung and write
|
|
96
|
+
the highest **contiguously reached** rung at or below the resolved env. A rung is reached only when
|
|
97
|
+
the merge commit is an ancestor of its `deploy.branches` branch
|
|
98
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at or
|
|
99
|
+
below the resolved one, not only the PR's base) **and** that branch's most recent deploy
|
|
100
|
+
**concluded `success`** — read the `conclusion`, never the `status`, because an in-flight deploy has
|
|
101
|
+
a null conclusion and looks identical to a pass. Only a concluded `success` promotes: a null
|
|
102
|
+
conclusion and every other conclusion (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`,
|
|
103
|
+
`stale`, `action_required`) leave the rung unreached. Where `deploy.order` is absent the ladder is
|
|
104
|
+
the single resolved env; where a branch exposes no deploy surface at all, ancestry alone decides
|
|
105
|
+
that rung.
|
|
106
|
+
|
|
107
|
+
A hotfix straight to `main` that skipped `staging` therefore resolves to the rung below the gap and
|
|
108
|
+
stays open; the terminal value, and provider-native closure with it, is earned only by a
|
|
109
|
+
promotion-complete merge. The recorded reason always carries all three fields —
|
|
110
|
+
`<first unreached env> (<its branch>) — <condition>`, where the condition is `missing ancestry`,
|
|
111
|
+
`deploy unknown: <run URL or "no concluded run">`, or `deploy concluded <conclusion>: <run URL>`. A
|
|
112
|
+
failing run named without its environment and branch is an incomplete reason. An open back-fill PR
|
|
113
|
+
against a skipped environment branch is outstanding delivery, not branch hygiene.
|
|
114
|
+
|
|
92
115
|
Full reference: [reference/config-resolution.md](../reference/config-resolution.md).
|
|
@@ -600,9 +600,73 @@ Skills that transition to `done` MUST resolve the env first:
|
|
|
600
600
|
1. **Explicit caller arg** (`target_env=staging`) — always wins.
|
|
601
601
|
2. **Branch inference** — derive from the PR's base branch via `deploy.branches`. Reverse-lookup: if base branch is `staging`, env is `staging`.
|
|
602
602
|
3. **Failure** — if neither resolves and `done` is a map, fail loudly. Never pick arbitrarily.
|
|
603
|
+
4. **Promotion completeness** — cap the resolved env by the gate below before writing anything. Steps 1–2 resolve a *candidate*; the gate decides what is written.
|
|
603
604
|
|
|
604
605
|
If a project's terminal state is the same regardless of env, set `done` to a string instead of a map (lifecycle skills accept either shape).
|
|
605
606
|
|
|
607
|
+
#### Promotion completeness: every environment at or below the resolved one
|
|
608
|
+
|
|
609
|
+
A merged PR's base branch says which environment the change **entered**. It never says which
|
|
610
|
+
environments the change has **reached**. A hotfix merged straight to `main` enters production by an
|
|
611
|
+
out-of-order route: the reverse-lookup resolves `production`, `production` is the terminal value,
|
|
612
|
+
and the item closes while `staging` has none of the fix. Reaching the terminal environment is not
|
|
613
|
+
the same as being delivered, and a base branch alone cannot tell those apart.
|
|
614
|
+
|
|
615
|
+
So the resolved env is not the env that gets written. Walk `deploy.order` from its **lowest** rung
|
|
616
|
+
and keep rungs while they are **reached**, stopping at the first that is not. A rung is reached
|
|
617
|
+
when both hold for its `deploy.branches` branch:
|
|
618
|
+
|
|
619
|
+
- **Ancestry** — the merge commit is an ancestor of the remote env branch:
|
|
620
|
+
`git fetch origin <branch> && git merge-base --is-ancestor <merge-sha> origin/<branch>`. Assert
|
|
621
|
+
this for **every** env branch at or below the resolved one — never only the PR's base.
|
|
622
|
+
- **Deploy health** — that branch's most recent deploy **concluded `success`**. Read the
|
|
623
|
+
`conclusion`, never the `status`: an in-flight deploy has a null conclusion and is
|
|
624
|
+
indistinguishable from a pass on the status field alone. Only `success` promotes — a null
|
|
625
|
+
conclusion and every other conclusion (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`,
|
|
626
|
+
`stale`, `action_required`) leave the rung **unreached**. "Did not fail" is not the test: a
|
|
627
|
+
cancelled or skipped run is not evidence the change deployed.
|
|
628
|
+
|
|
629
|
+
The written env is the highest **contiguously reached** rung at or below the resolved one — never a
|
|
630
|
+
higher rung, even when the higher rung is reached and a lower one is not. A merge present in `dev`
|
|
631
|
+
and `main` but absent from `staging` resolves to `dev` (`On Dev`), not `production`: the item stays
|
|
632
|
+
open at an intermediate waypoint, and the promotion gap is what holds it open. When no rung at all
|
|
633
|
+
is reached, write nothing and leave the item in its current role.
|
|
634
|
+
|
|
635
|
+
Refusals are named, never silent, and one schema covers every case. Whenever the gate caps the env
|
|
636
|
+
below the resolved one, the recorded reason MUST carry **all three** fields — none is optional in
|
|
637
|
+
any case:
|
|
638
|
+
|
|
639
|
+
```
|
|
640
|
+
<first unreached env> (<its branch>) — <condition>
|
|
641
|
+
```
|
|
642
|
+
|
|
643
|
+
`<condition>` is exactly one of:
|
|
644
|
+
|
|
645
|
+
- `missing ancestry` — the merge commit is not an ancestor of that branch.
|
|
646
|
+
- `deploy unknown: <run URL, or "no concluded run">` — the branch has a deploy surface, but its most
|
|
647
|
+
recent run has not concluded, or nothing has ever concluded for it.
|
|
648
|
+
- `deploy concluded <conclusion>: <run URL>` — the most recent run concluded as something other than
|
|
649
|
+
`success` (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`, `stale`, `action_required`).
|
|
650
|
+
|
|
651
|
+
"Merged to `main` and production deployed green" and "not in `staging`" are both facts; a reason that
|
|
652
|
+
carries only the first is the defect. A failing run named without its environment and branch, or an
|
|
653
|
+
environment named without the condition that failed it, is an incomplete reason and not acceptable.
|
|
654
|
+
|
|
655
|
+
Two classifications this gate forbids:
|
|
656
|
+
|
|
657
|
+
- An **open back-fill PR** against a skipped environment branch is *outstanding delivery*, not
|
|
658
|
+
branch hygiene. It is the evidence that a rung was skipped, so it is a reason to hold the item
|
|
659
|
+
open — never a reason to discount the gap and close anyway.
|
|
660
|
+
- An **in-flight deploy** is unknown, not green. When a rung's ancestry holds but its deploy run has
|
|
661
|
+
not concluded, do not write that rung this cycle: leave the item where it is and let a later cycle
|
|
662
|
+
read a concluded run. Only a concluded success promotes. A project that exposes **no** deploy
|
|
663
|
+
surface for a branch is a different case — there, ancestry alone decides the rung; the absence of
|
|
664
|
+
a deploy system is not an unconcluded deploy.
|
|
665
|
+
|
|
666
|
+
Where `deploy.order` is absent the ladder is the single resolved env, and the gate is the ancestry
|
|
667
|
+
and deploy-health check on that one rung. Multi-branch projects MUST set `deploy.order` (see "Env
|
|
668
|
+
order" below), so an absent order can never silently skip the ladder.
|
|
669
|
+
|
|
606
670
|
### Env → base branch (forward: the build base and PR base)
|
|
607
671
|
|
|
608
672
|
`deploy.branches` is also read in the **forward** direction by the build flow (`lisa-implement`): the environment a work item targets determines the branch the work is built on and the branch the PR opens against.
|
|
@@ -634,6 +698,7 @@ The true terminal `done` value is also the only value that triggers provider-nat
|
|
|
634
698
|
- If `done` is a string, that value is terminal.
|
|
635
699
|
- If `done` is an env-keyed map, the production / final environment's value is terminal. The conventional key is `production`; project-specific final env names must be explicit in deploy config or the lifecycle skill must fail rather than guessing.
|
|
636
700
|
- Intermediate env values (`dev`, `staging`, or configured equivalents) are deployment waypoints. Applying them must not close / resolve / complete the native tracker item.
|
|
701
|
+
- The terminal value is earned only by a **promotion-complete** merge: every env branch at or below the terminal one carries the merge commit, and none of their most recent concluded deploys failed. A merge that reached the terminal environment out of order has not earned the terminal value — see "Promotion completeness" above.
|
|
637
702
|
|
|
638
703
|
### Env order (sync-down chain)
|
|
639
704
|
|
|
@@ -56,6 +56,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
56
56
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup).
|
|
57
57
|
3. If `done` is a **string** in config, use it directly regardless of env.
|
|
58
58
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
59
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
60
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
61
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
62
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
63
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
64
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
65
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
66
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
67
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
68
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
69
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
70
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
71
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
72
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
73
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
74
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
75
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
76
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
77
|
+
→ "Promotion completeness".
|
|
59
78
|
|
|
60
79
|
```bash
|
|
61
80
|
TARGET_ENV="${target_env:-}"
|
|
@@ -478,10 +497,10 @@ A `done` env state (`status:on-dev`, `status:on-stg`, or the terminal value) ass
|
|
|
478
497
|
If the lifecycle run returned Success:
|
|
479
498
|
|
|
480
499
|
1. **Confirm the PR merged.** Read the live state of the issue's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
481
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
500
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave in `$CLAIMED`), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
482
501
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the issue under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it in `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once merged, applies this same env transition. Do **not** comment "Build complete" or close anything.
|
|
483
502
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the issue in `$CLAIMED`.
|
|
484
|
-
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
503
|
+
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
485
504
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
486
505
|
- If `github.labels.build.done` is a string, that string is terminal.
|
|
487
506
|
- If `github.labels.build.done` is an object, only the production/final environment value is terminal (default: `status:done`). Intermediate env values such as `status:on-dev` and `status:on-stg` are not terminal and must stay open.
|
|
@@ -39,6 +39,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
39
39
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup: if base is `staging`, env is `staging`).
|
|
40
40
|
3. If `done` in config is a **string** (not a map), use it directly regardless of env.
|
|
41
41
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
42
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
43
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
44
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
45
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
46
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
47
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
48
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
49
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
50
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
51
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
52
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
53
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
54
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
55
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
56
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
57
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
58
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
59
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
60
|
+
→ "Promotion completeness".
|
|
42
61
|
|
|
43
62
|
```bash
|
|
44
63
|
# Resolve env, then DONE.
|
|
@@ -334,10 +353,10 @@ A `done` env status (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
334
353
|
|
|
335
354
|
If the lifecycle run returned Success:
|
|
336
355
|
1. **Confirm the PR merged.** Read the live state of the ticket's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
337
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
356
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave in `$CLAIMED` for a later cycle), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
338
357
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the ticket under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it in `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once it is merged, applies this same env transition. Do **not** comment "Build complete" or file anything: the work is in-flight, not done.
|
|
339
358
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the ticket in `$CLAIMED` for human investigation.
|
|
340
|
-
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
359
|
+
2. Resolve `$DONE` for this ticket's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
341
360
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
342
361
|
- If `jira.workflow.done` is a string, that status is terminal.
|
|
343
362
|
- If `jira.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env statuses such as `On Dev` and `On Stg` are not terminal and must remain unresolved / open.
|
|
@@ -42,6 +42,25 @@ For env-keyed `done`, resolve the env first, then look up `done[<env>]`:
|
|
|
42
42
|
2. Otherwise, infer the env from the PR's base branch via `deploy.branches` (reverse lookup).
|
|
43
43
|
3. If `done` is a **string** in config, use it directly regardless of env.
|
|
44
44
|
4. If `done` is a **map** and env cannot be resolved, **fail loudly** — do not pick arbitrarily.
|
|
45
|
+
5. **Promotion completeness caps the result.** The base branch names the environment the change
|
|
46
|
+
*entered*, never the environments it has *reached*. Walk `deploy.order` from its lowest rung and
|
|
47
|
+
write the highest **contiguously reached** rung at or below the resolved env: a rung is reached
|
|
48
|
+
only when the merge commit is an ancestor of its `deploy.branches` branch
|
|
49
|
+
(`git merge-base --is-ancestor <merge-sha> origin/<branch>`, asserted for **every** env branch at
|
|
50
|
+
or below the resolved one — not only the PR's base) **and** that branch's most recent
|
|
51
|
+
deploy **concluded `success`** (read the `conclusion`, never the `status`; only `success`
|
|
52
|
+
promotes — a null conclusion and every other conclusion, `failure` / `cancelled` / `timed_out` /
|
|
53
|
+
`neutral` / `skipped` / `stale` / `action_required`, leave the rung unreached, and an in-flight
|
|
54
|
+
deploy is unknown, not green). Where `deploy.order` is absent the ladder is the single resolved
|
|
55
|
+
env; where a branch exposes no deploy surface at all, ancestry alone decides that rung. A hotfix
|
|
56
|
+
merged straight to the production branch that skipped `staging` therefore writes the rung below
|
|
57
|
+
the gap and stays open. The recorded reason carries all three fields —
|
|
58
|
+
`<first unreached env> (<its branch>) — <condition>`, the condition being `missing ancestry`,
|
|
59
|
+
`deploy unknown: <run URL, or "no concluded run">`, or
|
|
60
|
+
`deploy concluded <conclusion>: <run URL>`; a failing run named without its environment and
|
|
61
|
+
branch is an incomplete reason. An open back-fill PR against a
|
|
62
|
+
skipped environment branch is outstanding delivery, not branch hygiene. See `config-resolution`
|
|
63
|
+
→ "Promotion completeness".
|
|
45
64
|
|
|
46
65
|
```bash
|
|
47
66
|
TARGET_ENV="${target_env:-}"
|
|
@@ -333,10 +352,10 @@ A `done` env state (`On Dev`, `On Stg`, or the terminal value) asserts that the
|
|
|
333
352
|
|
|
334
353
|
If the lifecycle run returned Success:
|
|
335
354
|
1. **Confirm the PR merged.** Read the live state of the Issue's PR — `gh pr view <pr> --json state,mergedAt,mergeStateStatus,url`:
|
|
336
|
-
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`),
|
|
355
|
+
- **Merged** (`state == MERGED`) → proceed to resolve and apply `$DONE` below. Where the env deploy is observable (a deploy workflow run / deployment status keyed to the merged-into branch via `deploy.branches`), run the **promotion-completeness gate** (resolution step 5) across **every** env branch at or below the resolved env — not only the merged-into branch. A rung counts only when the merge commit is an ancestor of its branch **and** that branch's most recent deploy concluded `success`; a still-running or otherwise unconcluded deploy is treated like an open PR (leave at `$CLAIMED`), and a rung whose deploy concluded anything but `success` is recorded as an Error. Where a branch exposes no deploy surface at all, ancestry alone decides that rung.
|
|
337
356
|
- **Open / not yet merged** → do **not** transition. The build is sound but the change has reached no environment yet. Record the Issue under **"PR open — awaiting merge"** in the summary (with the PR URL and its `mergeStateStatus`), leave it at `$CLAIMED`, and stop. A later `lisa-repair-intake` cycle drives the open PR to merge — re-syncing a `BEHIND` branch so the already-enabled auto-merge can land, or surfacing a real blocker — and, once merged, applies this same env transition. Do **not** comment "Build complete" or change the native state.
|
|
338
357
|
- **Closed without merging** → record an Error (the PR was abandoned unmerged); leave the Issue at `$CLAIMED`.
|
|
339
|
-
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
358
|
+
2. Resolve `$DONE` for this issue's PR base branch using the Workflow resolution algorithm above, **including its promotion-completeness cap** (step 5): the value written is the highest contiguously reached rung at or below the resolved env, never the resolved env by itself. If env can't be resolved and `done` is env-keyed, record an Error and skip this transition — never guess.
|
|
340
359
|
3. Determine whether `$DONE` is the true terminal done value per the `leaf-only-lifecycle` rule's Terminal native closure section:
|
|
341
360
|
- If `linear.workflow.done` is a string, that state is terminal.
|
|
342
361
|
- If `linear.workflow.done` is an object, only the production/final environment value is terminal (default: `Done`). Intermediate env states such as `On Dev` and `On Stg` are not terminal — they are typed `started`, so the Issue correctly stays open and on the board.
|
|
@@ -352,8 +352,9 @@ the vendor build-intake runs**, skipping the claim transition (the item is alrea
|
|
|
352
352
|
2. **On agent success**, apply the scanner's post-agent transition yourself: `claimed → done`,
|
|
353
353
|
where `done` is **env-resolved** exactly as `lisa:<tracker>-build-intake` resolves it (per
|
|
354
354
|
`config-resolution` env-keyed `done`: explicit `target_env` arg wins; else reverse-lookup the
|
|
355
|
-
env from the resulting PR's base branch via `deploy.branches`;
|
|
356
|
-
|
|
355
|
+
env from the resulting PR's base branch via `deploy.branches`; then **cap the resolved env by
|
|
356
|
+
the promotion-completeness gate** — see "Promotion completeness" below; if `done` is a map and
|
|
357
|
+
env is unresolvable, fail loudly — never guess). repair-intake owns this transition because it is
|
|
357
358
|
standing in for the scanner that never got to finish it.
|
|
358
359
|
3. **On a surfaced blocker** (agent reports it cannot proceed), leave/move the item to `blocked`
|
|
359
360
|
with a `[lisa-repair-intake]` note (see Loop prevention). When the surfaced blocker is something
|
|
@@ -393,9 +394,14 @@ same reviewer. Use the same paginated REST reduction documented by
|
|
|
393
394
|
complete and the only thing missing is the env transition the build-intake never applied (its merge
|
|
394
395
|
gate left the item `claimed` because the merge landed after its agent returned). Do **not** re-dispatch
|
|
395
396
|
or file anything: apply the scanner's post-agent env-resolved `claimed → done` transition (the
|
|
396
|
-
resume-sequence step 2, env-resolved from the merged PR's base branch
|
|
397
|
-
|
|
398
|
-
|
|
397
|
+
resume-sequence step 2, env-resolved from the merged PR's base branch **and capped by the
|
|
398
|
+
promotion-completeness gate below**) and record it as a repair write.
|
|
399
|
+
|
|
400
|
+
**Run the gate before the write, never after.** The blocker path (step 4) assumes the item is still
|
|
401
|
+
`claimed`, so a deploy failure discovered *after* a lifecycle write has nowhere to go. Resolve every
|
|
402
|
+
rung first: if the rung's deploy concluded anything but `success`, write **nothing**, leave the item
|
|
403
|
+
`claimed`, and fall through to the blocker path; if it has not concluded, write nothing and leave it
|
|
404
|
+
`claimed` for a later cycle. Only a fully resolved gate result is ever written.
|
|
399
405
|
|
|
400
406
|
**3. PR only behind its base → re-sync in place (mechanical, not a blocker).** If the PR is clean but
|
|
401
407
|
behind its base — `mergeStateStatus == BEHIND` while `mergeable != CONFLICTING` and no required check
|
|
@@ -495,6 +501,48 @@ recurred (<date>, <ref>), so we're raising it once more for your review.`); a ma
|
|
|
495
501
|
_completed_ is a regression path, not a decline. Honor the backoff window and state fingerprint
|
|
496
502
|
(Loop prevention) so re-runs over the same unchanged blocker are no-ops.
|
|
497
503
|
|
|
504
|
+
#### Promotion completeness: the base branch is not the delivery
|
|
505
|
+
|
|
506
|
+
A merged PR's base branch names the environment the change **entered**, not the environments it has
|
|
507
|
+
**reached**. A hotfix merged straight to `main` enters production out of order: the reverse-lookup
|
|
508
|
+
resolves `production`, `production` is terminal, and the item closes while `staging` has none of the
|
|
509
|
+
fix and cannot deploy. The evidence is real — merged PR, merge-commit ancestry in `main`, a green
|
|
510
|
+
production deploy — and the inference from it is still wrong.
|
|
511
|
+
|
|
512
|
+
So before writing any env-resolved `done`, walk `deploy.order` from its **lowest** rung and keep
|
|
513
|
+
rungs while they are **reached**, stopping at the first that is not. A rung is reached when both
|
|
514
|
+
hold for its `deploy.branches` branch:
|
|
515
|
+
|
|
516
|
+
- **Ancestry** — `git fetch origin <branch> && git merge-base --is-ancestor <merge-sha> origin/<branch>`,
|
|
517
|
+
asserted for **every** env branch at or below the resolved env — never only the PR's base.
|
|
518
|
+
- **Deploy health** — that branch's most recent deploy **concluded `success`**. Read the
|
|
519
|
+
`conclusion`, never the `status`: an in-flight deploy has a null conclusion and is
|
|
520
|
+
indistinguishable from a pass on the status field alone. Only `success` promotes — a null
|
|
521
|
+
conclusion and every other conclusion (`failure`, `cancelled`, `timed_out`, `neutral`, `skipped`,
|
|
522
|
+
`stale`, `action_required`) leave the rung unreached. An unconcluded deploy is unknown, not green
|
|
523
|
+
— do not write that rung this cycle; a later cycle reads a concluded run. Where a project exposes
|
|
524
|
+
no deploy surface for a branch at all, ancestry alone decides that rung.
|
|
525
|
+
|
|
526
|
+
Write the highest **contiguously reached** rung at or below the resolved env — never a higher rung,
|
|
527
|
+
even when the higher rung is reached and a lower one is not. A merge present in `dev` and `main` but
|
|
528
|
+
absent from `staging` writes `dev` (`On Dev`), which is intermediate: the item stays natively open
|
|
529
|
+
and the promotion gap is what holds it open. When no rung is reached, write nothing and leave the
|
|
530
|
+
item in its current role.
|
|
531
|
+
|
|
532
|
+
The refusal is named, never silent. When the gate caps the env below the resolved one, the
|
|
533
|
+
`[lisa-repair-intake]` note MUST carry all three fields —
|
|
534
|
+
`<first unreached env> (<its branch>) — <condition>` — where the condition is exactly one of
|
|
535
|
+
`missing ancestry`, `deploy unknown: <run URL, or "no concluded run">` (an in-flight run, or a
|
|
536
|
+
branch with a deploy surface but nothing concluded), or
|
|
537
|
+
`deploy concluded <conclusion>: <run URL>`. A failing run named without its environment and branch,
|
|
538
|
+
or an environment named without its condition, is an incomplete note.
|
|
539
|
+
|
|
540
|
+
**Do not classify the gap away.** An open back-fill PR against a skipped environment branch is
|
|
541
|
+
*outstanding delivery*, not branch hygiene: it is the evidence that a rung was skipped, so it holds
|
|
542
|
+
the item open. Recording "the fix reached dev and production but skipped staging, so that PR is a
|
|
543
|
+
branch back-fill, not outstanding delivery" and then closing anyway is this defect exactly — the
|
|
544
|
+
condition observed, filed under the wrong heading, and overridden.
|
|
545
|
+
|
|
498
546
|
### Build `blocked` → re-evaluate, unblock if cleared
|
|
499
547
|
|
|
500
548
|
1. Read the block reason and classify the blocker (see Blocker classification & clearing). An item
|
|
@@ -556,6 +604,18 @@ native-open / active / unresolved:
|
|
|
556
604
|
2. Verify the terminal `done` role is the true final value per `leaf-only-lifecycle` and
|
|
557
605
|
`config-resolution` env-keyed `done`. Intermediate env labels (for example `status:on-dev` or
|
|
558
606
|
`status:on-stg`) are not terminal and must stay open.
|
|
607
|
+
|
|
608
|
+
Verify too that the terminal role was **earned**, not merely worn: re-run the
|
|
609
|
+
promotion-completeness gate (above) against the item's merge commit, and close **only** when the
|
|
610
|
+
gate reports the terminal rung itself as reached. On a branch with a deploy surface that means
|
|
611
|
+
`conclusion == success`; an in-flight run, or no concluded run at all, does **not** reach the
|
|
612
|
+
rung and does **not** authorize closure. Where a branch exposes no deploy surface, ancestry alone
|
|
613
|
+
decides it, exactly as the gate documents. If any env branch at or below the terminal one lacks
|
|
614
|
+
the commit or fails its deploy-health test, do **not** close: demote the item to the highest
|
|
615
|
+
contiguously reached rung, keep it open, and refresh a `[lisa-repair-intake]` note carrying the
|
|
616
|
+
full refusal reason (env, branch, condition). A terminal label applied before the gate existed is
|
|
617
|
+
exactly how an out-of-order hotfix reaches native closure — so the label alone is never
|
|
618
|
+
sufficient evidence to close.
|
|
559
619
|
3. Perform the provider-native terminal action idempotently:
|
|
560
620
|
- GitHub: `gh issue close <number> --repo <org>/<repo> --reason completed`.
|
|
561
621
|
- Linear: move the issue to the configured Done / Completed native workflow state if available;
|