@patronage/software-factory 1.0.0-alpha.28 → 1.0.0-alpha.29
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/index.d.ts +113 -12
- package/dist/index.js +320 -30
- package/dist/schemas.d.ts +51 -0
- package/dist/schemas.js +11 -0
- package/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1360,8 +1360,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
1360
1360
|
generationId: z.ZodNumber;
|
|
1361
1361
|
headSha: z.ZodString;
|
|
1362
1362
|
outcome: z.ZodEnum<{
|
|
1363
|
-
stale: "stale";
|
|
1364
1363
|
active: "active";
|
|
1364
|
+
stale: "stale";
|
|
1365
1365
|
}>;
|
|
1366
1366
|
reason: z.ZodString;
|
|
1367
1367
|
recordedAt: z.ZodISODateTime;
|
|
@@ -1609,9 +1609,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
|
|
|
1609
1609
|
reviewedPatchId: z.ZodOptional<z.ZodString>;
|
|
1610
1610
|
status: z.ZodEnum<{
|
|
1611
1611
|
"not-required": "not-required";
|
|
1612
|
+
stale: "stale";
|
|
1612
1613
|
blocked: "blocked";
|
|
1613
1614
|
current: "current";
|
|
1614
|
-
stale: "stale";
|
|
1615
1615
|
missing: "missing";
|
|
1616
1616
|
}>;
|
|
1617
1617
|
}, z.core.$strip>;
|
|
@@ -1621,9 +1621,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
|
|
|
1621
1621
|
reviewedPatchId: z.ZodOptional<z.ZodString>;
|
|
1622
1622
|
status: z.ZodEnum<{
|
|
1623
1623
|
"not-required": "not-required";
|
|
1624
|
+
stale: "stale";
|
|
1624
1625
|
blocked: "blocked";
|
|
1625
1626
|
current: "current";
|
|
1626
|
-
stale: "stale";
|
|
1627
1627
|
missing: "missing";
|
|
1628
1628
|
}>;
|
|
1629
1629
|
}, z.core.$strip>>;
|
|
@@ -1657,7 +1657,7 @@ type ReadinessStatus = "ready" | "blocked";
|
|
|
1657
1657
|
type ManagedReadinessLedger = z.infer<typeof managedReadinessLedgerSchema>;
|
|
1658
1658
|
declare function validateManagedReadinessLedger(value: unknown): ManagedReadinessLedger;
|
|
1659
1659
|
declare namespace status_check_rollup_d_exports {
|
|
1660
|
-
export { CheckState, HOSTED_VERIFY_CHECK_NAME, StatusCheckRollup, hostedVerifyCheckState, isFactoryReadyCheck, isHostedVerifyCheck, pendingCheckNames, statusCheckState };
|
|
1660
|
+
export { CheckState, HOSTED_VERIFY_CHECK_NAME, HOSTED_VERIFY_DRAFT_CHECK_NAME, StatusCheckRollup, hostedVerifyCheckState, isDraftHostedVerifyCheck, isExcludedFromAdmissionChecks, isFactoryReadyCheck, isHostedVerifyCheck, isSupersededHostedVerifyCheck, pendingCheckNames, statusCheckState };
|
|
1661
1661
|
}
|
|
1662
1662
|
type CheckState = "passed" | "failed" | "pending" | "none" | "unknown";
|
|
1663
1663
|
interface StatusCheckRollup {
|
|
@@ -1691,6 +1691,39 @@ declare const HOSTED_VERIFY_CHECK_NAME = "verify";
|
|
|
1691
1691
|
* would accept exactly the thing the pin rejects.
|
|
1692
1692
|
*/
|
|
1693
1693
|
declare const isHostedVerifyCheck: (check: StatusCheckRollup) => boolean;
|
|
1694
|
+
/**
|
|
1695
|
+
* The name the hosted summary job takes on a DRAFT head (#1045).
|
|
1696
|
+
*
|
|
1697
|
+
* The workflow renames that job so the pinned `verify` context does not exist
|
|
1698
|
+
* while the candidate is a draft. The generator that emits the workflow owns
|
|
1699
|
+
* the literal; this is the reader's copy of it, and
|
|
1700
|
+
* `software-factory-hq/alchemy/verify-workflow.test.ts` pins the two equal.
|
|
1701
|
+
*/
|
|
1702
|
+
declare const HOSTED_VERIFY_DRAFT_CHECK_NAME = "verify (draft)";
|
|
1703
|
+
/**
|
|
1704
|
+
* Is this the draft-head summary run's residue?
|
|
1705
|
+
*
|
|
1706
|
+
* The draft run and the `ready_for_review` run land on the SAME head SHA, and
|
|
1707
|
+
* they post two different contexts — `verify (draft)` and `verify` — which
|
|
1708
|
+
* `effectiveChecks` keys separately and therefore never collapses. The Verify
|
|
1709
|
+
* workflow cancels an in-flight pull-request run when the next one starts, and
|
|
1710
|
+
* `ready_for_review` IS the next one, so undrafting typically cancels the
|
|
1711
|
+
* draft run mid-flight. That leaves a CANCELLED `verify (draft)` row sitting
|
|
1712
|
+
* next to a green `verify` on the admitted head.
|
|
1713
|
+
*
|
|
1714
|
+
* GitHub does not care: the branch ruleset pins `verify` alone. The factory's
|
|
1715
|
+
* own rollup read would: `CANCELLED` is a failed state, so the candidate's
|
|
1716
|
+
* `requiredChecks` would read `failed` forever and no push-free repair could
|
|
1717
|
+
* clear it. The draft run is a superseded generation of the summary job the
|
|
1718
|
+
* `verify` row already answers for, so admission does not judge it.
|
|
1719
|
+
*/
|
|
1720
|
+
declare const isDraftHostedVerifyCheck: (check: StatusCheckRollup) => boolean;
|
|
1721
|
+
/**
|
|
1722
|
+
* The rollup rows an admission read must not judge: the factory's own branded
|
|
1723
|
+
* checks (it would be judging itself) and the draft-head summary residue
|
|
1724
|
+
* above.
|
|
1725
|
+
*/
|
|
1726
|
+
declare const isExcludedFromAdmissionChecks: (check: StatusCheckRollup) => boolean;
|
|
1694
1727
|
/**
|
|
1695
1728
|
* The hosted `verify` gate's state on this head, read by presence (#477).
|
|
1696
1729
|
*
|
|
@@ -1716,6 +1749,25 @@ declare const hostedVerifyCheckState: (rollup: StatusCheckRollup[] | undefined)
|
|
|
1716
1749
|
*/
|
|
1717
1750
|
declare const pendingCheckNames: (rollup: StatusCheckRollup[] | undefined, exclude?: (check: StatusCheckRollup) => boolean) => string[];
|
|
1718
1751
|
declare const statusCheckState: (rollup: StatusCheckRollup[] | undefined, exclude?: (check: StatusCheckRollup) => boolean) => CheckState;
|
|
1752
|
+
/**
|
|
1753
|
+
* Is this a hosted `verify` check run that GitHub started before `cutoffIso`?
|
|
1754
|
+
* (#1045)
|
|
1755
|
+
*
|
|
1756
|
+
* A caller that waits for `verify` on a head it just changed the state of —
|
|
1757
|
+
* `pr:publish` undrafting a candidate — must not accept the run that was
|
|
1758
|
+
* already there. #1034's history is the shape: a draft head carried a `verify`
|
|
1759
|
+
* that had run nothing, the wait saw it as terminal on its first attempt, and
|
|
1760
|
+
* readiness read `passed` about two minutes before the `ready_for_review` run
|
|
1761
|
+
* created its own check runs. The same shape is reachable from any superseded
|
|
1762
|
+
* `verify` generation still on the head.
|
|
1763
|
+
*
|
|
1764
|
+
* Fail CLOSED on an unreadable timestamp: a `verify` row that cannot be shown
|
|
1765
|
+
* to be newer than the cutoff is treated as superseded. The wait is bounded,
|
|
1766
|
+
* so the cost of that refusal is one timeout that falls through to the
|
|
1767
|
+
* caller's existing behaviour; the cost of failing open is the false green
|
|
1768
|
+
* this exists to remove.
|
|
1769
|
+
*/
|
|
1770
|
+
declare const isSupersededHostedVerifyCheck: (check: StatusCheckRollup, cutoffIso: string) => boolean;
|
|
1719
1771
|
//#endregion
|
|
1720
1772
|
//#region src/github-client.d.ts
|
|
1721
1773
|
/** Read a branch tip from GitHub rather than a checkout's cached remote ref. */
|
|
@@ -1782,6 +1834,33 @@ interface PrVerifyCommandResult {
|
|
|
1782
1834
|
name: string;
|
|
1783
1835
|
scope: "docs-only" | "trivial" | "full";
|
|
1784
1836
|
}
|
|
1837
|
+
/**
|
|
1838
|
+
* One failing test a verification command reported (#1035).
|
|
1839
|
+
*
|
|
1840
|
+
* The proof keeps the failing test's IDENTITY and its failure CLASS, and
|
|
1841
|
+
* nothing else. That is enough to decide retry-or-fix — a `timeout` under
|
|
1842
|
+
* machine load is a flake, an `assertion` is a real break — without rerunning
|
|
1843
|
+
* the aggregate to read a title off the console.
|
|
1844
|
+
*
|
|
1845
|
+
* It deliberately keeps no raw output tail and no error message body. This
|
|
1846
|
+
* payload is transmitted to HQ ingest, and arbitrary command output can carry
|
|
1847
|
+
* secrets (an echoed environment, a connection string inside a stack frame). A
|
|
1848
|
+
* file path, a test title, and a closed `kind` enum cannot. The cost is the
|
|
1849
|
+
* assertion diff and the stack frames: they help you FIX a failure, and the
|
|
1850
|
+
* operator still reads them from the run's own console output.
|
|
1851
|
+
*/
|
|
1852
|
+
interface PrVerifyTestFailure {
|
|
1853
|
+
/** The test file the reporter named, as the reporter wrote it. */
|
|
1854
|
+
file: string;
|
|
1855
|
+
/**
|
|
1856
|
+
* The failure class. `timeout` is the test-timeout error vitest raises at
|
|
1857
|
+
* its own ceiling; `assertion` is a failed expectation; `error` is every
|
|
1858
|
+
* other thrown failure.
|
|
1859
|
+
*/
|
|
1860
|
+
kind: "assertion" | "error" | "timeout";
|
|
1861
|
+
/** The full test title, suite names included, as the reporter wrote it. */
|
|
1862
|
+
title: string;
|
|
1863
|
+
}
|
|
1785
1864
|
interface PrVerifyExecutedCommand {
|
|
1786
1865
|
command: string;
|
|
1787
1866
|
counts?: {
|
|
@@ -1790,6 +1869,12 @@ interface PrVerifyExecutedCommand {
|
|
|
1790
1869
|
};
|
|
1791
1870
|
durationMs: number;
|
|
1792
1871
|
exitCode: number;
|
|
1872
|
+
/**
|
|
1873
|
+
* The failing tests this command reported (#1035). Present only when the
|
|
1874
|
+
* command produced parseable failures, so a passing command's record is
|
|
1875
|
+
* byte-identical to the one it wrote before this field existed.
|
|
1876
|
+
*/
|
|
1877
|
+
failures?: PrVerifyTestFailure[];
|
|
1793
1878
|
name: string;
|
|
1794
1879
|
scope: "docs-only" | "trivial" | "full";
|
|
1795
1880
|
}
|
|
@@ -1864,7 +1949,7 @@ interface PrVerifyProof {
|
|
|
1864
1949
|
type PrVerifyBaselineFullProof = NonNullable<PrVerifyProof["baselineFullProofs"]>[number];
|
|
1865
1950
|
declare function validatePrVerifyProof(value: unknown): PrVerifyProof;
|
|
1866
1951
|
declare namespace verification_proof_d_exports {
|
|
1867
|
-
export { PrVerifyBaselineFullProof, PrVerifyCommandResult, PrVerifyExecutedCommand, PrVerifyNotDemandedRecord, PrVerifyNotRequiredCommand, PrVerifyProof, PrVerifySchemaVersion, ResolvedPrVerifyMode, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, VerificationHeadShas, VerificationProofState, VerifiedHeadShas, prVerifyFullHeadShasForDiff, prVerifyProofHeadShas, readPrVerifyProof, resolveVerifyProofApplicability, trustedCatchUpRecognition, trustedImpactStamp, validatePrVerifyProof, verificationProofApplicabilityFor, verificationProofBlockingReason, verificationProofForReadiness, verificationProofStateFor, verifyProofIdentityMatches, verifyProofPassed };
|
|
1952
|
+
export { PrVerifyBaselineFullProof, PrVerifyCommandResult, PrVerifyExecutedCommand, PrVerifyNotDemandedRecord, PrVerifyNotRequiredCommand, PrVerifyProof, PrVerifySchemaVersion, PrVerifyTestFailure, ResolvedPrVerifyMode, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, VerificationHeadShas, VerificationProofState, VerifiedHeadShas, prVerifyFullHeadShasForDiff, prVerifyProofHeadShas, readPrVerifyProof, resolveVerifyProofApplicability, trustedCatchUpRecognition, trustedImpactStamp, validatePrVerifyProof, verificationProofApplicabilityFor, verificationProofBlockingReason, verificationProofForReadiness, verificationProofStateFor, verifyProofIdentityMatches, verifyProofPassed };
|
|
1868
1953
|
}
|
|
1869
1954
|
interface VerifiedHeadShas {
|
|
1870
1955
|
docsOnlyVerifiedHeadShas: string[];
|
|
@@ -2334,6 +2419,14 @@ interface PrReadyProof {
|
|
|
2334
2419
|
headSha: string;
|
|
2335
2420
|
outcome: "armed" | "merged" | "not-armed";
|
|
2336
2421
|
};
|
|
2422
|
+
/**
|
|
2423
|
+
* The authoring session readiness resolved for the review-independence
|
|
2424
|
+
* cross-check (#1037). Recorded so the branded check can print it beside the
|
|
2425
|
+
* review session, model, and producer the ledger already carries — the four
|
|
2426
|
+
* identities a human weighs at merge time. Additive and optional: proofs
|
|
2427
|
+
* written before it carry no field, so no schema version moves.
|
|
2428
|
+
*/
|
|
2429
|
+
authoringSession?: string;
|
|
2337
2430
|
/**
|
|
2338
2431
|
* Why this run blocked, one entry per refusing demand (#391): `code` is the
|
|
2339
2432
|
* demand key from the resolver's vocabulary, `detail` the one-sentence
|
|
@@ -2639,9 +2732,9 @@ declare const loadEvidenceEnvelopes: (cwd: string) => LoadedEvidenceEnvelope[];
|
|
|
2639
2732
|
declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale", "missing", "blocked"];
|
|
2640
2733
|
declare const reviewStatusSchema: z.ZodEnum<{
|
|
2641
2734
|
"not-required": "not-required";
|
|
2735
|
+
stale: "stale";
|
|
2642
2736
|
blocked: "blocked";
|
|
2643
2737
|
current: "current";
|
|
2644
|
-
stale: "stale";
|
|
2645
2738
|
missing: "missing";
|
|
2646
2739
|
}>;
|
|
2647
2740
|
type ReviewStatus = z.infer<typeof reviewStatusSchema>;
|
|
@@ -3014,6 +3107,8 @@ interface VerificationCommandOutcome {
|
|
|
3014
3107
|
};
|
|
3015
3108
|
durationMs: number;
|
|
3016
3109
|
exitCode: number;
|
|
3110
|
+
/** The failing tests scraped from this command's output (#1035). */
|
|
3111
|
+
failures?: PrVerifyTestFailure[];
|
|
3017
3112
|
}
|
|
3018
3113
|
interface PrVerifyArgs extends LoadProjectProfileInput {
|
|
3019
3114
|
authoringSession?: string;
|
|
@@ -3150,6 +3245,12 @@ interface PrPublishDependencies extends PrReadyDependencies {
|
|
|
3150
3245
|
cwd: string;
|
|
3151
3246
|
}) => Promise<HqFlushResult>;
|
|
3152
3247
|
runPrVerify?: (args: PrVerifyArgs) => Promise<PrVerifyProof>;
|
|
3248
|
+
/**
|
|
3249
|
+
* Injectable clock for the promotion instant the hosted-run await measures
|
|
3250
|
+
* `verify` staleness against (#1045; tests only — production reads the real
|
|
3251
|
+
* clock).
|
|
3252
|
+
*/
|
|
3253
|
+
now?: () => Date;
|
|
3153
3254
|
/**
|
|
3154
3255
|
* Injectable delay for the bounded hosted-run await (#348; tests only —
|
|
3155
3256
|
* production always waits the real interval).
|
|
@@ -3885,8 +3986,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
|
|
|
3885
3986
|
interiorCycle: z.ZodOptional<z.ZodNumber>;
|
|
3886
3987
|
observedAt: z.ZodString;
|
|
3887
3988
|
purpose: z.ZodEnum<{
|
|
3888
|
-
review: "review";
|
|
3889
3989
|
code: "code";
|
|
3990
|
+
review: "review";
|
|
3890
3991
|
}>;
|
|
3891
3992
|
slotResolution: z.ZodObject<{
|
|
3892
3993
|
effort: z.ZodEnum<{
|
|
@@ -3964,8 +4065,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
|
|
|
3964
4065
|
interiorCycle: z.ZodOptional<z.ZodNumber>;
|
|
3965
4066
|
observedAt: z.ZodString;
|
|
3966
4067
|
purpose: z.ZodEnum<{
|
|
3967
|
-
review: "review";
|
|
3968
4068
|
code: "code";
|
|
4069
|
+
review: "review";
|
|
3969
4070
|
}>;
|
|
3970
4071
|
slotResolution: z.ZodObject<{
|
|
3971
4072
|
effort: z.ZodEnum<{
|
|
@@ -4034,8 +4135,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
|
|
|
4034
4135
|
interiorCycle: z.ZodOptional<z.ZodNumber>;
|
|
4035
4136
|
observedAt: z.ZodString;
|
|
4036
4137
|
purpose: z.ZodEnum<{
|
|
4037
|
-
review: "review";
|
|
4038
4138
|
code: "code";
|
|
4139
|
+
review: "review";
|
|
4039
4140
|
}>;
|
|
4040
4141
|
slotResolution: z.ZodObject<{
|
|
4041
4142
|
effort: z.ZodEnum<{
|
|
@@ -4107,7 +4208,7 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
|
|
|
4107
4208
|
exitCode: number | null;
|
|
4108
4209
|
harness: string;
|
|
4109
4210
|
observedAt: string;
|
|
4110
|
-
purpose: "
|
|
4211
|
+
purpose: "code" | "review";
|
|
4111
4212
|
slotResolution: {
|
|
4112
4213
|
effort: "high" | "low" | "medium" | "xhigh";
|
|
4113
4214
|
engine: string;
|
|
@@ -4461,13 +4562,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
|
|
|
4461
4562
|
reviews: {
|
|
4462
4563
|
correctness: {
|
|
4463
4564
|
required: boolean;
|
|
4464
|
-
status: "not-required" | "
|
|
4565
|
+
status: "not-required" | "stale" | "blocked" | "current" | "missing";
|
|
4465
4566
|
reviewedHeadSha?: string | undefined;
|
|
4466
4567
|
reviewedPatchId?: string | undefined;
|
|
4467
4568
|
};
|
|
4468
4569
|
security?: {
|
|
4469
4570
|
required: boolean;
|
|
4470
|
-
status: "not-required" | "
|
|
4571
|
+
status: "not-required" | "stale" | "blocked" | "current" | "missing";
|
|
4471
4572
|
reviewedHeadSha?: string | undefined;
|
|
4472
4573
|
reviewedPatchId?: string | undefined;
|
|
4473
4574
|
} | undefined;
|
package/dist/index.js
CHANGED
|
@@ -18,7 +18,7 @@ import { FACTORY_APP_BROKER_ORIGIN, FACTORY_APP_BROKER_PATH, FACTORY_CURSOR_OIDC
|
|
|
18
18
|
import http from "node:http";
|
|
19
19
|
import { pathToFileURL } from "node:url";
|
|
20
20
|
//#region package.json
|
|
21
|
-
var version = "1.0.0-alpha.
|
|
21
|
+
var version = "1.0.0-alpha.29";
|
|
22
22
|
//#endregion
|
|
23
23
|
//#region src/cli-entry.ts
|
|
24
24
|
/**
|
|
@@ -6576,10 +6576,48 @@ function verifyOutput(proof) {
|
|
|
6576
6576
|
title: `pr:verify ${value.outcome ?? "unknown"}`
|
|
6577
6577
|
};
|
|
6578
6578
|
}
|
|
6579
|
+
/**
|
|
6580
|
+
* The refusals to name, preferring the attributed projection (#391) so each
|
|
6581
|
+
* line carries its demand code. `blockingReasons` is the fallback because the
|
|
6582
|
+
* throwing path in `pr:ready` publishes a proof that carries only that field.
|
|
6583
|
+
* The two projections hold the same refusals in the same order, so reading
|
|
6584
|
+
* either one tells the same story.
|
|
6585
|
+
*/
|
|
6586
|
+
const readyReasonLines = (value) => {
|
|
6587
|
+
const named = value.blockedReasons ?? [];
|
|
6588
|
+
if (named.length > 0) return named.map((reason) => ({
|
|
6589
|
+
...reason.code === void 0 ? {} : { code: reason.code },
|
|
6590
|
+
detail: reason.detail ?? "unrecorded"
|
|
6591
|
+
}));
|
|
6592
|
+
return (value.blockingReasons ?? []).map((reason) => ({ detail: typeof reason === "string" ? reason : "unrecorded" }));
|
|
6593
|
+
};
|
|
6594
|
+
const readyReasonBlock = (reasons) => {
|
|
6595
|
+
return `\n\n${reasons.length === 1 ? "1 blocking reason:" : `${reasons.length} blocking reasons:`}\n\n${reasons.map((reason) => `- ${reason.code === void 0 ? "" : `**${reason.code}** — `}${reason.detail}`).join("\n")}`;
|
|
6596
|
+
};
|
|
6597
|
+
const readyIdentityField = (value) => value === void 0 || value.trim().length === 0 ? "unrecorded" : `\`${value}\``;
|
|
6598
|
+
/**
|
|
6599
|
+
* #1037: the review identity the proof already records, printed where the
|
|
6600
|
+
* merge decision is made. `pr:ready` recorded the review session, model, and
|
|
6601
|
+
* producer, and the authoring session it was checked against, but only the PR
|
|
6602
|
+
* body prose showed them — so a human merging from the checks page could not
|
|
6603
|
+
* see which model produced the review that admitted the candidate.
|
|
6604
|
+
*
|
|
6605
|
+
* This is recording made visible, not a control. The attended merge is the
|
|
6606
|
+
* control.
|
|
6607
|
+
*/
|
|
6608
|
+
const readyIdentityBlock = (value) => {
|
|
6609
|
+
const runs = value.ledger?.reviewRuns ?? [];
|
|
6610
|
+
if (runs.length === 0 && value.authoringSession === void 0) return "";
|
|
6611
|
+
return `\n\nRecorded review identity:\n\n${[...runs.map((run) => {
|
|
6612
|
+
const rung = run.rung === void 0 ? "" : ` at rung \`${run.rung}\``;
|
|
6613
|
+
return `- ${run.kind ?? "review"}${rung}: session ${readyIdentityField(run.sessionId)} · model ${readyIdentityField(run.model)} · producer ${readyIdentityField(run.producer)}`;
|
|
6614
|
+
}), `- authoring session: ${readyIdentityField(value.authoringSession)}`].join("\n")}`;
|
|
6615
|
+
};
|
|
6579
6616
|
function readyOutput(proof) {
|
|
6580
6617
|
const value = proof;
|
|
6618
|
+
const reasons = readyReasonLines(value);
|
|
6581
6619
|
return {
|
|
6582
|
-
summary: `Schema v${value.schemaVersion}; readiness verdict **${value.status ?? "unknown"}
|
|
6620
|
+
summary: `${`Schema v${value.schemaVersion}; readiness verdict **${value.status ?? "unknown"}**;${reasons.length === 0 ? " no blocking reasons;" : ""} ledger head \`${value.ledger?.headSha ?? "unknown"}\`.`}${reasons.length === 0 ? "" : readyReasonBlock(reasons)}${readyIdentityBlock(value)}`,
|
|
6583
6621
|
title: `pr:ready ${value.status ?? "unknown"}`
|
|
6584
6622
|
};
|
|
6585
6623
|
}
|
|
@@ -7508,6 +7546,15 @@ const assertPrVerifyProofRules = (proof, context) => {
|
|
|
7508
7546
|
* factory must adopt this release before HQ ingests its pr:verify proofs.
|
|
7509
7547
|
*/
|
|
7510
7548
|
const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS = [4];
|
|
7549
|
+
const prVerifyTestFailureSchema = z.object({
|
|
7550
|
+
file: z.string().min(1),
|
|
7551
|
+
kind: z.enum([
|
|
7552
|
+
"assertion",
|
|
7553
|
+
"error",
|
|
7554
|
+
"timeout"
|
|
7555
|
+
]),
|
|
7556
|
+
title: z.string().min(1)
|
|
7557
|
+
});
|
|
7511
7558
|
const executedCommandSchema = z.object({
|
|
7512
7559
|
command: z.string().min(1),
|
|
7513
7560
|
counts: z.object({
|
|
@@ -7516,6 +7563,7 @@ const executedCommandSchema = z.object({
|
|
|
7516
7563
|
}).optional(),
|
|
7517
7564
|
durationMs: z.number().nonnegative(),
|
|
7518
7565
|
exitCode: z.number(),
|
|
7566
|
+
failures: z.array(prVerifyTestFailureSchema).min(1).optional(),
|
|
7519
7567
|
name: z.string().min(1),
|
|
7520
7568
|
scope: z.enum([
|
|
7521
7569
|
"docs-only",
|
|
@@ -7797,6 +7845,13 @@ const verificationProofForReadiness = (state) => {
|
|
|
7797
7845
|
const failedExecutedCommand = (proof) => {
|
|
7798
7846
|
return proof.executedCommands?.find((command) => command.exitCode !== 0) ?? proof.executedCommands?.at(-1);
|
|
7799
7847
|
};
|
|
7848
|
+
const MAX_NAMED_TEST_FAILURES = 3;
|
|
7849
|
+
const formatFailedTests = (failures) => {
|
|
7850
|
+
if (!failures || failures.length === 0) return "";
|
|
7851
|
+
const named = failures.slice(0, MAX_NAMED_TEST_FAILURES).map((failure) => `${failure.file} > ${failure.title} (${failure.kind})`).join("; ");
|
|
7852
|
+
const remaining = failures.length - MAX_NAMED_TEST_FAILURES;
|
|
7853
|
+
return remaining > 0 ? ` on ${named}; and ${remaining} more failing test${remaining === 1 ? "" : "s"}` : ` on ${named}`;
|
|
7854
|
+
};
|
|
7800
7855
|
const verificationProofBlockingReason = (state) => {
|
|
7801
7856
|
if (state.kind === "typed-missing") {
|
|
7802
7857
|
if (state.reason === "enoent") return `Typed pr:verify proof at ${state.path} does not exist; pr:verify has not run for this PR head. Run patronage-factory pr:verify.`;
|
|
@@ -7808,7 +7863,7 @@ const verificationProofBlockingReason = (state) => {
|
|
|
7808
7863
|
}
|
|
7809
7864
|
if (state.kind === "typed-aborted") {
|
|
7810
7865
|
const failed = failedExecutedCommand(state.proof);
|
|
7811
|
-
if (failed) return `Typed pr:verify proof shows verification aborted at ${failed.name} (exit code ${failed.exitCode}); verification did not pass. Re-run patronage-factory pr:verify.`;
|
|
7866
|
+
if (failed) return `Typed pr:verify proof shows verification aborted at ${failed.name} (exit code ${failed.exitCode})${formatFailedTests(failed.failures)}; verification did not pass. Re-run patronage-factory pr:verify.`;
|
|
7812
7867
|
return "Typed pr:verify proof shows verification aborted; verification did not pass. Re-run patronage-factory pr:verify.";
|
|
7813
7868
|
}
|
|
7814
7869
|
};
|
|
@@ -8036,6 +8091,111 @@ function parseVitestCounts(stdout) {
|
|
|
8036
8091
|
if (testsMatch) counts.tests = Number(testsMatch.groups?.total ?? testsMatch.groups?.passed);
|
|
8037
8092
|
return counts;
|
|
8038
8093
|
}
|
|
8094
|
+
const MAX_RECORDED_TEST_FAILURES = 50;
|
|
8095
|
+
const ANSI_ESCAPE_PATTERN = /\u001B\[[0-9;]*m/gu;
|
|
8096
|
+
/**
|
|
8097
|
+
* Vitest's per-failure header: `FAIL <file> > <suite> > <test title>`. This
|
|
8098
|
+
* pattern claims only the `FAIL ` prefix; `splitVitestFailHeader` below reads
|
|
8099
|
+
* the rest, because a multi-project run puts a project label between `FAIL`
|
|
8100
|
+
* and the file and its shape depends on color support.
|
|
8101
|
+
*/
|
|
8102
|
+
const VITEST_FAIL_HEADER_PATTERN = /^\s*FAIL\s+(?<rest>\S.*)$/u;
|
|
8103
|
+
/** Vitest separates the file, each suite, and the test title with ` > `. */
|
|
8104
|
+
const VITEST_TITLE_SEPARATOR = " > ";
|
|
8105
|
+
/**
|
|
8106
|
+
* Splits a failure header into the failing file and the full test title.
|
|
8107
|
+
*
|
|
8108
|
+
* The file is the LAST whitespace-separated token before the first ` > `,
|
|
8109
|
+
* which is what makes this survive a multi-project run. Vitest's
|
|
8110
|
+
* `formatProjectName` writes the project label as `|name|` when color is
|
|
8111
|
+
* unsupported and as a color-boxed ` name ` when it is; after this module
|
|
8112
|
+
* strips the SGR codes both leave the label as its own token ahead of the
|
|
8113
|
+
* file, and both drop out here. The title keeps every suite name, so the
|
|
8114
|
+
* split is on the FIRST separator and never a later one.
|
|
8115
|
+
*
|
|
8116
|
+
* Answers `undefined` for a header that names no test.
|
|
8117
|
+
*/
|
|
8118
|
+
const splitVitestFailHeader = (rest) => {
|
|
8119
|
+
const separatorIndex = rest.indexOf(VITEST_TITLE_SEPARATOR);
|
|
8120
|
+
if (separatorIndex === -1) return;
|
|
8121
|
+
const file = rest.slice(0, separatorIndex).trim().split(/\s+/u).at(-1);
|
|
8122
|
+
const title = rest.slice(separatorIndex + 3).trim();
|
|
8123
|
+
if (!file || title.length === 0) return;
|
|
8124
|
+
return {
|
|
8125
|
+
file,
|
|
8126
|
+
title
|
|
8127
|
+
};
|
|
8128
|
+
};
|
|
8129
|
+
const VITEST_TIMEOUT_PATTERN = /^\s*\S*Error:\s+Test timed out in /u;
|
|
8130
|
+
const VITEST_ASSERTION_PATTERN = /^\s*\S*AssertionError\b/u;
|
|
8131
|
+
/**
|
|
8132
|
+
* Reads the failure class off the error line under a failure header. Anything
|
|
8133
|
+
* that is neither the test-timeout error nor an assertion error — including a
|
|
8134
|
+
* line that is not error-shaped at all — classifies as `error` rather than
|
|
8135
|
+
* guessing at a narrower class.
|
|
8136
|
+
*/
|
|
8137
|
+
const classifyVitestFailure = (errorLine) => {
|
|
8138
|
+
if (errorLine === void 0) return "error";
|
|
8139
|
+
if (VITEST_TIMEOUT_PATTERN.test(errorLine)) return "timeout";
|
|
8140
|
+
if (VITEST_ASSERTION_PATTERN.test(errorLine)) return "assertion";
|
|
8141
|
+
return "error";
|
|
8142
|
+
};
|
|
8143
|
+
/**
|
|
8144
|
+
* Scrapes the failing tests out of a vitest run's console output (#1035).
|
|
8145
|
+
*
|
|
8146
|
+
* Callers pass BOTH captured streams: vitest writes its `Failed Tests`
|
|
8147
|
+
* section to stderr, so stdout alone carries no failure header.
|
|
8148
|
+
*
|
|
8149
|
+
* Deliberately line-shaped, the same class of scan as `parseVitestCounts`
|
|
8150
|
+
* above: it reads the `FAIL <file> > <title>` header and the error line that
|
|
8151
|
+
* follows it, and nothing else. It must never grow toward a parser of vitest's
|
|
8152
|
+
* report. Output it does not recognize yields an empty list, never a throw —
|
|
8153
|
+
* an unreadable report is a proof that records no failures, not a verification
|
|
8154
|
+
* run that crashes while writing why it failed.
|
|
8155
|
+
*/
|
|
8156
|
+
function parseVitestFailures(stdout) {
|
|
8157
|
+
const lines = stdout.replace(ANSI_ESCAPE_PATTERN, "").split(/\r?\n/u);
|
|
8158
|
+
const failures = [];
|
|
8159
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8160
|
+
for (const [index, line] of lines.entries()) {
|
|
8161
|
+
const header = line.match(VITEST_FAIL_HEADER_PATTERN);
|
|
8162
|
+
if (!header?.groups?.rest) continue;
|
|
8163
|
+
const split = splitVitestFailHeader(header.groups.rest);
|
|
8164
|
+
if (!split) continue;
|
|
8165
|
+
const { file, title } = split;
|
|
8166
|
+
const key = `${file}\u0000${title}`;
|
|
8167
|
+
if (seen.has(key)) continue;
|
|
8168
|
+
seen.add(key);
|
|
8169
|
+
const errorLine = lines.slice(index + 1, index + 3).find((candidate) => candidate.trim().length > 0);
|
|
8170
|
+
failures.push({
|
|
8171
|
+
file,
|
|
8172
|
+
kind: classifyVitestFailure(errorLine),
|
|
8173
|
+
title
|
|
8174
|
+
});
|
|
8175
|
+
if (failures.length >= MAX_RECORDED_TEST_FAILURES) break;
|
|
8176
|
+
}
|
|
8177
|
+
return failures;
|
|
8178
|
+
}
|
|
8179
|
+
/**
|
|
8180
|
+
* Reduces one captured verification run to the outcome the proof records.
|
|
8181
|
+
*
|
|
8182
|
+
* Which stream each scan reads is the load-bearing decision here. Vitest's
|
|
8183
|
+
* default reporter prints the run tree and the `Test Files` summary that
|
|
8184
|
+
* `parseVitestCounts` reads to stdout, but prints the whole `Failed Tests`
|
|
8185
|
+
* section — every `FAIL` header and every error line — through
|
|
8186
|
+
* `logger.error`, which is stderr. Scanning stdout alone for failures finds
|
|
8187
|
+
* none at all, so the failure scan reads both streams.
|
|
8188
|
+
*/
|
|
8189
|
+
function verificationOutcomeOf(result) {
|
|
8190
|
+
const counts = parseVitestCounts(result.stdout);
|
|
8191
|
+
const failures = result.exitCode === 0 ? [] : parseVitestFailures(`${result.stdout}\n${result.stderr}`);
|
|
8192
|
+
return {
|
|
8193
|
+
...counts ? { counts } : {},
|
|
8194
|
+
durationMs: result.durationMs,
|
|
8195
|
+
exitCode: result.exitCode,
|
|
8196
|
+
...failures.length > 0 ? { failures } : {}
|
|
8197
|
+
};
|
|
8198
|
+
}
|
|
8039
8199
|
function defaultPrVerifyGit() {
|
|
8040
8200
|
return {
|
|
8041
8201
|
changedFiles,
|
|
@@ -8044,13 +8204,7 @@ function defaultPrVerifyGit() {
|
|
|
8044
8204
|
mergeBaseSha,
|
|
8045
8205
|
recognizeCatchUp: recognizeCatchUpCandidate,
|
|
8046
8206
|
runVerificationCommand(command, cwd, env) {
|
|
8047
|
-
|
|
8048
|
-
const counts = parseVitestCounts(result.stdout);
|
|
8049
|
-
return {
|
|
8050
|
-
...counts ? { counts } : {},
|
|
8051
|
-
durationMs: result.durationMs,
|
|
8052
|
-
exitCode: result.exitCode
|
|
8053
|
-
};
|
|
8207
|
+
return verificationOutcomeOf(runInheritedTee("bash", ["-lc", command], cwd, env));
|
|
8054
8208
|
},
|
|
8055
8209
|
showFileAtRef,
|
|
8056
8210
|
stablePatchId: runStablePatchId,
|
|
@@ -8136,6 +8290,19 @@ function formatVerifyTimingSummary(proof) {
|
|
|
8136
8290
|
}
|
|
8137
8291
|
return lines.join("\n");
|
|
8138
8292
|
}
|
|
8293
|
+
/**
|
|
8294
|
+
* The line `pr:verify` prints last when it aborts (#1035): where the record
|
|
8295
|
+
* is, which command failed, and — when the command reported them — which
|
|
8296
|
+
* tests failed and in what class. A timeout under machine load and a real
|
|
8297
|
+
* assertion look identical without this, so every red verify used to cost a
|
|
8298
|
+
* full visible rerun just to read a test title.
|
|
8299
|
+
*/
|
|
8300
|
+
function formatVerifyFailurePointer(proof, proofPath) {
|
|
8301
|
+
const failed = proof.executedCommands?.find((command) => command.exitCode !== 0);
|
|
8302
|
+
const lines = [failed ? `pr:verify aborted at ${failed.name} (exit code ${failed.exitCode}); record: ${proofPath}` : `pr:verify aborted; record: ${proofPath}`];
|
|
8303
|
+
for (const failure of failed?.failures ?? []) lines.push(` ${failure.kind}: ${failure.file} > ${failure.title}`);
|
|
8304
|
+
return lines.join("\n");
|
|
8305
|
+
}
|
|
8139
8306
|
function resolveMode({ classification, mode }) {
|
|
8140
8307
|
if (mode === "auto") {
|
|
8141
8308
|
if (classification === "docs/process-only") return "docs-only";
|
|
@@ -8299,6 +8466,7 @@ function runPrVerify(args, dependencies = {}) {
|
|
|
8299
8466
|
writeProofCopies(proof);
|
|
8300
8467
|
failureProofWritten = true;
|
|
8301
8468
|
if (executedCommands.length > 0) console.log(`\n${formatVerifyTimingSummary(proof)}`);
|
|
8469
|
+
console.log(`\n${formatVerifyFailurePointer(proof, output)}`);
|
|
8302
8470
|
publishFactoryCheckSafely(publishCheckRun, {
|
|
8303
8471
|
conclusion: "failure",
|
|
8304
8472
|
cwd,
|
|
@@ -8321,7 +8489,8 @@ function runPrVerify(args, dependencies = {}) {
|
|
|
8321
8489
|
exitCode: result.exitCode,
|
|
8322
8490
|
name: command.name,
|
|
8323
8491
|
scope: command.scope,
|
|
8324
|
-
...result.counts ? { counts: result.counts } : {}
|
|
8492
|
+
...result.counts ? { counts: result.counts } : {},
|
|
8493
|
+
...result.failures && result.failures.length > 0 ? { failures: result.failures } : {}
|
|
8325
8494
|
};
|
|
8326
8495
|
executedCommands.push(executedCommand);
|
|
8327
8496
|
if (result.exitCode !== 0) {
|
|
@@ -9706,6 +9875,7 @@ z.object({
|
|
|
9706
9875
|
"not-armed"
|
|
9707
9876
|
])
|
|
9708
9877
|
}).optional(),
|
|
9878
|
+
authoringSession: z.string().min(1).optional(),
|
|
9709
9879
|
blockedReasons: blockedReasonsSchema.optional(),
|
|
9710
9880
|
blockingReasons: z.array(z.string()),
|
|
9711
9881
|
command: z.literal("patronage-factory pr:ready"),
|
|
@@ -14638,9 +14808,13 @@ const renderPrBodySections = ({ reviewProof, verifyProof }) => `${renderPrBodySe
|
|
|
14638
14808
|
//#region src/pr-readiness/status-check-rollup.ts
|
|
14639
14809
|
var status_check_rollup_exports = /* @__PURE__ */ __exportAll({
|
|
14640
14810
|
HOSTED_VERIFY_CHECK_NAME: () => HOSTED_VERIFY_CHECK_NAME$1,
|
|
14811
|
+
HOSTED_VERIFY_DRAFT_CHECK_NAME: () => HOSTED_VERIFY_DRAFT_CHECK_NAME,
|
|
14641
14812
|
hostedVerifyCheckState: () => hostedVerifyCheckState,
|
|
14813
|
+
isDraftHostedVerifyCheck: () => isDraftHostedVerifyCheck,
|
|
14814
|
+
isExcludedFromAdmissionChecks: () => isExcludedFromAdmissionChecks,
|
|
14642
14815
|
isFactoryReadyCheck: () => isFactoryReadyCheck,
|
|
14643
14816
|
isHostedVerifyCheck: () => isHostedVerifyCheck,
|
|
14817
|
+
isSupersededHostedVerifyCheck: () => isSupersededHostedVerifyCheck,
|
|
14644
14818
|
pendingCheckNames: () => pendingCheckNames,
|
|
14645
14819
|
statusCheckState: () => statusCheckState
|
|
14646
14820
|
});
|
|
@@ -14683,6 +14857,39 @@ const HOSTED_VERIFY_CHECK_NAME$1 = "verify";
|
|
|
14683
14857
|
*/
|
|
14684
14858
|
const isHostedVerifyCheck = (check) => check.name === "verify" && Boolean(check.workflowName);
|
|
14685
14859
|
/**
|
|
14860
|
+
* The name the hosted summary job takes on a DRAFT head (#1045).
|
|
14861
|
+
*
|
|
14862
|
+
* The workflow renames that job so the pinned `verify` context does not exist
|
|
14863
|
+
* while the candidate is a draft. The generator that emits the workflow owns
|
|
14864
|
+
* the literal; this is the reader's copy of it, and
|
|
14865
|
+
* `software-factory-hq/alchemy/verify-workflow.test.ts` pins the two equal.
|
|
14866
|
+
*/
|
|
14867
|
+
const HOSTED_VERIFY_DRAFT_CHECK_NAME = `${HOSTED_VERIFY_CHECK_NAME$1} (draft)`;
|
|
14868
|
+
/**
|
|
14869
|
+
* Is this the draft-head summary run's residue?
|
|
14870
|
+
*
|
|
14871
|
+
* The draft run and the `ready_for_review` run land on the SAME head SHA, and
|
|
14872
|
+
* they post two different contexts — `verify (draft)` and `verify` — which
|
|
14873
|
+
* `effectiveChecks` keys separately and therefore never collapses. The Verify
|
|
14874
|
+
* workflow cancels an in-flight pull-request run when the next one starts, and
|
|
14875
|
+
* `ready_for_review` IS the next one, so undrafting typically cancels the
|
|
14876
|
+
* draft run mid-flight. That leaves a CANCELLED `verify (draft)` row sitting
|
|
14877
|
+
* next to a green `verify` on the admitted head.
|
|
14878
|
+
*
|
|
14879
|
+
* GitHub does not care: the branch ruleset pins `verify` alone. The factory's
|
|
14880
|
+
* own rollup read would: `CANCELLED` is a failed state, so the candidate's
|
|
14881
|
+
* `requiredChecks` would read `failed` forever and no push-free repair could
|
|
14882
|
+
* clear it. The draft run is a superseded generation of the summary job the
|
|
14883
|
+
* `verify` row already answers for, so admission does not judge it.
|
|
14884
|
+
*/
|
|
14885
|
+
const isDraftHostedVerifyCheck = (check) => check.name === HOSTED_VERIFY_DRAFT_CHECK_NAME && Boolean(check.workflowName);
|
|
14886
|
+
/**
|
|
14887
|
+
* The rollup rows an admission read must not judge: the factory's own branded
|
|
14888
|
+
* checks (it would be judging itself) and the draft-head summary residue
|
|
14889
|
+
* above.
|
|
14890
|
+
*/
|
|
14891
|
+
const isExcludedFromAdmissionChecks = (check) => isFactoryReadyCheck(check) || isDraftHostedVerifyCheck(check);
|
|
14892
|
+
/**
|
|
14686
14893
|
* The hosted `verify` gate's state on this head, read by presence (#477).
|
|
14687
14894
|
*
|
|
14688
14895
|
* This is the one rollup question the rollup can answer honestly. Wave 2
|
|
@@ -14769,6 +14976,31 @@ const statusCheckState = (rollup, exclude) => {
|
|
|
14769
14976
|
if (currentChecks.some((check) => PENDING_STATES.has(progressState(check)))) return "pending";
|
|
14770
14977
|
return "unknown";
|
|
14771
14978
|
};
|
|
14979
|
+
/**
|
|
14980
|
+
* Is this a hosted `verify` check run that GitHub started before `cutoffIso`?
|
|
14981
|
+
* (#1045)
|
|
14982
|
+
*
|
|
14983
|
+
* A caller that waits for `verify` on a head it just changed the state of —
|
|
14984
|
+
* `pr:publish` undrafting a candidate — must not accept the run that was
|
|
14985
|
+
* already there. #1034's history is the shape: a draft head carried a `verify`
|
|
14986
|
+
* that had run nothing, the wait saw it as terminal on its first attempt, and
|
|
14987
|
+
* readiness read `passed` about two minutes before the `ready_for_review` run
|
|
14988
|
+
* created its own check runs. The same shape is reachable from any superseded
|
|
14989
|
+
* `verify` generation still on the head.
|
|
14990
|
+
*
|
|
14991
|
+
* Fail CLOSED on an unreadable timestamp: a `verify` row that cannot be shown
|
|
14992
|
+
* to be newer than the cutoff is treated as superseded. The wait is bounded,
|
|
14993
|
+
* so the cost of that refusal is one timeout that falls through to the
|
|
14994
|
+
* caller's existing behaviour; the cost of failing open is the false green
|
|
14995
|
+
* this exists to remove.
|
|
14996
|
+
*/
|
|
14997
|
+
const isSupersededHostedVerifyCheck = (check, cutoffIso) => {
|
|
14998
|
+
if (!isHostedVerifyCheck(check)) return false;
|
|
14999
|
+
const startedMs = checkTimestampMs(check);
|
|
15000
|
+
const cutoffMs = Date.parse(cutoffIso);
|
|
15001
|
+
if (startedMs === void 0 || Number.isNaN(cutoffMs)) return true;
|
|
15002
|
+
return startedMs < cutoffMs;
|
|
15003
|
+
};
|
|
14772
15004
|
//#endregion
|
|
14773
15005
|
//#region src/arm-auto-merge.ts
|
|
14774
15006
|
/**
|
|
@@ -15605,6 +15837,43 @@ const requiredChecksEvaluation = (input) => {
|
|
|
15605
15837
|
outcomes
|
|
15606
15838
|
};
|
|
15607
15839
|
};
|
|
15840
|
+
/**
|
|
15841
|
+
* REPAIR SITES 2 and 3 of 3 (#1045). #1048 makes the permitted set a type:
|
|
15842
|
+
* only a `RepairableDemand` blocker may carry a repair, so a fourth site on a
|
|
15843
|
+
* proof refusal fails `tsc`. The type keys on the demand, not on the state, so
|
|
15844
|
+
* it still permits a repair on a FAILED required check — the evaluator test
|
|
15845
|
+
* that turns every non-route-owned refusal on at once pins that half.
|
|
15846
|
+
*
|
|
15847
|
+
* Both hosted-check demands in `collectBlockers` are route-owned in exactly
|
|
15848
|
+
* two states, and in both the repair is the same one — wait for the NEXT
|
|
15849
|
+
* hosted run:
|
|
15850
|
+
*
|
|
15851
|
+
* 1. `requiredChecks === "pending"`: a run is in flight on this head.
|
|
15852
|
+
* 2. `draft === true`: the candidate is a draft, so the hosted verification
|
|
15853
|
+
* workflow deliberately posts no `verify` context at all (#1045 — `core`
|
|
15854
|
+
* and `docs` skip on a draft, and the summary job is renamed so the pinned
|
|
15855
|
+
* context is ABSENT rather than a green or gray check that ran nothing).
|
|
15856
|
+
* Absence is what `hostedVerifyCheck: "none"` reports, so without this
|
|
15857
|
+
* repair a draft would raise a human blocker no human can clear, and
|
|
15858
|
+
* `pr:ready` would publish a red check on every draft — the notification
|
|
15859
|
+
* noise #1037 removed.
|
|
15860
|
+
*
|
|
15861
|
+
* Draft is route-owned by construction: `pr:publish` undrafts the candidate
|
|
15862
|
+
* itself, and the repair is literally named `await-post-undraft-checks`
|
|
15863
|
+
* because it is about the run the promotion triggers.
|
|
15864
|
+
*
|
|
15865
|
+
* A draft whose checks came back FAILED is deliberately excluded: a real red
|
|
15866
|
+
* is not a not-yet, and waiting for another run would not clear it. That stays
|
|
15867
|
+
* a human blocker, exactly as it is today.
|
|
15868
|
+
*/
|
|
15869
|
+
const awaitPostUndraftChecksRepair = (input) => {
|
|
15870
|
+
if (!(input.requiredChecks === "pending" || input.draft && input.requiredChecks !== "failed")) return {};
|
|
15871
|
+
return { repair: {
|
|
15872
|
+
action: "Poll and retry until post-undraft GitHub checks settle or fail; a draft head and UNSTABLE/pending are transient route-owned states, not human blockers.",
|
|
15873
|
+
code: "await-post-undraft-checks",
|
|
15874
|
+
command: `gh pr checks ${input.pr} --watch`
|
|
15875
|
+
} };
|
|
15876
|
+
};
|
|
15608
15877
|
const collectBlockers = ({ correctnessRequired, correctnessStatus, correctnessUntyped, input, prVerifyStatus, verificationProof, securityRequired, securityStatus, securityUntyped }) => {
|
|
15609
15878
|
const blockers = [];
|
|
15610
15879
|
if (prVerifyStatus === "missing") blockers.push({
|
|
@@ -15652,22 +15921,21 @@ const collectBlockers = ({ correctnessRequired, correctnessStatus, correctnessUn
|
|
|
15652
15921
|
demand: DEMAND_KEYS.mergeState,
|
|
15653
15922
|
reason: `GitHub merge state is ${input.mergeStateStatus}/${input.mergeable}; expected CLEAN or review-only BLOCKED with MERGEABLE.`
|
|
15654
15923
|
});
|
|
15924
|
+
const awaitPostUndraftChecks = awaitPostUndraftChecksRepair(input);
|
|
15655
15925
|
if ((input.hostedVerifyCheck ?? "none") === "none") blockers.push({
|
|
15656
15926
|
demand: DEMAND_KEYS.githubChecks,
|
|
15657
|
-
reason: `GitHub has no ${HOSTED_VERIFY_CHECK_NAME$1} check run from its workflow producer on this head; the branch ruleset pins it as a required check and the status rollup does not report the shortfall
|
|
15927
|
+
reason: `GitHub has no ${HOSTED_VERIFY_CHECK_NAME$1} check run from its workflow producer on this head; the branch ruleset pins it as a required check and the status rollup does not report the shortfall.`,
|
|
15928
|
+
...awaitPostUndraftChecks
|
|
15658
15929
|
});
|
|
15659
15930
|
if (input.requiredChecks === "none" && input.classification !== "docs/process-only") blockers.push({
|
|
15660
15931
|
demand: DEMAND_KEYS.githubChecks,
|
|
15661
|
-
reason: "GitHub has no current checks for a non-docs PR."
|
|
15932
|
+
reason: "GitHub has no current checks for a non-docs PR.",
|
|
15933
|
+
...awaitPostUndraftChecks
|
|
15662
15934
|
});
|
|
15663
15935
|
else if (input.requiredChecks !== "passed" && input.requiredChecks !== "none") blockers.push({
|
|
15664
15936
|
demand: DEMAND_KEYS.githubChecks,
|
|
15665
15937
|
reason: `${PENDING_CHECKS_BLOCKER_REASON_PREFIX} ${input.requiredChecks}; expected passed.`,
|
|
15666
|
-
...
|
|
15667
|
-
action: "Poll and retry until post-undraft GitHub checks settle or fail; UNSTABLE/pending is a transient route-owned state, not a human blocker.",
|
|
15668
|
-
code: "await-post-undraft-checks",
|
|
15669
|
-
command: `gh pr checks ${input.pr} --watch`
|
|
15670
|
-
} } : {}
|
|
15938
|
+
...awaitPostUndraftChecks
|
|
15671
15939
|
});
|
|
15672
15940
|
if (input.draft) {
|
|
15673
15941
|
const promotion = prUndraftFollowUp(input.pr);
|
|
@@ -15683,7 +15951,7 @@ const collectBlockers = ({ correctnessRequired, correctnessStatus, correctnessUn
|
|
|
15683
15951
|
}
|
|
15684
15952
|
return blockers;
|
|
15685
15953
|
};
|
|
15686
|
-
const orderRepairs = (repairs) => repairs.toSorted((a, b) => READINESS_REPAIR_CODES.indexOf(a.code) - READINESS_REPAIR_CODES.indexOf(b.code));
|
|
15954
|
+
const orderRepairs = (repairs) => [...new Map(repairs.map((repair) => [repair.code, repair])).values()].toSorted((a, b) => READINESS_REPAIR_CODES.indexOf(a.code) - READINESS_REPAIR_CODES.indexOf(b.code));
|
|
15687
15955
|
const verificationProofForInput = ({ input }) => {
|
|
15688
15956
|
if (input.verificationProof) return {
|
|
15689
15957
|
fullVerifiedHeadShas: input.docsOnlyVerifyBaselineHeadShas,
|
|
@@ -16668,6 +16936,7 @@ const prReadyProofSchema = z.object({
|
|
|
16668
16936
|
"not-armed"
|
|
16669
16937
|
])
|
|
16670
16938
|
}).optional(),
|
|
16939
|
+
authoringSession: z.string().min(1).optional(),
|
|
16671
16940
|
blockedReasons: blockedReasonsSchema.optional(),
|
|
16672
16941
|
blockingReasons: z.array(z.string()),
|
|
16673
16942
|
command: z.literal("patronage-factory pr:ready"),
|
|
@@ -16994,7 +17263,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
16994
17263
|
requiredChecks
|
|
16995
17264
|
});
|
|
16996
17265
|
const output = path.resolve(cwd, args.output ?? ".factory-memory/pr-ready.json");
|
|
16997
|
-
const externalCheckState = statusCheckState(pr.statusCheckRollup,
|
|
17266
|
+
const externalCheckState = statusCheckState(pr.statusCheckRollup, isExcludedFromAdmissionChecks);
|
|
16998
17267
|
const hostedVerifyCheck = hostedVerifyCheckState(pr.statusCheckRollup);
|
|
16999
17268
|
const effectiveMergeStateStatus = pr.mergeStateStatus === "UNSTABLE" && (externalCheckState === "passed" || externalCheckState === "pending" || externalCheckState === "none") ? "CLEAN" : pr.mergeStateStatus;
|
|
17000
17269
|
const mergeFreeze = armingMergeFreezeOutcome({
|
|
@@ -17056,6 +17325,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
17056
17325
|
});
|
|
17057
17326
|
const undraftRepair = evaluation.repairs.find((repair) => repair.code === "undraft-pr");
|
|
17058
17327
|
const pendingExternalChecksOnly = externalCheckState === "pending" && evaluation.blockingReasons.length === 1;
|
|
17328
|
+
const routeOwnedNotYetOnly = evaluation.status === "blocked" && evaluation.humanBlockingReasons.length === 0;
|
|
17059
17329
|
const boundary = waveDemand?.kind === "demanded" ? waveDemand.demand : void 0;
|
|
17060
17330
|
const arming = evaluation.status === "ready" && boundary?.autoMerge === true ? (dependencies.armAutoMerge ?? armAutoMerge)({
|
|
17061
17331
|
cwd,
|
|
@@ -17088,10 +17358,12 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
17088
17358
|
...arming ? [armingNotice(arming)] : [],
|
|
17089
17359
|
...waveDemand?.notices ?? [],
|
|
17090
17360
|
...evaluation.status === "ready" && !boundary ? ["No boundary wave authorized this candidate, so nothing was armed: pr:ready admits it but schedules no merge. Re-run with --epic <issue> to resolve the wave demand and arm. That issue can be the work issue itself, carrying its own fence; do not file a second boundary issue for one candidate."] : [],
|
|
17091
|
-
...evaluation.status === "ready" && boundary?.autoMerge === false ? [`This candidate is admitted but unscheduled: boundary wave ${boundary.wave} does not authorize machine merge. GitHub auto-merge remains disabled; use the attended merge operation authorized for this wave.`] : []
|
|
17361
|
+
...evaluation.status === "ready" && boundary?.autoMerge === false ? [`This candidate is admitted but unscheduled: boundary wave ${boundary.wave} does not authorize machine merge. GitHub auto-merge remains disabled; use the attended merge operation authorized for this wave.`] : [],
|
|
17362
|
+
...routeOwnedNotYetOnly ? [`No ${FACTORY_CHECK_NAMES["pr-ready"]} check run was published for ${pr.headRefOid}: every refusal on this run is route-owned and not yet cleared (${evaluation.repairs.map((repair) => repair.code).join(", ")}). An absent required check blocks the merge, so the silence admits nothing; a proof failure still publishes a red check. Clear the repair and re-run pr:ready to green the head.`] : []
|
|
17092
17363
|
];
|
|
17093
17364
|
const proof = {
|
|
17094
17365
|
...arming ? { arming } : {},
|
|
17366
|
+
...authoringSession ? { authoringSession } : {},
|
|
17095
17367
|
...evaluation.blockedReasons.length > 0 ? { blockedReasons: evaluation.blockedReasons } : {},
|
|
17096
17368
|
blockingReasons: evaluation.blockingReasons,
|
|
17097
17369
|
command: "patronage-factory pr:ready",
|
|
@@ -17119,7 +17391,7 @@ async function runPrReady(args, dependencies = {}) {
|
|
|
17119
17391
|
sha: pr.headRefOid,
|
|
17120
17392
|
conclusion: CHECK_CONCLUSION_BY_READINESS_STATUS[proof.status]
|
|
17121
17393
|
};
|
|
17122
|
-
const publishedFinalCheck = await (dependencies.publishFinalCheckRun ? dependencies.publishFinalCheckRun(finalCheckInput) : ensureFactoryCheckRunPublished(finalCheckInput, { onDiagnostic: (message) => process.stderr.write(message) }));
|
|
17394
|
+
const publishedFinalCheck = routeOwnedNotYetOnly || await (dependencies.publishFinalCheckRun ? dependencies.publishFinalCheckRun(finalCheckInput) : ensureFactoryCheckRunPublished(finalCheckInput, { onDiagnostic: (message) => process.stderr.write(message) }));
|
|
17123
17395
|
finalCheckPublished = true;
|
|
17124
17396
|
if (!publishedFinalCheck) {
|
|
17125
17397
|
notices.push(`The ${FACTORY_CHECK_NAMES["pr-ready"]} check run was NOT confirmed for ${pr.headRefOid}, so GitHub is not serving this run's verdict as the settled state of the required check. Either the publication never landed there, or another run of that name is still unfinished on this head (#524) — an unfinished run blocks the merge whatever this run published, and publishing a newer one does not clear it. GitHub reports no rollup reason for either. Re-run pr:ready once the factory GitHub App can post; if it stays unconfirmed, look for a non-terminal ${FACTORY_CHECK_NAMES["pr-ready"]} run on ${pr.headRefOid}.`);
|
|
@@ -17514,7 +17786,7 @@ const authorityRule = (waveRung) => waveRung === void 0 ? `Emit schemaVersion 1
|
|
|
17514
17786
|
*/
|
|
17515
17787
|
const reviewFooterInstructions = (waveRung) => {
|
|
17516
17788
|
const schemaVersion = waveRung === void 0 ? 1 : 2;
|
|
17517
|
-
return `Do not print a machine-readable footer in your chat response.
|
|
17789
|
+
return `Do not print a machine-readable footer in your chat response. Write your findings to the typed findings file at the path given in your dispatch/task instructions (the file pr:review reads via --findings <path>); that file is the verdict. The file must be valid JSON matching review-findings-file.ts's schema (schemaVersion ${schemaVersion}):
|
|
17518
17790
|
{
|
|
17519
17791
|
"schemaVersion": ${schemaVersion},
|
|
17520
17792
|
${waveRung === void 0 ? "" : ` "producer": "<the engine and model that ran this review, for example codex-cli/gpt-5.6-sol>",\n "rung": "${waveRung}",\n`} "model": "<your model identifier>",
|
|
@@ -18283,6 +18555,7 @@ async function runPrReview(args, dependencies = {}) {
|
|
|
18283
18555
|
}
|
|
18284
18556
|
//#endregion
|
|
18285
18557
|
//#region src/pr-publish.ts
|
|
18558
|
+
const defaultNow = () => /* @__PURE__ */ new Date();
|
|
18286
18559
|
var PrPublishFollowUpError = class extends Error {
|
|
18287
18560
|
result;
|
|
18288
18561
|
constructor(result, cause) {
|
|
@@ -18614,7 +18887,8 @@ const PUBLISH_HOSTED_CHECKS_WAIT_MS = 15e3;
|
|
|
18614
18887
|
* PR #983 measured the second half: `verify` finished first, readiness ran and
|
|
18615
18888
|
* published red, and the head's status-HUD job was still in progress.
|
|
18616
18889
|
*
|
|
18617
|
-
* The `patronage-factory/*` family
|
|
18890
|
+
* The `patronage-factory/*` family and the draft-head summary residue are
|
|
18891
|
+
* excluded through `isExcludedFromAdmissionChecks` (#1045),
|
|
18618
18892
|
* the predicate readiness itself uses to exclude its own writes from the
|
|
18619
18893
|
* rollup gate. Waiting on it would deadlock: readiness is what completes those
|
|
18620
18894
|
* runs, and a stranded non-terminal generation of one (#524) never finishes.
|
|
@@ -18626,7 +18900,7 @@ const PUBLISH_HOSTED_CHECKS_WAIT_MS = 15e3;
|
|
|
18626
18900
|
* proceeds exactly as it does today. A moved head is reported, never absorbed:
|
|
18627
18901
|
* the caller aborts the transaction on it.
|
|
18628
18902
|
*/
|
|
18629
|
-
async function awaitHostedChecksSettled({ expectedHeadSha, fetchPr, report = (message) => process.stderr.write(message), sleep = setTimeout$1 }) {
|
|
18903
|
+
async function awaitHostedChecksSettled({ expectedHeadSha, fetchPr, report = (message) => process.stderr.write(message), sleep = setTimeout$1, verifyNotBefore }) {
|
|
18630
18904
|
for (let attempts = 0;; attempts += 1) {
|
|
18631
18905
|
const pr = fetchPr();
|
|
18632
18906
|
if (!sameHeadSha(pr.headRefOid, expectedHeadSha)) return {
|
|
@@ -18634,9 +18908,10 @@ async function awaitHostedChecksSettled({ expectedHeadSha, fetchPr, report = (me
|
|
|
18634
18908
|
attempts,
|
|
18635
18909
|
status: "head-moved"
|
|
18636
18910
|
};
|
|
18637
|
-
const
|
|
18911
|
+
const rollup = verifyNotBefore === void 0 ? pr.statusCheckRollup : pr.statusCheckRollup?.filter((check) => !isSupersededHostedVerifyCheck(check, verifyNotBefore));
|
|
18912
|
+
const verifyState = hostedVerifyCheckState(rollup);
|
|
18638
18913
|
const verifySettled = verifyState !== "none" && verifyState !== "pending";
|
|
18639
|
-
const pending = pendingCheckNames(
|
|
18914
|
+
const pending = pendingCheckNames(rollup, isExcludedFromAdmissionChecks);
|
|
18640
18915
|
if (verifySettled && pending.length === 0) return {
|
|
18641
18916
|
attempts,
|
|
18642
18917
|
status: "settled",
|
|
@@ -18671,15 +18946,28 @@ const reevaluateReadiness = (args, dependencies) => (dependencies.runPrReady ??
|
|
|
18671
18946
|
* invocation's verify and review proof describe the head it waited for. That
|
|
18672
18947
|
* is the same reason the #348 await aborts on head movement.
|
|
18673
18948
|
*/
|
|
18674
|
-
const awaitHostedChecksBeforeReadiness = async ({ dependencies, expectedHeadSha, fetchPr }) => {
|
|
18949
|
+
const awaitHostedChecksBeforeReadiness = async ({ dependencies, expectedHeadSha, fetchPr, verifyNotBefore }) => {
|
|
18675
18950
|
const outcome = await awaitHostedChecksSettled({
|
|
18676
18951
|
expectedHeadSha,
|
|
18677
18952
|
fetchPr,
|
|
18678
|
-
...dependencies.sleep === void 0 ? {} : { sleep: dependencies.sleep }
|
|
18953
|
+
...dependencies.sleep === void 0 ? {} : { sleep: dependencies.sleep },
|
|
18954
|
+
...verifyNotBefore === void 0 ? {} : { verifyNotBefore }
|
|
18679
18955
|
});
|
|
18680
18956
|
if (outcome.status === "head-moved") throw new PrPublishTransactionAbortedError(`awaited the head's hosted check runs for ${expectedHeadSha}, but the head moved to ${outcome.actualHeadSha} while waiting. Re-run pr:publish against the current head.`);
|
|
18681
18957
|
return outcome;
|
|
18682
18958
|
};
|
|
18959
|
+
/**
|
|
18960
|
+
* The instant `pr:publish` measures `verify` staleness from (#1045). Read
|
|
18961
|
+
* before the promotion, so every check run that already existed on the head
|
|
18962
|
+
* sorts strictly before it.
|
|
18963
|
+
*/
|
|
18964
|
+
const promotionInstant = (dependencies) => (dependencies.now ?? defaultNow)().toISOString();
|
|
18965
|
+
/**
|
|
18966
|
+
* The staleness cutoff to hand the hosted-run await — present only when this
|
|
18967
|
+
* invocation actually promoted the candidate. Without a promotion there is no
|
|
18968
|
+
* draft-era `verify` to distrust, and the wait keeps today's behaviour.
|
|
18969
|
+
*/
|
|
18970
|
+
const promotionVerifyCutoff = (promotion, promotionStartedAt) => promotion.status === "succeeded" ? { verifyNotBefore: promotionStartedAt } : {};
|
|
18683
18971
|
const requirePromotionHead = ({ actualHeadSha, expectedHeadSha, phase }) => {
|
|
18684
18972
|
if (!sameHeadSha(actualHeadSha, expectedHeadSha)) throw new PrPublishTransactionAbortedError(`${phase}: live PR head ${actualHeadSha} differs from the proved publish head ${expectedHeadSha}. Re-run pr:publish against the current head.`);
|
|
18685
18973
|
};
|
|
@@ -18887,6 +19175,7 @@ async function runPrPublish(args, dependencies = {}) {
|
|
|
18887
19175
|
json: false,
|
|
18888
19176
|
report: false
|
|
18889
19177
|
};
|
|
19178
|
+
const promotionStartedAt = promotionInstant(dependencies);
|
|
18890
19179
|
const promotion = await promoteDraftBeforeDemandEvaluation({
|
|
18891
19180
|
cwd,
|
|
18892
19181
|
expectedHeadSha: verifyProof.headSha,
|
|
@@ -18899,7 +19188,8 @@ async function runPrPublish(args, dependencies = {}) {
|
|
|
18899
19188
|
await awaitHostedChecksBeforeReadiness({
|
|
18900
19189
|
dependencies,
|
|
18901
19190
|
expectedHeadSha: verifyProof.headSha,
|
|
18902
|
-
fetchPr
|
|
19191
|
+
fetchPr,
|
|
19192
|
+
...promotionVerifyCutoff(promotion, promotionStartedAt)
|
|
18903
19193
|
});
|
|
18904
19194
|
const initialProof = await reevaluateReadiness(readyEvalArgs, publishDependencies);
|
|
18905
19195
|
requirePromotedProofHead({
|
package/dist/schemas.d.ts
CHANGED
|
@@ -531,6 +531,33 @@ interface PrVerifyCommandResult {
|
|
|
531
531
|
name: string;
|
|
532
532
|
scope: "docs-only" | "trivial" | "full";
|
|
533
533
|
}
|
|
534
|
+
/**
|
|
535
|
+
* One failing test a verification command reported (#1035).
|
|
536
|
+
*
|
|
537
|
+
* The proof keeps the failing test's IDENTITY and its failure CLASS, and
|
|
538
|
+
* nothing else. That is enough to decide retry-or-fix — a `timeout` under
|
|
539
|
+
* machine load is a flake, an `assertion` is a real break — without rerunning
|
|
540
|
+
* the aggregate to read a title off the console.
|
|
541
|
+
*
|
|
542
|
+
* It deliberately keeps no raw output tail and no error message body. This
|
|
543
|
+
* payload is transmitted to HQ ingest, and arbitrary command output can carry
|
|
544
|
+
* secrets (an echoed environment, a connection string inside a stack frame). A
|
|
545
|
+
* file path, a test title, and a closed `kind` enum cannot. The cost is the
|
|
546
|
+
* assertion diff and the stack frames: they help you FIX a failure, and the
|
|
547
|
+
* operator still reads them from the run's own console output.
|
|
548
|
+
*/
|
|
549
|
+
interface PrVerifyTestFailure {
|
|
550
|
+
/** The test file the reporter named, as the reporter wrote it. */
|
|
551
|
+
file: string;
|
|
552
|
+
/**
|
|
553
|
+
* The failure class. `timeout` is the test-timeout error vitest raises at
|
|
554
|
+
* its own ceiling; `assertion` is a failed expectation; `error` is every
|
|
555
|
+
* other thrown failure.
|
|
556
|
+
*/
|
|
557
|
+
kind: "assertion" | "error" | "timeout";
|
|
558
|
+
/** The full test title, suite names included, as the reporter wrote it. */
|
|
559
|
+
title: string;
|
|
560
|
+
}
|
|
534
561
|
interface PrVerifyExecutedCommand {
|
|
535
562
|
command: string;
|
|
536
563
|
counts?: {
|
|
@@ -539,6 +566,12 @@ interface PrVerifyExecutedCommand {
|
|
|
539
566
|
};
|
|
540
567
|
durationMs: number;
|
|
541
568
|
exitCode: number;
|
|
569
|
+
/**
|
|
570
|
+
* The failing tests this command reported (#1035). Present only when the
|
|
571
|
+
* command produced parseable failures, so a passing command's record is
|
|
572
|
+
* byte-identical to the one it wrote before this field existed.
|
|
573
|
+
*/
|
|
574
|
+
failures?: PrVerifyTestFailure[];
|
|
542
575
|
name: string;
|
|
543
576
|
scope: "docs-only" | "trivial" | "full";
|
|
544
577
|
}
|
|
@@ -640,6 +673,15 @@ declare const prVerifyProofSchema: z.ZodObject<{
|
|
|
640
673
|
}, z.core.$strip>>;
|
|
641
674
|
durationMs: z.ZodNumber;
|
|
642
675
|
exitCode: z.ZodNumber;
|
|
676
|
+
failures: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
677
|
+
file: z.ZodString;
|
|
678
|
+
kind: z.ZodEnum<{
|
|
679
|
+
error: "error";
|
|
680
|
+
assertion: "assertion";
|
|
681
|
+
timeout: "timeout";
|
|
682
|
+
}>;
|
|
683
|
+
title: z.ZodString;
|
|
684
|
+
}, z.core.$strip>>>;
|
|
643
685
|
name: z.ZodString;
|
|
644
686
|
scope: z.ZodEnum<{
|
|
645
687
|
trivial: "trivial";
|
|
@@ -734,6 +776,14 @@ interface PrReadyProof {
|
|
|
734
776
|
headSha: string;
|
|
735
777
|
outcome: "armed" | "merged" | "not-armed";
|
|
736
778
|
};
|
|
779
|
+
/**
|
|
780
|
+
* The authoring session readiness resolved for the review-independence
|
|
781
|
+
* cross-check (#1037). Recorded so the branded check can print it beside the
|
|
782
|
+
* review session, model, and producer the ledger already carries — the four
|
|
783
|
+
* identities a human weighs at merge time. Additive and optional: proofs
|
|
784
|
+
* written before it carry no field, so no schema version moves.
|
|
785
|
+
*/
|
|
786
|
+
authoringSession?: string;
|
|
737
787
|
/**
|
|
738
788
|
* Why this run blocked, one entry per refusing demand (#391): `code` is the
|
|
739
789
|
* demand key from the resolver's vocabulary, `detail` the one-sentence
|
|
@@ -1014,6 +1064,7 @@ declare const prReadyProofSchema: z.ZodObject<{
|
|
|
1014
1064
|
"not-armed": "not-armed";
|
|
1015
1065
|
}>;
|
|
1016
1066
|
}, z.core.$strip>>;
|
|
1067
|
+
authoringSession: z.ZodOptional<z.ZodString>;
|
|
1017
1068
|
blockedReasons: z.ZodOptional<z.ZodArray<z.ZodObject<{
|
|
1018
1069
|
code: z.ZodString;
|
|
1019
1070
|
detail: z.ZodString;
|
package/dist/schemas.js
CHANGED
|
@@ -1171,6 +1171,15 @@ const assertPrVerifyProofRules = (proof, context) => {
|
|
|
1171
1171
|
* factory must adopt this release before HQ ingests its pr:verify proofs.
|
|
1172
1172
|
*/
|
|
1173
1173
|
const SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS = [4];
|
|
1174
|
+
const prVerifyTestFailureSchema = z.object({
|
|
1175
|
+
file: z.string().min(1),
|
|
1176
|
+
kind: z.enum([
|
|
1177
|
+
"assertion",
|
|
1178
|
+
"error",
|
|
1179
|
+
"timeout"
|
|
1180
|
+
]),
|
|
1181
|
+
title: z.string().min(1)
|
|
1182
|
+
});
|
|
1174
1183
|
const executedCommandSchema = z.object({
|
|
1175
1184
|
command: z.string().min(1),
|
|
1176
1185
|
counts: z.object({
|
|
@@ -1179,6 +1188,7 @@ const executedCommandSchema = z.object({
|
|
|
1179
1188
|
}).optional(),
|
|
1180
1189
|
durationMs: z.number().nonnegative(),
|
|
1181
1190
|
exitCode: z.number(),
|
|
1191
|
+
failures: z.array(prVerifyTestFailureSchema).min(1).optional(),
|
|
1182
1192
|
name: z.string().min(1),
|
|
1183
1193
|
scope: z.enum([
|
|
1184
1194
|
"docs-only",
|
|
@@ -1614,6 +1624,7 @@ const prReadyProofSchema = z.object({
|
|
|
1614
1624
|
"not-armed"
|
|
1615
1625
|
])
|
|
1616
1626
|
}).optional(),
|
|
1627
|
+
authoringSession: z.string().min(1).optional(),
|
|
1617
1628
|
blockedReasons: blockedReasonsSchema.optional(),
|
|
1618
1629
|
blockingReasons: z.array(z.string()),
|
|
1619
1630
|
command: z.literal("patronage-factory pr:ready"),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@patronage/software-factory",
|
|
3
|
-
"version": "1.0.0-alpha.
|
|
3
|
+
"version": "1.0.0-alpha.29",
|
|
4
4
|
"description": "Shared Patronage software factory CLI and project-profile validation tools",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"picomatch": "^4.0.5",
|
|
38
38
|
"yaml": "^2.9.0",
|
|
39
39
|
"zod": "4.4.3",
|
|
40
|
-
"@patronage/factory-ci": "1.0.0-alpha.
|
|
40
|
+
"@patronage/factory-ci": "1.0.0-alpha.29"
|
|
41
41
|
},
|
|
42
42
|
"devDependencies": {
|
|
43
43
|
"@types/node": "24.13.3",
|