@patronage/software-factory 1.0.0-alpha.36 → 1.0.0-alpha.37

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/README.md CHANGED
@@ -4,7 +4,7 @@
4
4
 
5
5
  The factory admits a pull request from evidence bound to its candidate. It validates policy and proof; it does not schedule workers, choose models, or run reviews. Those are jobs for the harness or operator.
6
6
 
7
- Admission is fail closed. A clean-session review and required verification produce typed proof, then readiness decides whether the candidate may proceed. Evidence stays current when the candidate patch is unchanged; the push-triggered Verify workflow covers integration risk on every factory merge target — `main` and the `epic/**` integration branches. When a target goes red, the factory refuses new readiness decisions and authorized arming against that target; candidates armed before the target went red can still land. Epic policy, including review minimums and auto-merge authority, lives in the boundary manifest. HQ observes this work; it never blocks a gate.
7
+ Admission is fail closed. A clean-session review and required verification produce typed proof, then readiness decides whether the candidate may proceed. Evidence stays current when the candidate patch is unchanged; the push-triggered Verify workflow covers integration risk on every factory merge target — `main` and the `epic/**` integration branches. When a target goes red, the factory refuses new readiness decisions and authorized arming against that target; candidates armed before the target went red can still land. `pr:ready` arms GitHub native auto-merge for every admitted pull request. It does not arm when the repository does not allow auto-merge, when the pull request carries the `factory:hold` label, or when a boundary wave keeps the candidate attended. Epic policy, including review minimums and the auto-merge decision for its members, lives in the boundary manifest. HQ observes this work; it never blocks a gate.
8
8
 
9
9
  ## Develop the package
10
10
 
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { Command } from "commander";
2
- import { z } from "zod";
3
2
  import { existsSync, readFileSync, rmSync } from "node:fs";
3
+ import { z } from "zod";
4
4
  import { PreviewProofRegistration } from "@patronage/factory-ci";
5
5
  import { JsonValue, Questions, SystemOneRequest, choice, noul, score } from "@typesafe-ai/sdk";
6
6
 
@@ -948,10 +948,18 @@ declare const blockedReasonSchema: z.ZodObject<{
948
948
  type BlockedReason = z.infer<typeof blockedReasonSchema>;
949
949
  //#endregion
950
950
  //#region src/arm-auto-merge.d.ts
951
+ /**
952
+ * How GitHub merges the candidate. `pr:ready` chooses `merge` for a
953
+ * recognized faithful catch-up merge (ADR 0028), because a squash drops the
954
+ * second parent and the target loses the merged base as an ancestor (#829).
955
+ * Every other candidate is squashed.
956
+ */
957
+ type AutoMergeMethod = "merge" | "squash";
951
958
  interface ArmAutoMergeInput {
952
959
  cwd: string;
953
960
  /** The validated PR head this arming is a compare-and-set against. */
954
961
  headSha: string;
962
+ mergeMethod: AutoMergeMethod;
955
963
  owner: string;
956
964
  pr: number;
957
965
  repo: string;
@@ -1660,8 +1668,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
1660
1668
  generationId: z.ZodNumber;
1661
1669
  headSha: z.ZodString;
1662
1670
  outcome: z.ZodEnum<{
1663
- stale: "stale";
1664
1671
  active: "active";
1672
+ stale: "stale";
1665
1673
  }>;
1666
1674
  reason: z.ZodString;
1667
1675
  recordedAt: z.ZodISODateTime;
@@ -1950,9 +1958,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1950
1958
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1951
1959
  status: z.ZodEnum<{
1952
1960
  "not-required": "not-required";
1961
+ stale: "stale";
1953
1962
  blocked: "blocked";
1954
1963
  current: "current";
1955
- stale: "stale";
1956
1964
  missing: "missing";
1957
1965
  }>;
1958
1966
  }, z.core.$strip>;
@@ -1962,9 +1970,9 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1962
1970
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1963
1971
  status: z.ZodEnum<{
1964
1972
  "not-required": "not-required";
1973
+ stale: "stale";
1965
1974
  blocked: "blocked";
1966
1975
  current: "current";
1967
- stale: "stale";
1968
1976
  missing: "missing";
1969
1977
  }>;
1970
1978
  }, z.core.$strip>>;
