@juangcarmona/lighthouse-sdlc 0.18.5 → 0.18.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/epic-delivery.md +1 -1
- package/package.json +1 -1
- package/src/epic-journal.mjs +15 -1
- package/src/epic-native.mjs +64 -8
- package/workflows/lighthouse-sdlc-epic.yml +2 -2
package/epic-delivery.md
CHANGED
|
@@ -22,7 +22,7 @@ All accepted children make the aggregate `publication-eligible`; the parent stil
|
|
|
22
22
|
|
|
23
23
|
The native epic workflow is not installed or enabled. The implementation includes authenticated child admission, epic-targeted candidates, owner-authorized internal acceptance and draft aggregate publication, but automatic child dispatch, review recovery and complete native acceptance remain unfinished. The current `github-decompose --apply` legacy helper is not this lifecycle and does not provide retry-safe native sub-issue orchestration. Do not use it as a substitute.
|
|
24
24
|
|
|
25
|
-
Native
|
|
25
|
+
Native child delivery requires the default branch to remain at the authenticated refinement revision. Aggregate acceptance can inspect an advanced default branch after merge: it requires retained readiness evidence, accepted child history, passing checks, owner approval and the merged commit in the current branch. Missing or expired evidence stops acceptance and leaves the parent open. This path has not yet been validated end to end in an installed native workflow.
|
|
26
26
|
|
|
27
27
|
## GitHub reconciliation adapter
|
|
28
28
|
|
package/package.json
CHANGED
package/src/epic-journal.mjs
CHANGED
|
@@ -19,7 +19,9 @@ function checkedReceipt(key, receipt, repository, parent) {
|
|
|
19
19
|
if (needsPublication && (!publication || !positive(publication.run) || !Number.isSafeInteger(publication.attempt) || publication.attempt < 1 || !sha(publication.head) || Object.keys(publication).sort().join(',') !== 'attempt,head,run') || !needsPublication && publication !== undefined) throw Error('Epic publication receipt is invalid');
|
|
20
20
|
const merge = receipt.merge, merging = ['merging', 'accepted'].includes(receipt.state);
|
|
21
21
|
if (merging && (receipt.correlation.kind !== 'pull-request' || !merge || !sha(merge.head) || merge.head !== publication.head || !sha(merge.base) || !sha(merge.candidate) || !Number.isSafeInteger(merge.review) || merge.review < 1 || receipt.state === 'accepted' && !sha(merge.commit) || Object.keys(merge).sort().join(',') !== (receipt.state === 'accepted' ? 'base,candidate,commit,head,review' : 'base,candidate,head,review')) || !merging && merge !== undefined) throw Error('Epic merge receipt is invalid');
|
|
22
|
-
const
|
|
22
|
+
const readiness = receipt.readiness;
|
|
23
|
+
if (readiness !== undefined && (receipt.correlation.kind !== 'aggregate' || receipt.state !== 'linked' || !readiness || !sha(readiness.head) || !sha(readiness.base) || !sha(readiness.candidate) || Object.keys(readiness).sort().join(',') !== 'base,candidate,head')) throw Error('Epic aggregate readiness receipt is invalid');
|
|
24
|
+
const expected = ['correlation', ...(merge ? ['merge'] : []), ...(publication ? ['publication'] : []), ...(readiness ? ['readiness'] : []), ...(resource ? ['resource'] : []), 'schemaVersion', 'state'].sort().join(',');
|
|
23
25
|
if (Object.keys(receipt).sort().join(',') !== expected) throw Error('Epic receipt has unexpected fields');
|
|
24
26
|
return receipt;
|
|
25
27
|
}
|
|
@@ -55,6 +57,18 @@ export async function readEpicChildReceipt(input) {
|
|
|
55
57
|
return readEpicResourceReceipt(input);
|
|
56
58
|
}
|
|
57
59
|
|
|
60
|
+
export async function readEpicAggregateReceipt(input) {
|
|
61
|
+
if (input.correlation?.kind !== 'aggregate' || !input.resource) throw Error('Invalid epic aggregate admission');
|
|
62
|
+
const { api, repository, correlation } = input;
|
|
63
|
+
const comments = await issueComments(api, correlation.parent);
|
|
64
|
+
const matches = comments.filter(c => c.user?.login === 'github-actions[bot]' && c.user.type === 'Bot' && c.body?.includes(marker)).map(c => parseEntry(c, repository, correlation.parent)).filter(entry => entry.key === keyFor(correlation));
|
|
65
|
+
if (matches.length !== 1) throw Error('Epic aggregate journal is missing or ambiguous');
|
|
66
|
+
const sourceRevision = matches[0].revision;
|
|
67
|
+
const receipt = await readEpicResourceReceipt({ ...input, sourceRevision });
|
|
68
|
+
if (!receipt.readiness) throw Error('Epic aggregate has no readiness evidence');
|
|
69
|
+
return { sourceRevision, receipt };
|
|
70
|
+
}
|
|
71
|
+
|
|
58
72
|
export async function createEpicJournal({ api, repository, owner, parent, sourceRevision, defaultBranch, run, path, authorize, readArtifact = readActionsJson }) {
|
|
59
73
|
if (!/^[\w.-]+\/[\w.-]+$/.test(repository ?? '') || typeof owner !== 'string' || !positive(parent) || !sha(sourceRevision) || !defaultBranch || !positive(run?.id) || !Number.isSafeInteger(run?.run_attempt) || run.run_attempt < 1 || run.status !== 'in_progress' || run.path !== '.github/workflows/lighthouse-sdlc-epic.yml' || run.event !== 'workflow_dispatch' || run.actor?.login !== owner || run.triggering_actor?.login !== owner || run.head_sha !== sourceRevision || run.head_branch !== defaultBranch || run.repository?.full_name !== repository || typeof authorize !== 'function' || typeof path !== 'string') throw Error('Epic journal requires an authenticated owner workflow');
|
|
60
74
|
parent = Number(parent);
|
package/src/epic-native.mjs
CHANGED
|
@@ -7,7 +7,7 @@ import { loadRefinement } from './proposal.mjs';
|
|
|
7
7
|
import { validateStageEnvelope } from './host-contract.mjs';
|
|
8
8
|
import { assessDecomposition, epicPlanDigest } from './epic.mjs';
|
|
9
9
|
import { reconcileEpicChildren, readEpicMarker, epicMarker, epicCorrelation, epicChildBody, epicJournalKey } from './epic-github.mjs';
|
|
10
|
-
import { createEpicJournal, readEpicChildReceipt, readEpicResourceReceipt } from './epic-journal.mjs';
|
|
10
|
+
import { createEpicJournal, readEpicAggregateReceipt, readEpicChildReceipt, readEpicResourceReceipt } from './epic-journal.mjs';
|
|
11
11
|
import { isDeepStrictEqual } from 'node:util';
|
|
12
12
|
import { checkImplementationPullRequest } from './proposal-review.mjs';
|
|
13
13
|
import { readActionsJson } from './actions-artifact.mjs';
|
|
@@ -22,11 +22,12 @@ import { setPullRequestDraft, projectImplementationPrState } from './pr-state.mj
|
|
|
22
22
|
const isOwnerAnswer = (comment, owner) => comment.user?.login === owner && !comment.body?.includes('<!-- lighthouse-run:') && !comment.body?.includes('<!-- lighthouse-actions:');
|
|
23
23
|
const paused = issue => issue.state !== 'open' || issue.pull_request || issue.labels.some(label => ['future', 'human-working', 'agent-working', 'needs-info', 'waiting-review', 'execution-failed', 'triage', 'refine', 'propose', 'implement'].includes(label.name));
|
|
24
24
|
|
|
25
|
-
export async function prepareNativeEpic(api, repository, owner, issueNumber, load = loadRefinement) {
|
|
25
|
+
export async function prepareNativeEpic(api, repository, owner, issueNumber, load = loadRefinement, historicalRevision) {
|
|
26
26
|
if (!/^[\w.-]+\/[\w.-]+$/.test(repository ?? '') || typeof owner !== 'string' || !Number.isSafeInteger(issueNumber) || issueNumber < 1) throw Error('Invalid epic identity');
|
|
27
27
|
const issue = await api(`issues/${issueNumber}`), comments = await issueComments(api, issueNumber), repo = await api('');
|
|
28
28
|
if (paused(issue) || !issue.labels.some(label => label.name === 'ready')) throw Error('Epic parent is not ready or is paused');
|
|
29
|
-
const sourceRevision = (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha;
|
|
29
|
+
const sourceRevision = historicalRevision ?? (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha;
|
|
30
|
+
if (!/^[a-f0-9]{40}$/.test(sourceRevision ?? '')) throw Error('Epic source revision is invalid');
|
|
30
31
|
const current = { owner, issue: issueNumber, sourceRevision, taskDigest: taskDigest(issue, comments.filter(comment => isOwnerAnswer(comment, owner))) };
|
|
31
32
|
const evidence = await load(api, repository, comments, current);
|
|
32
33
|
if (evidence.request?.issue !== issueNumber || evidence.request.stage !== 'refine' || evidence.request.sourceRevision !== sourceRevision || evidence.request.taskDigest !== current.taskDigest || validateStageEnvelope(evidence.envelope, evidence.request).length) throw Error('Epic refinement result is invalid or stale');
|
|
@@ -39,26 +40,27 @@ export async function prepareNativeEpic(api, repository, owner, issueNumber, loa
|
|
|
39
40
|
return { issue: issueNumber, repository, owner, sourceRevision, defaultBranch: repo.default_branch, taskDigest: current.taskDigest, reportId: report.id, reportBody: report.body, plan: assessment.plan };
|
|
40
41
|
}
|
|
41
42
|
|
|
42
|
-
export async function authorizeNativeEpic(api, snapshot) {
|
|
43
|
+
export async function authorizeNativeEpic(api, snapshot, expectedHead = snapshot.sourceRevision) {
|
|
43
44
|
const issue = await api(`issues/${snapshot.issue}`), comments = await issueComments(api, snapshot.issue), repo = await api('');
|
|
44
45
|
const report = comments.find(comment => comment.id === snapshot.reportId);
|
|
45
46
|
const later = comments.some(comment => Number(comment.id) > Number(snapshot.reportId) && comment.user?.login === 'github-actions[bot]' && comment.user.type === 'Bot' && /<!-- lighthouse-(?:stage:refine:|dispatch:)/.test(comment.body ?? ''));
|
|
46
|
-
if (paused(issue) || !issue.labels.some(label => label.name === 'ready') || repo.default_branch !== snapshot.defaultBranch || (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha !==
|
|
47
|
+
if (paused(issue) || !issue.labels.some(label => label.name === 'ready') || repo.default_branch !== snapshot.defaultBranch || (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha !== expectedHead || taskDigest(issue, comments.filter(comment => isOwnerAnswer(comment, snapshot.owner))) !== snapshot.taskDigest || !report || report.user?.login !== 'github-actions[bot]' || report.user.type !== 'Bot' || report.body !== snapshot.reportBody || later) throw Error('Epic parent authority or refinement changed');
|
|
47
48
|
return true;
|
|
48
49
|
}
|
|
49
50
|
|
|
50
|
-
export async function authenticateEpicChild({ api, repository, owner, child, load, readArtifact, allowClosed = false }) {
|
|
51
|
+
export async function authenticateEpicChild({ api, repository, owner, child, load, readArtifact, allowClosed = false, historicalSnapshot, expectedHead }) {
|
|
51
52
|
if (!Number.isSafeInteger(child) || child < 1) throw Error('Invalid epic child');
|
|
52
53
|
const issue = await api(`issues/${child}`), marker = readEpicMarker(issue.body);
|
|
53
54
|
if (issue.number !== child || !Number.isSafeInteger(issue.id) || issue.id < 1 || issue.state !== 'open' && !(allowClosed && issue.state === 'closed') || issue.pull_request || issue.user?.login !== 'github-actions[bot]' || issue.user.type !== 'Bot' || !marker || marker.kind !== 'child' || marker.repository !== repository || marker.parent === child) throw Error('Epic child identity is not trusted');
|
|
54
|
-
const snapshot = await prepareNativeEpic(api, repository, owner, marker.parent, load);
|
|
55
|
+
const snapshot = historicalSnapshot ?? await prepareNativeEpic(api, repository, owner, marker.parent, load);
|
|
56
|
+
if (snapshot.issue !== marker.parent || snapshot.repository !== repository || snapshot.owner !== owner) throw Error('Epic child snapshot identity changed');
|
|
55
57
|
const slice = snapshot.plan.slices.find(item => item.id === marker.slice);
|
|
56
58
|
const expected = slice && epicCorrelation({ repository, parent: marker.parent, slice: slice.id, planDigest: epicPlanDigest(snapshot.plan), base: `lighthouse/epic-${marker.parent}`, dependencies: slice.dependencies });
|
|
57
59
|
if (!expected || !isDeepStrictEqual(marker, expected) || issue.title !== slice.title || issue.body !== epicChildBody(marker.parent, slice, expected)) throw Error('Epic child no longer matches the current plan');
|
|
58
60
|
const parent = await api(`issues/${child}/parent`);
|
|
59
61
|
if (parent?.number !== marker.parent) throw Error('Epic child parent link changed');
|
|
60
62
|
await readEpicChildReceipt({ api, repository, owner, correlation: expected, sourceRevision: snapshot.sourceRevision, defaultBranch: snapshot.defaultBranch, resource: { id: issue.id, number: child }, ...(readArtifact ? { readArtifact } : {}) });
|
|
61
|
-
await authorizeNativeEpic(api, snapshot);
|
|
63
|
+
await authorizeNativeEpic(api, snapshot, expectedHead);
|
|
62
64
|
return { parent: marker.parent, slice: marker.slice, planDigest: marker.planDigest, base: marker.base, sourceRevision: snapshot.sourceRevision };
|
|
63
65
|
}
|
|
64
66
|
|
|
@@ -349,6 +351,10 @@ export async function readyEpicAggregate({ api, repository, owner, snapshot, jou
|
|
|
349
351
|
return { pr, head: published.head, base: base.object.sha, candidate: pr.merge_commit_sha };
|
|
350
352
|
};
|
|
351
353
|
const first = await observe();
|
|
354
|
+
const correlation = epicCorrelation({ repository, parent: snapshot.issue, slice: 'aggregate', planDigest: epicPlanDigest(snapshot.plan), base: `lighthouse/epic-${snapshot.issue}`, dependencies: snapshot.plan.slices.map(slice => slice.id), kind: 'aggregate' });
|
|
355
|
+
const key = epicJournalKey(correlation), receipt = await journal.readReceipt(key);
|
|
356
|
+
if (receipt?.state !== 'linked' || receipt.resource?.id !== first.pr.id || receipt.resource?.number !== first.pr.number || !isDeepStrictEqual(receipt.correlation, correlation)) throw Error('Aggregate readiness journal changed');
|
|
357
|
+
await journal.writeReceipt(key, { ...receipt, readiness: { head: first.head, base: first.base, candidate: first.candidate } });
|
|
352
358
|
if (first.pr.draft) {
|
|
353
359
|
try { await setDraft(first.pr, false); }
|
|
354
360
|
catch (error) { if ((await api(`pulls/${first.pr.number}`)).draft) throw error; }
|
|
@@ -358,6 +364,55 @@ export async function readyEpicAggregate({ api, repository, owner, snapshot, jou
|
|
|
358
364
|
return { status: 'ready', pullRequest: fresh.pr.number, head: fresh.head, base: fresh.base, parent: 'open' };
|
|
359
365
|
}
|
|
360
366
|
|
|
367
|
+
export async function acceptEpicAggregate({ api, repository, owner, issue, run, load, loadPolicy, readArtifact }) {
|
|
368
|
+
const repo = await api(''), branch = `lighthouse/epic-${issue}`;
|
|
369
|
+
const head = (await api(`commits/${encodeURIComponent(repo.default_branch)}`)).sha;
|
|
370
|
+
if (!/^[a-f0-9]{40}$/.test(head ?? '') || run.status !== 'in_progress' || run.path !== '.github/workflows/lighthouse-sdlc-epic.yml' || run.event !== 'workflow_dispatch' || run.actor?.login !== owner || run.triggering_actor?.login !== owner || run.head_sha !== head || run.head_branch !== repo.default_branch || run.repository?.full_name !== repository) throw Error('Epic acceptance requires an authenticated owner workflow');
|
|
371
|
+
await checkEpicLeaseConfiguration(api, head);
|
|
372
|
+
const pulls = await api(`pulls?state=all&head=${encodeURIComponent(`${owner}:${branch}`)}&base=${encodeURIComponent(repo.default_branch)}&per_page=100`);
|
|
373
|
+
if (!Array.isArray(pulls) || pulls.length !== 1) throw Error('Aggregate PR inventory is missing or ambiguous');
|
|
374
|
+
const pr = await api(`pulls/${pulls[0].number}`), correlation = readEpicMarker(pr.body);
|
|
375
|
+
if (!correlation || correlation.kind !== 'aggregate' || correlation.repository !== repository || correlation.parent !== issue || correlation.base !== branch || correlation.slice !== 'aggregate' || !Number.isSafeInteger(pr.id) || !Number.isSafeInteger(pr.number)) throw Error('Aggregate PR identity is invalid');
|
|
376
|
+
const { sourceRevision, receipt } = await readEpicAggregateReceipt({ api, repository, owner, correlation, defaultBranch: repo.default_branch, resource: { id: pr.id, number: pr.number }, ...(readArtifact ? { readArtifact } : {}) });
|
|
377
|
+
const snapshot = await prepareNativeEpic(api, repository, owner, issue, load, sourceRevision);
|
|
378
|
+
const expected = epicCorrelation({ repository, parent: issue, slice: 'aggregate', planDigest: epicPlanDigest(snapshot.plan), base: branch, dependencies: snapshot.plan.slices.map(slice => slice.id), kind: 'aggregate' });
|
|
379
|
+
if (!isDeepStrictEqual(correlation, expected)) throw Error('Aggregate plan changed');
|
|
380
|
+
const expectedBody = `Aggregate changes for #${issue}.\n\nRefs #${issue}.\n\nReview and merge remain separate decisions.\n\n${epicMarker(expected)}`;
|
|
381
|
+
const journal = { readReceipt: async key => {
|
|
382
|
+
const match = snapshot.plan.slices.map(slice => epicCorrelation({ repository, parent: issue, slice: slice.id, planDigest: expected.planDigest, base: branch, dependencies: slice.dependencies, kind: 'pull-request' })).find(item => epicJournalKey(item) === key);
|
|
383
|
+
if (!match) throw Error('Unknown epic receipt');
|
|
384
|
+
return readEpicResourceReceipt({ api, repository, owner, correlation: match, sourceRevision, defaultBranch: repo.default_branch, allowedStates: ['accepted'], ...(readArtifact ? { readArtifact } : {}) });
|
|
385
|
+
} };
|
|
386
|
+
const verify = async () => {
|
|
387
|
+
await authorizeNativeEpic(api, snapshot, head);
|
|
388
|
+
const currentReceipt = await readEpicAggregateReceipt({ api, repository, owner, correlation, defaultBranch: repo.default_branch, resource: { id: pr.id, number: pr.number }, ...(readArtifact ? { readArtifact } : {}) });
|
|
389
|
+
if (currentReceipt.sourceRevision !== sourceRevision || !isDeepStrictEqual(currentReceipt.receipt, receipt)) throw Error('Aggregate readiness evidence changed');
|
|
390
|
+
const fresh = await api(`pulls/${pr.number}`), binding = receipt.readiness;
|
|
391
|
+
if (fresh.id !== pr.id || !fresh.merged || fresh.state !== 'closed' || fresh.user?.login !== 'github-actions[bot]' || fresh.user.type !== 'Bot' || fresh.head?.ref !== branch || fresh.head.sha !== binding.head || fresh.head.repo?.full_name !== repository || fresh.base?.ref !== repo.default_branch || fresh.base.repo?.full_name !== repository || fresh.body !== expectedBody || !/^[a-f0-9]{40}$/.test(fresh.merge_commit_sha ?? '')) throw Error('Aggregate merge differs from readiness evidence');
|
|
392
|
+
const commit = await api(`git/commits/${fresh.merge_commit_sha}`), candidate = await api(`git/commits/${binding.candidate}`);
|
|
393
|
+
if (!Array.isArray(commit.parents) || ![1, 2].includes(commit.parents.length) || commit.parents[0].sha !== binding.base || commit.parents.length === 2 && commit.parents[1].sha !== binding.head || !/^[a-f0-9]{40}$/.test(commit.tree?.sha ?? '') || commit.tree.sha !== candidate?.tree?.sha || candidate.parents?.length !== 2 || candidate.parents[0].sha !== binding.base || candidate.parents[1].sha !== binding.head) throw Error('Aggregate merge commit differs from its checked candidate');
|
|
394
|
+
const reviews = await list(api, `pulls/${pr.number}/reviews`);
|
|
395
|
+
const latest = reviews.filter(review => review.user?.login === owner && review.user.type === 'User' && ['APPROVED', 'CHANGES_REQUESTED', 'DISMISSED'].includes(review.state)).sort((a, b) => b.id - a.id)[0];
|
|
396
|
+
if (latest?.state !== 'APPROVED' || latest.commit_id !== binding.head) throw Error('Aggregate owner approval is missing or revoked');
|
|
397
|
+
if (!await verifyEpicChecks(api, { repository, sourceRevision: head, base: repo.default_branch, candidate: binding.candidate, loadPolicy })) throw Error('Aggregate checks are no longer valid');
|
|
398
|
+
const compare = await api(`compare/${fresh.merge_commit_sha}...${head}`);
|
|
399
|
+
if (!['identical', 'ahead'].includes(compare.status)) throw Error('Aggregate merge is absent from current main history');
|
|
400
|
+
await verifyEpicBranchHistory(api, snapshot, journal, binding.head, true, loadPolicy);
|
|
401
|
+
for (const slice of snapshot.plan.slices) {
|
|
402
|
+
const childCorrelation = epicCorrelation({ repository, parent: issue, slice: slice.id, planDigest: expected.planDigest, base: branch, dependencies: slice.dependencies });
|
|
403
|
+
const children = await findEpicCandidates(api, childCorrelation);
|
|
404
|
+
if (children.length !== 1 || children[0].state !== 'closed') throw Error('Epic child completion is missing or ambiguous');
|
|
405
|
+
await authenticateEpicChild({ api, repository, owner, child: children[0].number, readArtifact, allowClosed: true, historicalSnapshot: snapshot, expectedHead: head });
|
|
406
|
+
}
|
|
407
|
+
await authorizeNativeEpic(api, snapshot, head);
|
|
408
|
+
};
|
|
409
|
+
await verify();
|
|
410
|
+
await verify();
|
|
411
|
+
try { await api(`issues/${issue}`, 'PATCH', { state: 'closed', state_reason: 'completed' }); }
|
|
412
|
+
catch (error) { if ((await api(`issues/${issue}`)).state !== 'closed') throw error; }
|
|
413
|
+
return { status: 'accepted', pullRequest: pr.number, parent: 'closed' };
|
|
414
|
+
}
|
|
415
|
+
|
|
361
416
|
export async function ensureEpicBase(api, snapshot, authorize, verifyAdvanced = async () => false) {
|
|
362
417
|
const branch = `lighthouse/epic-${snapshot.issue}`;
|
|
363
418
|
const read = () => api(`git/ref/heads/${branch}`, 'GET', undefined, true);
|
|
@@ -397,6 +452,7 @@ export async function runNativeEpic({ api, repository, owner, issue, run, journa
|
|
|
397
452
|
|
|
398
453
|
export async function runEpicAction({ api, repository, owner, issue, run, journalPath, action, child, implementationRun, decision, readArtifact, load, loadPolicy, setDraft = (pr, draft) => setPullRequestDraft(process.env.GH_TOKEN, pr, draft) }) {
|
|
399
454
|
if (action === 'reconcile') return runNativeEpic({ api, repository, owner, issue, run, journalPath, readArtifact, load, loadPolicy });
|
|
455
|
+
if (action === 'accept-aggregate') return acceptEpicAggregate({ api, repository, owner, issue, run, load, loadPolicy, readArtifact });
|
|
400
456
|
if (!['register-pr', 'ready-child', 'accept-child', 'publish-aggregate', 'ready-aggregate'].includes(action) || !['publish-aggregate', 'ready-aggregate'].includes(action) && (!Number.isSafeInteger(child) || child < 1)) throw Error('Invalid epic action or child');
|
|
401
457
|
const snapshot = await prepareNativeEpic(api, repository, owner, issue, load);
|
|
402
458
|
await checkEpicLeaseConfiguration(api, snapshot.sourceRevision);
|
|
@@ -12,7 +12,7 @@ on:
|
|
|
12
12
|
required: true
|
|
13
13
|
default: reconcile
|
|
14
14
|
type: choice
|
|
15
|
-
options: [reconcile, register-pr, ready-child, accept-child, publish-aggregate, ready-aggregate]
|
|
15
|
+
options: [reconcile, register-pr, ready-child, accept-child, publish-aggregate, ready-aggregate, accept-aggregate]
|
|
16
16
|
child:
|
|
17
17
|
description: Child issue number for PR registration or acceptance
|
|
18
18
|
required: false
|
|
@@ -70,7 +70,7 @@ jobs:
|
|
|
70
70
|
GH_TOKEN: ${{ github.token }}
|
|
71
71
|
run: node src/packages/sdlc/src/epic-native.mjs
|
|
72
72
|
- name: Preserve the epic journal
|
|
73
|
-
if: always()
|
|
73
|
+
if: always() && inputs.action != 'accept-aggregate'
|
|
74
74
|
uses: actions/upload-artifact@v7
|
|
75
75
|
with:
|
|
76
76
|
name: lighthouse-epic-journal-${{ inputs.issue }}-${{ github.run_attempt }}
|