@attalabs/vinaya 0.8.0 → 0.8.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/aeg-root/enforcement.md +2 -2
- package/aeg-root/roles/planner.md +2 -2
- package/aeg-root/roles/reviewer.md +1 -1
- package/aeg-root/skills/brief-authoring/SKILL.md +1 -1
- package/aeg-root/state-machine.md +6 -6
- package/aeg-root/tranche-model.md +1 -1
- package/dist/checks/bin/check-branch-topology.js +21 -5
- package/dist/checks/bin/check-brief-shape.js +21 -5
- package/dist/checks/bin/check-closes-n.js +21 -5
- package/dist/checks/bin/check-coherence.js +200 -10
- package/dist/checks/bin/check-dead-branch-push.js +21 -5
- package/dist/checks/bin/check-dispatch-readiness.js +223 -11
- package/dist/checks/bin/check-doc-coverage-push.js +21 -5
- package/dist/checks/bin/check-doc-coverage.js +55 -10
- package/dist/checks/bin/check-first-push-dispatch.js +224 -12
- package/dist/checks/bin/check-issue-assignment.js +21 -5
- package/dist/checks/bin/check-no-disk-state.js +21 -5
- package/dist/checks/bin/check-reader-resolvable-prose.js +21 -5
- package/dist/checks/bin/check-registry-gates.js +21 -5
- package/dist/checks/bin/check-review-gate.js +21 -5
- package/dist/checks/bin/check-single-plan-pr.js +21 -5
- package/dist/checks/bin/check-test-plan.js +21 -5
- package/dist/index.js +6 -2
- package/package.json +4 -4
|
@@ -1220,7 +1220,8 @@ function mapForgeFacts(raw) {
|
|
|
1220
1220
|
reviewDecision: mapReviewDecision(raw.pullRequest?.reviewDecision),
|
|
1221
1221
|
stateReason: mapStateReason(raw.issue.stateReason),
|
|
1222
1222
|
closedAt: raw.issue.closedAt ?? null,
|
|
1223
|
-
mergedAt: raw.pullRequest?.mergedAt ?? null
|
|
1223
|
+
mergedAt: raw.pullRequest?.mergedAt ?? null,
|
|
1224
|
+
closedByActor: raw.closedByActor ?? null
|
|
1224
1225
|
};
|
|
1225
1226
|
}
|
|
1226
1227
|
function mapStateReason(reason) {
|
|
@@ -1313,6 +1314,7 @@ function buildBatchQuery(tranche, tasks) {
|
|
|
1313
1314
|
timelineItems(last: 1, itemTypes: [CLOSED_EVENT]) {
|
|
1314
1315
|
nodes {
|
|
1315
1316
|
... on ClosedEvent {
|
|
1317
|
+
actor { login }
|
|
1316
1318
|
closer {
|
|
1317
1319
|
... on PullRequest {
|
|
1318
1320
|
number
|
|
@@ -1350,7 +1352,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1350
1352
|
const issue = repository[`${alias}_issue`];
|
|
1351
1353
|
const ref = repository[`${alias}_ref`];
|
|
1352
1354
|
const prs = repository[`${alias}_prs`];
|
|
1353
|
-
const
|
|
1355
|
+
const timelineNode = issue?.timelineItems?.nodes?.[0];
|
|
1356
|
+
const rawCloser = timelineNode?.closer ?? null;
|
|
1354
1357
|
const closingPr = rawCloser && typeof rawCloser.number === "number" && typeof rawCloser.url === "string" ? rawCloser : null;
|
|
1355
1358
|
const branchPr = prs && prs.nodes.length > 0 && prs.nodes[0] ? prs.nodes[0] : null;
|
|
1356
1359
|
return {
|
|
@@ -1362,7 +1365,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1362
1365
|
labels: issue.labels.nodes.map((n) => n.name)
|
|
1363
1366
|
} : null,
|
|
1364
1367
|
refExists: Boolean(ref && ref.name.length > 0),
|
|
1365
|
-
pullRequest: closingPr ?? branchPr
|
|
1368
|
+
pullRequest: closingPr ?? branchPr,
|
|
1369
|
+
closedByActor: timelineNode?.actor?.login ?? null
|
|
1366
1370
|
};
|
|
1367
1371
|
}
|
|
1368
1372
|
function describeError(err) {
|
|
@@ -1500,6 +1504,11 @@ var FORGE_FACT_INPUTS = [
|
|
|
1500
1504
|
fact: "mergedAt",
|
|
1501
1505
|
readsFrom: "PullRequest.mergedAt",
|
|
1502
1506
|
meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
|
|
1507
|
+
},
|
|
1508
|
+
{
|
|
1509
|
+
fact: "closedByActor",
|
|
1510
|
+
readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
|
|
1511
|
+
meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
|
|
1503
1512
|
}
|
|
1504
1513
|
];
|
|
1505
1514
|
var DERIVED_STATUSES = [
|
|
@@ -1979,7 +1988,7 @@ function isGrandfathered(isoDate) {
|
|
|
1979
1988
|
return isoDate.slice(0, 10) < COHERENCE_ENFORCED_FROM;
|
|
1980
1989
|
}
|
|
1981
1990
|
var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
|
|
1982
|
-
function checkA1(entries) {
|
|
1991
|
+
function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
1983
1992
|
const failures = [];
|
|
1984
1993
|
for (const e of entries) {
|
|
1985
1994
|
if (!e.facts)
|
|
@@ -1987,6 +1996,9 @@ function checkA1(entries) {
|
|
|
1987
1996
|
if (e.facts.stateReason === "not_planned")
|
|
1988
1997
|
continue;
|
|
1989
1998
|
if (e.facts.issueState === "closed" && e.facts.prState !== "merged") {
|
|
1999
|
+
const handClosed = e.facts.stateReason === "completed" && isPrincipal(e.facts.closedByActor, principalAllowlist);
|
|
2000
|
+
if (handClosed)
|
|
2001
|
+
continue;
|
|
1990
2002
|
failures.push({
|
|
1991
2003
|
issue: e.task.issue,
|
|
1992
2004
|
tranche: e.trancheSlug,
|
|
@@ -2676,9 +2688,13 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2676
2688
|
return terms;
|
|
2677
2689
|
}
|
|
2678
2690
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2691
|
+
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2692
|
+
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
2693
|
+
}
|
|
2679
2694
|
function checkDispatchReadiness(input) {
|
|
2680
2695
|
const { trancheSlug, task } = input;
|
|
2681
2696
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
2697
|
+
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2682
2698
|
const blockers = [];
|
|
2683
2699
|
if (task.issue === null) {
|
|
2684
2700
|
blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
@@ -2689,7 +2705,7 @@ function checkDispatchReadiness(input) {
|
|
|
2689
2705
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
2690
2706
|
}
|
|
2691
2707
|
for (const dep of input.dependsOn) {
|
|
2692
|
-
if (!dep.merged) {
|
|
2708
|
+
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
2693
2709
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
2694
2710
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
2695
2711
|
}
|
|
@@ -1218,7 +1218,8 @@ function mapForgeFacts(raw) {
|
|
|
1218
1218
|
reviewDecision: mapReviewDecision(raw.pullRequest?.reviewDecision),
|
|
1219
1219
|
stateReason: mapStateReason(raw.issue.stateReason),
|
|
1220
1220
|
closedAt: raw.issue.closedAt ?? null,
|
|
1221
|
-
mergedAt: raw.pullRequest?.mergedAt ?? null
|
|
1221
|
+
mergedAt: raw.pullRequest?.mergedAt ?? null,
|
|
1222
|
+
closedByActor: raw.closedByActor ?? null
|
|
1222
1223
|
};
|
|
1223
1224
|
}
|
|
1224
1225
|
function mapStateReason(reason) {
|
|
@@ -1311,6 +1312,7 @@ function buildBatchQuery(tranche, tasks) {
|
|
|
1311
1312
|
timelineItems(last: 1, itemTypes: [CLOSED_EVENT]) {
|
|
1312
1313
|
nodes {
|
|
1313
1314
|
... on ClosedEvent {
|
|
1315
|
+
actor { login }
|
|
1314
1316
|
closer {
|
|
1315
1317
|
... on PullRequest {
|
|
1316
1318
|
number
|
|
@@ -1348,7 +1350,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1348
1350
|
const issue = repository[`${alias}_issue`];
|
|
1349
1351
|
const ref = repository[`${alias}_ref`];
|
|
1350
1352
|
const prs = repository[`${alias}_prs`];
|
|
1351
|
-
const
|
|
1353
|
+
const timelineNode = issue?.timelineItems?.nodes?.[0];
|
|
1354
|
+
const rawCloser = timelineNode?.closer ?? null;
|
|
1352
1355
|
const closingPr = rawCloser && typeof rawCloser.number === "number" && typeof rawCloser.url === "string" ? rawCloser : null;
|
|
1353
1356
|
const branchPr = prs && prs.nodes.length > 0 && prs.nodes[0] ? prs.nodes[0] : null;
|
|
1354
1357
|
return {
|
|
@@ -1360,7 +1363,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1360
1363
|
labels: issue.labels.nodes.map((n) => n.name)
|
|
1361
1364
|
} : null,
|
|
1362
1365
|
refExists: Boolean(ref && ref.name.length > 0),
|
|
1363
|
-
pullRequest: closingPr ?? branchPr
|
|
1366
|
+
pullRequest: closingPr ?? branchPr,
|
|
1367
|
+
closedByActor: timelineNode?.actor?.login ?? null
|
|
1364
1368
|
};
|
|
1365
1369
|
}
|
|
1366
1370
|
function describeError(err) {
|
|
@@ -1498,6 +1502,11 @@ var FORGE_FACT_INPUTS = [
|
|
|
1498
1502
|
fact: "mergedAt",
|
|
1499
1503
|
readsFrom: "PullRequest.mergedAt",
|
|
1500
1504
|
meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
fact: "closedByActor",
|
|
1508
|
+
readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
|
|
1509
|
+
meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
|
|
1501
1510
|
}
|
|
1502
1511
|
];
|
|
1503
1512
|
var DERIVED_STATUSES = [
|
|
@@ -1977,7 +1986,7 @@ function isGrandfathered(isoDate) {
|
|
|
1977
1986
|
return isoDate.slice(0, 10) < COHERENCE_ENFORCED_FROM;
|
|
1978
1987
|
}
|
|
1979
1988
|
var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
|
|
1980
|
-
function checkA1(entries) {
|
|
1989
|
+
function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
1981
1990
|
const failures = [];
|
|
1982
1991
|
for (const e of entries) {
|
|
1983
1992
|
if (!e.facts)
|
|
@@ -1985,6 +1994,9 @@ function checkA1(entries) {
|
|
|
1985
1994
|
if (e.facts.stateReason === "not_planned")
|
|
1986
1995
|
continue;
|
|
1987
1996
|
if (e.facts.issueState === "closed" && e.facts.prState !== "merged") {
|
|
1997
|
+
const handClosed = e.facts.stateReason === "completed" && isPrincipal(e.facts.closedByActor, principalAllowlist);
|
|
1998
|
+
if (handClosed)
|
|
1999
|
+
continue;
|
|
1988
2000
|
failures.push({
|
|
1989
2001
|
issue: e.task.issue,
|
|
1990
2002
|
tranche: e.trancheSlug,
|
|
@@ -2674,9 +2686,13 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2674
2686
|
return terms;
|
|
2675
2687
|
}
|
|
2676
2688
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2689
|
+
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2690
|
+
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
2691
|
+
}
|
|
2677
2692
|
function checkDispatchReadiness(input) {
|
|
2678
2693
|
const { trancheSlug, task } = input;
|
|
2679
2694
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
2695
|
+
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2680
2696
|
const blockers = [];
|
|
2681
2697
|
if (task.issue === null) {
|
|
2682
2698
|
blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
@@ -2687,7 +2703,7 @@ function checkDispatchReadiness(input) {
|
|
|
2687
2703
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
2688
2704
|
}
|
|
2689
2705
|
for (const dep of input.dependsOn) {
|
|
2690
|
-
if (!dep.merged) {
|
|
2706
|
+
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
2691
2707
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
2692
2708
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
2693
2709
|
}
|
|
@@ -1218,7 +1218,8 @@ function mapForgeFacts(raw) {
|
|
|
1218
1218
|
reviewDecision: mapReviewDecision(raw.pullRequest?.reviewDecision),
|
|
1219
1219
|
stateReason: mapStateReason(raw.issue.stateReason),
|
|
1220
1220
|
closedAt: raw.issue.closedAt ?? null,
|
|
1221
|
-
mergedAt: raw.pullRequest?.mergedAt ?? null
|
|
1221
|
+
mergedAt: raw.pullRequest?.mergedAt ?? null,
|
|
1222
|
+
closedByActor: raw.closedByActor ?? null
|
|
1222
1223
|
};
|
|
1223
1224
|
}
|
|
1224
1225
|
function mapStateReason(reason) {
|
|
@@ -1311,6 +1312,7 @@ function buildBatchQuery(tranche, tasks) {
|
|
|
1311
1312
|
timelineItems(last: 1, itemTypes: [CLOSED_EVENT]) {
|
|
1312
1313
|
nodes {
|
|
1313
1314
|
... on ClosedEvent {
|
|
1315
|
+
actor { login }
|
|
1314
1316
|
closer {
|
|
1315
1317
|
... on PullRequest {
|
|
1316
1318
|
number
|
|
@@ -1348,7 +1350,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1348
1350
|
const issue = repository[`${alias}_issue`];
|
|
1349
1351
|
const ref = repository[`${alias}_ref`];
|
|
1350
1352
|
const prs = repository[`${alias}_prs`];
|
|
1351
|
-
const
|
|
1353
|
+
const timelineNode = issue?.timelineItems?.nodes?.[0];
|
|
1354
|
+
const rawCloser = timelineNode?.closer ?? null;
|
|
1352
1355
|
const closingPr = rawCloser && typeof rawCloser.number === "number" && typeof rawCloser.url === "string" ? rawCloser : null;
|
|
1353
1356
|
const branchPr = prs && prs.nodes.length > 0 && prs.nodes[0] ? prs.nodes[0] : null;
|
|
1354
1357
|
return {
|
|
@@ -1360,7 +1363,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1360
1363
|
labels: issue.labels.nodes.map((n) => n.name)
|
|
1361
1364
|
} : null,
|
|
1362
1365
|
refExists: Boolean(ref && ref.name.length > 0),
|
|
1363
|
-
pullRequest: closingPr ?? branchPr
|
|
1366
|
+
pullRequest: closingPr ?? branchPr,
|
|
1367
|
+
closedByActor: timelineNode?.actor?.login ?? null
|
|
1364
1368
|
};
|
|
1365
1369
|
}
|
|
1366
1370
|
function describeError(err) {
|
|
@@ -1498,6 +1502,11 @@ var FORGE_FACT_INPUTS = [
|
|
|
1498
1502
|
fact: "mergedAt",
|
|
1499
1503
|
readsFrom: "PullRequest.mergedAt",
|
|
1500
1504
|
meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
|
|
1505
|
+
},
|
|
1506
|
+
{
|
|
1507
|
+
fact: "closedByActor",
|
|
1508
|
+
readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
|
|
1509
|
+
meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
|
|
1501
1510
|
}
|
|
1502
1511
|
];
|
|
1503
1512
|
var DERIVED_STATUSES = [
|
|
@@ -1977,7 +1986,7 @@ function isGrandfathered(isoDate) {
|
|
|
1977
1986
|
return isoDate.slice(0, 10) < COHERENCE_ENFORCED_FROM;
|
|
1978
1987
|
}
|
|
1979
1988
|
var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
|
|
1980
|
-
function checkA1(entries) {
|
|
1989
|
+
function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
1981
1990
|
const failures = [];
|
|
1982
1991
|
for (const e of entries) {
|
|
1983
1992
|
if (!e.facts)
|
|
@@ -1985,6 +1994,9 @@ function checkA1(entries) {
|
|
|
1985
1994
|
if (e.facts.stateReason === "not_planned")
|
|
1986
1995
|
continue;
|
|
1987
1996
|
if (e.facts.issueState === "closed" && e.facts.prState !== "merged") {
|
|
1997
|
+
const handClosed = e.facts.stateReason === "completed" && isPrincipal(e.facts.closedByActor, principalAllowlist);
|
|
1998
|
+
if (handClosed)
|
|
1999
|
+
continue;
|
|
1988
2000
|
failures.push({
|
|
1989
2001
|
issue: e.task.issue,
|
|
1990
2002
|
tranche: e.trancheSlug,
|
|
@@ -2674,9 +2686,13 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2674
2686
|
return terms;
|
|
2675
2687
|
}
|
|
2676
2688
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2689
|
+
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2690
|
+
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
2691
|
+
}
|
|
2677
2692
|
function checkDispatchReadiness(input) {
|
|
2678
2693
|
const { trancheSlug, task } = input;
|
|
2679
2694
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
2695
|
+
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2680
2696
|
const blockers = [];
|
|
2681
2697
|
if (task.issue === null) {
|
|
2682
2698
|
blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
@@ -2687,7 +2703,7 @@ function checkDispatchReadiness(input) {
|
|
|
2687
2703
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
2688
2704
|
}
|
|
2689
2705
|
for (const dep of input.dependsOn) {
|
|
2690
|
-
if (!dep.merged) {
|
|
2706
|
+
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
2691
2707
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
2692
2708
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
2693
2709
|
}
|
|
@@ -1215,7 +1215,8 @@ function mapForgeFacts(raw) {
|
|
|
1215
1215
|
reviewDecision: mapReviewDecision(raw.pullRequest?.reviewDecision),
|
|
1216
1216
|
stateReason: mapStateReason(raw.issue.stateReason),
|
|
1217
1217
|
closedAt: raw.issue.closedAt ?? null,
|
|
1218
|
-
mergedAt: raw.pullRequest?.mergedAt ?? null
|
|
1218
|
+
mergedAt: raw.pullRequest?.mergedAt ?? null,
|
|
1219
|
+
closedByActor: raw.closedByActor ?? null
|
|
1219
1220
|
};
|
|
1220
1221
|
}
|
|
1221
1222
|
function mapStateReason(reason) {
|
|
@@ -1308,6 +1309,7 @@ function buildBatchQuery(tranche, tasks) {
|
|
|
1308
1309
|
timelineItems(last: 1, itemTypes: [CLOSED_EVENT]) {
|
|
1309
1310
|
nodes {
|
|
1310
1311
|
... on ClosedEvent {
|
|
1312
|
+
actor { login }
|
|
1311
1313
|
closer {
|
|
1312
1314
|
... on PullRequest {
|
|
1313
1315
|
number
|
|
@@ -1345,7 +1347,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1345
1347
|
const issue = repository[`${alias}_issue`];
|
|
1346
1348
|
const ref = repository[`${alias}_ref`];
|
|
1347
1349
|
const prs = repository[`${alias}_prs`];
|
|
1348
|
-
const
|
|
1350
|
+
const timelineNode = issue?.timelineItems?.nodes?.[0];
|
|
1351
|
+
const rawCloser = timelineNode?.closer ?? null;
|
|
1349
1352
|
const closingPr = rawCloser && typeof rawCloser.number === "number" && typeof rawCloser.url === "string" ? rawCloser : null;
|
|
1350
1353
|
const branchPr = prs && prs.nodes.length > 0 && prs.nodes[0] ? prs.nodes[0] : null;
|
|
1351
1354
|
return {
|
|
@@ -1357,7 +1360,8 @@ function extractRawFromResponse(repository, alias) {
|
|
|
1357
1360
|
labels: issue.labels.nodes.map((n) => n.name)
|
|
1358
1361
|
} : null,
|
|
1359
1362
|
refExists: Boolean(ref && ref.name.length > 0),
|
|
1360
|
-
pullRequest: closingPr ?? branchPr
|
|
1363
|
+
pullRequest: closingPr ?? branchPr,
|
|
1364
|
+
closedByActor: timelineNode?.actor?.login ?? null
|
|
1361
1365
|
};
|
|
1362
1366
|
}
|
|
1363
1367
|
function describeError(err) {
|
|
@@ -1495,6 +1499,11 @@ var FORGE_FACT_INPUTS = [
|
|
|
1495
1499
|
fact: "mergedAt",
|
|
1496
1500
|
readsFrom: "PullRequest.mergedAt",
|
|
1497
1501
|
meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
|
|
1502
|
+
},
|
|
1503
|
+
{
|
|
1504
|
+
fact: "closedByActor",
|
|
1505
|
+
readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
|
|
1506
|
+
meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
|
|
1498
1507
|
}
|
|
1499
1508
|
];
|
|
1500
1509
|
var DERIVED_STATUSES = [
|
|
@@ -1974,7 +1983,7 @@ function isGrandfathered(isoDate) {
|
|
|
1974
1983
|
return isoDate.slice(0, 10) < COHERENCE_ENFORCED_FROM;
|
|
1975
1984
|
}
|
|
1976
1985
|
var R1_GRANDFATHERED_ISSUES = new Set([279, 280, 281, 282]);
|
|
1977
|
-
function checkA1(entries) {
|
|
1986
|
+
function checkA1(entries, principalAllowlist = PRINCIPAL_ALLOWLIST) {
|
|
1978
1987
|
const failures = [];
|
|
1979
1988
|
for (const e of entries) {
|
|
1980
1989
|
if (!e.facts)
|
|
@@ -1982,6 +1991,9 @@ function checkA1(entries) {
|
|
|
1982
1991
|
if (e.facts.stateReason === "not_planned")
|
|
1983
1992
|
continue;
|
|
1984
1993
|
if (e.facts.issueState === "closed" && e.facts.prState !== "merged") {
|
|
1994
|
+
const handClosed = e.facts.stateReason === "completed" && isPrincipal(e.facts.closedByActor, principalAllowlist);
|
|
1995
|
+
if (handClosed)
|
|
1996
|
+
continue;
|
|
1985
1997
|
failures.push({
|
|
1986
1998
|
issue: e.task.issue,
|
|
1987
1999
|
tranche: e.trancheSlug,
|
|
@@ -2671,9 +2683,13 @@ function parseGlossaryTerms(glossaryContent) {
|
|
|
2671
2683
|
return terms;
|
|
2672
2684
|
}
|
|
2673
2685
|
// ../../packages/aeg-core/src/dispatch-gate.ts
|
|
2686
|
+
function isHandClosedByRecognizedPrincipal(dep, principalAllowlist) {
|
|
2687
|
+
return dep.issueState === "closed" && dep.stateReason === "completed" && isPrincipal(dep.closedByActor ?? null, principalAllowlist);
|
|
2688
|
+
}
|
|
2674
2689
|
function checkDispatchReadiness(input) {
|
|
2675
2690
|
const { trancheSlug, task } = input;
|
|
2676
2691
|
const taskLabel = `task ${task.id} (tranche ${trancheSlug})`;
|
|
2692
|
+
const principalAllowlist = input.principalAllowlist ?? PRINCIPAL_ALLOWLIST;
|
|
2677
2693
|
const blockers = [];
|
|
2678
2694
|
if (task.issue === null) {
|
|
2679
2695
|
blockers.push(`dispatch-gate issue-existence: ${taskLabel} has no Issue (#TBD or blank) in the topology — not dispatchable until the Planner cuts the Issue.`);
|
|
@@ -2684,7 +2700,7 @@ function checkDispatchReadiness(input) {
|
|
|
2684
2700
|
blockers.push(`dispatch-gate rationale: Issue #${input.issue.number} for ${taskLabel} fails the rationale gate (checkIssueRationale) — the Planner must complete the eight-field rationale before this task is dispatchable.`);
|
|
2685
2701
|
}
|
|
2686
2702
|
for (const dep of input.dependsOn) {
|
|
2687
|
-
if (!dep.merged) {
|
|
2703
|
+
if (!dep.merged && !isHandClosedByRecognizedPrincipal(dep, principalAllowlist)) {
|
|
2688
2704
|
const issueStr = dep.issue !== null ? ` (#${dep.issue})` : "";
|
|
2689
2705
|
blockers.push(`dispatch-gate depends-on: ${taskLabel} depends on ${dep.id}${issueStr}, whose PR is not merged yet — not dispatchable, it serializes behind it.`);
|
|
2690
2706
|
}
|
package/dist/index.js
CHANGED
|
@@ -424,6 +424,11 @@ var FORGE_FACT_INPUTS = [
|
|
|
424
424
|
fact: "mergedAt",
|
|
425
425
|
readsFrom: "PullRequest.mergedAt",
|
|
426
426
|
meaning: "Timestamp for the coherence oracle grandfather cutoff. No derivation rule reads it."
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
fact: "closedByActor",
|
|
430
|
+
readsFrom: "ClosedEvent.actor.login (most recent timeline CLOSED_EVENT)",
|
|
431
|
+
meaning: "GitHub login that hand-closed the Issue. No task-status derivation rule reads it — it is consumed by dispatch-gate.ts/coherence-checks.ts A1 to recognize a hand-closed dependency as resolved (task vinaya-engine-v1 21, #99), not by this status model."
|
|
427
432
|
}
|
|
428
433
|
];
|
|
429
434
|
var DERIVED_STATUSES = [
|
|
@@ -2965,8 +2970,7 @@ function coreCheckRegistry() {
|
|
|
2965
2970
|
BASE_SHA: { optional: true },
|
|
2966
2971
|
PR_BODY: { optional: true },
|
|
2967
2972
|
PR_BODY_FILE: { optional: true },
|
|
2968
|
-
|
|
2969
|
-
WAIVER_LABEL_ACTOR: { optional: true },
|
|
2973
|
+
PR_NUMBER: { optional: true },
|
|
2970
2974
|
GITHUB_REPOSITORY: { optional: true },
|
|
2971
2975
|
GITHUB_TOKEN: { optional: true },
|
|
2972
2976
|
GH_TOKEN: { optional: true }
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@attalabs/vinaya",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.2",
|
|
4
4
|
"description": "Vinaya — Agentic Engineering Harness. Deterministic checks every AI coding agent must satisfy before merge.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -50,10 +50,10 @@
|
|
|
50
50
|
"zod": "3.25.76"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
|
-
"@
|
|
54
|
-
"@
|
|
53
|
+
"@attalabs/aeg-core": "workspace:*",
|
|
54
|
+
"@attalabs/aeg-forge-state": "workspace:*",
|
|
55
55
|
"@atta/typescript-config": "workspace:*",
|
|
56
|
-
"@
|
|
56
|
+
"@attalabs/vinaya-sources": "workspace:*",
|
|
57
57
|
"@types/bun": "latest",
|
|
58
58
|
"typescript": "^5.5.0"
|
|
59
59
|
}
|