@@ -2611,11 +2619,13 @@ interface PrReadyProof {
2611
2619
  schemaVersion: 3;
2612
2620
  /**
2613
2621
  * What GitHub actually did when this run armed native auto-merge (#477).
2614
- * Present exactly when a ready wave authorizes machine merge and arming ran;
2615
- * read back from the PR rather than inferred from the invocation, because `gh pr merge
2616
- * --auto` arms, merges, or does nothing with the same exit code and the same
2617
- * empty output. `not-armed` means the candidate is admitted but nothing will
2618
- * merge it, and `followUp` carries the re-dispatch.
2622
+ * Present exactly when a ready run called the arming module (#1299): not
2623
+ * for an `autoMerge: false` wave, and not when a notice names why the run
2624
+ * did not arm. Read back from the PR rather than inferred from the
2625
+ * invocation, because `gh pr merge --auto` arms, merges, or does nothing
2626
+ * with the same exit code and the same empty output. `not-armed` means the
2627
+ * candidate is admitted but nothing will merge it, and `followUp` carries
2628
+ * the re-dispatch.
2619
2629
  */
2620
2630
  arming?: {
2621
2631
  detail?: string;
@@ -2687,13 +2697,61 @@ interface PrReadyProof {
2687
2697
  */
2688
2698
  waivedDemands?: WaivedDemand[];
2689
2699
  }
2700
+ /** An issue's sub-issue parent, as the membership rule reads it (#1299). */
2701
+ interface IssueParent {
2702
+ labels: string[];
2703
+ number: number;
2704
+ state: string;
2705
+ }
2706
+ /** What `pr:ready` reads about one pull request before it arms (#1299). */
2707
+ interface PullRequestArmingFacts {
2708
+ /** GitHub's per-repository "Allow auto-merge" setting. */
2709
+ autoMergeAllowed: boolean;
2710
+ /**
2711
+ * GitHub's closing references for the pull request to issues in its own
2712
+ * repository, with their parents.
2713
+ */
2714
+ closingIssues: {
2715
+ number: number;
2716
+ parent: IssueParent | null;
2717
+ }[];
2718
+ /**
2719
+ * GitHub has more closing references than the one page `pr:ready` reads,
2720
+ * so `closingIssues` is not the whole set.
2721
+ */
2722
+ closingIssuesIncomplete: boolean;
2723
+ labels: string[];
2724
+ }
2725
+ /**
2726
+ * The GitHub reads behind the reasons not to arm an admitted candidate
2727
+ * (#1299). A read throws when GitHub cannot answer; `pr:ready` then does not
2728
+ * arm, and its notice says which read failed.
2729
+ */
2730
+ interface ArmingPolicyReads {
2731
+ /** One issue's sub-issue parent, or `null` when it has none. */
2732
+ readIssueParent: (input: {
2733
+ issue: number;
2734
+ owner: string;
2735
+ repo: string;
2736
+ }) => IssueParent | null;
2737
+ readPullRequest: (input: {
2738
+ owner: string;
2739
+ pr: number;
2740
+ repo: string;
2741
+ }) => PullRequestArmingFacts;
2742
+ }
2690
2743
  interface PrReadyDependencies {
2691
2744
  /**
2692
- * Arms GitHub native auto-merge only for a passing boundary wave that
2693
- * authorizes machine merge (#477). Defaults to the real `gh pr merge --auto`
2694
- * call plus the read-back that says what actually happened.
2745
+ * Arms GitHub native auto-merge for an admitted candidate (#477, #1299).
2746
+ * Defaults to the real `gh pr merge --auto` call plus the read-back that
2747
+ * says what actually happened.
2695
2748
  */
2696
2749
  armAutoMerge?: (input: ArmAutoMergeInput) => ArmAutoMergeOutcome;
2750
+ /**
2751
+ * The GitHub reads behind the reasons not to arm (#1299). Defaults to the
2752
+ * real GraphQL reads.
2753
+ */
2754
+ armingPolicy?: ArmingPolicyReads;
2697
2755
  /**
2698
2756
  * Reads the authoritative base review policy (#922): the profile committed at
2699
2757
  * the live tip of the PR's own base ref. Defaults to the real GitHub reads,
@@ -2935,9 +2993,9 @@ declare const loadEvidenceEnvelopes: (cwd: string) => LoadedEvidenceEnvelope[];
2935
2993
  declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale", "missing", "blocked"];
2936
2994
  declare const reviewStatusSchema: z.ZodEnum<{
2937
2995
  "not-required": "not-required";
2996
+ stale: "stale";
2938
2997
  blocked: "blocked";
2939
2998
  current: "current";
2940
- stale: "stale";
2941
2999
  missing: "missing";
2942
3000
  }>;
2943
3001
  type ReviewStatus = z.infer<typeof reviewStatusSchema>;
@@ -3445,11 +3503,12 @@ interface PrPublishHandoff {
3445
3503
  routeOwnedRepairs: ReadinessRepair[];
3446
3504
  /**
3447
3505
  * Whether the merge is actually on GitHub's schedule (#477, #515 review).
3448
- * An admitted candidate is not a handed-off one: `pr:ready` arms only when a
3449
- * boundary wave authorized it, and the arming can fail to take effect. A
3506
+ * An admitted candidate is not a handed-off one: `pr:ready` does not arm
3507
+ * when the repository does not allow auto-merge, the pull request carries
3508
+ * `factory:hold`, a boundary wave keeps it attended, or it is an epic member
3509
+ * run without `--epic` (#1299), and the arming can fail to take effect. A
3450
3510
  * readiness status of `ready` says the candidate passed, never that anything
3451
- * will merge it — only an auto-merge-authorized boundary wave can schedule
3452
- * one — so publish reads this before claiming nothing is owed.
3511
+ * will merge it, so publish reads this before claiming nothing is owed.
3453
3512
  */
3454
3513
  scheduled: boolean;
3455
3514
  status: PrReadyProof["status"];
@@ -4824,11 +4883,125 @@ interface EvaluationInput {
4824
4883
  waivers?: DemandWaiver[];
4825
4884
  }
4826
4885
  declare const evaluateReadiness: (input: EvaluationInput) => {
4886
+ humanBlockingReasons: string[];
4887
+ ledger: {
4888
+ baseSha: string;
4889
+ blockingReasons: string[];
4890
+ classification: "docs/process-only" | "trivial" | "non-trivial";
4891
+ github: {
4892
+ draft: boolean;
4893
+ mergeStateStatus: string;
4894
+ mergeable: string;
4895
+ requiredChecks: "unknown" | "pending" | "passed" | "failed" | "none";
4896
+ unresolvedReviewThreads: number;
4897
+ reviewDecision?: string | null | undefined;
4898
+ };
4899
+ headSha: string;
4900
+ patchId: string;
4901
+ pr: number;
4902
+ repairs: {
4903
+ action: string;
4904
+ code: "undraft-pr" | "await-post-undraft-checks";
4905
+ command: string;
4906
+ }[];
4907
+ reviews: {
4908
+ correctness: {
4909
+ required: boolean;
4910
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4911
+ reviewedHeadSha?: string | undefined;
4912
+ reviewedPatchId?: string | undefined;
4913
+ };
4914
+ security?: {
4915
+ required: boolean;
4916
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4917
+ reviewedHeadSha?: string | undefined;
4918
+ reviewedPatchId?: string | undefined;
4919
+ } | undefined;
4920
+ };
4921
+ schemaVersion: 2;
4922
+ verification: {
4923
+ command: "patronage-factory pr:verify";
4924
+ prVerify: "missing";
4925
+ } | {
4926
+ command: "patronage-factory pr:verify";
4927
+ prVerify: "stale";
4928
+ verifiedHeadSha: string;
4929
+ } | {
4930
+ command: "patronage-factory pr:verify";
4931
+ prVerify: "passed-via-head";
4932
+ verifiedHeadSha: string;
4933
+ } | {
4934
+ command: "patronage-factory pr:verify";
4935
+ docsOnlyVerifiedHeadSha: string;
4936
+ prVerify: "docs-only-delta";
4937
+ verifiedHeadSha?: string | undefined;
4938
+ } | {
4939
+ command: "patronage-factory pr:verify";
4940
+ prVerify: "trivial-delta";
4941
+ trivialVerifiedHeadSha: string;
4942
+ verifiedHeadSha?: string | undefined;
4943
+ };
4944
+ externalChecks?: {
4945
+ checkType: "review" | "verify";
4946
+ name: string;
4947
+ scopeReason: string;
4948
+ status: "satisfied" | "unmet" | "out-of-scope";
4949
+ reason?: string | undefined;
4950
+ scope?: {
4951
+ classifications: ("docs/process-only" | "trivial" | "non-trivial")[];
4952
+ } | undefined;
4953
+ }[] | undefined;
4954
+ mergeBaseSha?: string | undefined;
4955
+ reviewCycleState?: {
4956
+ autoBlockingFindings: number;
4957
+ countsBySeverity: {
4958
+ critical: number;
4959
+ high: number;
4960
+ low: number;
4961
+ medium: number;
4962
+ unknown: number;
4963
+ };
4964
+ nonBlockingFindings: number;
4965
+ openFindings: number;
4966
+ windowExhausted: boolean;
4967
+ highestBlockingSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
4968
+ highestOpenSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
4969
+ maxReviewCycles?: number | undefined;
4970
+ reviewCycle?: number | undefined;
4971
+ staleRepeatFindings?: number | undefined;
4972
+ } | undefined;
4973
+ reviewLadder?: {
4974
+ cycleCounts: {
4975
+ gate: number;
4976
+ interior: number;
4977
+ };
4978
+ nextAction: "run-interior-cycle" | "advance-to-gate" | "run-gate-cycle" | "accept-nonblocking-findings" | "escalate-to-triage" | "ready-for-human";
4979
+ stage: "interior" | "gate" | "interior-complete";
4980
+ forcedTransition?: "gate-cap-exhausted" | "interior-cap-reached" | undefined;
4981
+ } | undefined;
4982
+ reviewRuns?: PrReviewResult[] | undefined;
4983
+ reviewTerminalState?: "blocked" | "accepted-with-findings" | "clean" | undefined;
4984
+ };
4985
+ /**
4986
+ * Facts a reader needs that are not refusals (#477): the settle-window
4987
+ * arming notice, and every waived demand rendered so it can never read as
4988
+ * a met one.
4989
+ */
4990
+ notices: string[];
4991
+ repairs: {
4992
+ action: string;
4993
+ code: "undraft-pr" | "await-post-undraft-checks";
4994
+ command: string;
4995
+ }[];
4996
+ status: ReadinessStatus; /** Demands that were in force, were NOT met, and the operator waived. */
4997
+ waivedDemands: WaivedDemand[];
4998
+ catchUpRecognition: CatchUpRecognition;
4827
4999
  blockedReasons: {
4828
5000
  code: string;
4829
5001
  detail: string;
4830
5002
  }[];
4831
5003
  blockingReasons: string[];
5004
+ } | {
4832
5005
  humanBlockingReasons: string[];
4833
5006
  ledger: {
4834
5007
  baseSha: string;
@@ -4853,13 +5026,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4853
5026
  reviews: {
4854
5027
  correctness: {
4855
5028
  required: boolean;
4856
- status: "not-required" | "blocked" | "current" | "stale" | "missing";
5029
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4857
5030
  reviewedHeadSha?: string | undefined;
4858
5031
  reviewedPatchId?: string | undefined;
4859
5032
  };
4860
5033
  security?: {
4861
5034
  required: boolean;
4862
- status: "not-required" | "blocked" | "current" | "stale" | "missing";
5035
+ status: "not-required" | "stale" | "blocked" | "current" | "missing";
4863
5036
  reviewedHeadSha?: string | undefined;
4864
5037
  reviewedPatchId?: string | undefined;
4865
5038
  } | undefined;
@@ -4941,6 +5114,12 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4941
5114
  }[];
4942
5115
  status: ReadinessStatus; /** Demands that were in force, were NOT met, and the operator waived. */
4943
5116
  waivedDemands: WaivedDemand[];
5117
+ catchUpRecognition?: undefined;
5118
+ blockedReasons: {
5119
+ code: string;
5120
+ detail: string;
5121
+ }[];
5122
+ blockingReasons: string[];
4944
5123
  };
4945
5124
  //#endregion
4946
5125
  //#region src/evidence-emit.d.ts