@patronage/software-factory 1.0.0-alpha.0 → 1.0.0-alpha.1

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 CHANGED
@@ -61,8 +61,8 @@ declare const boundaryReviewProofBaseSchema: z.ZodObject<{
61
61
  }>;
62
62
  producer: z.ZodString;
63
63
  rung: z.ZodEnum<{
64
- oracle: "oracle";
65
64
  "independent-model": "independent-model";
65
+ oracle: "oracle";
66
66
  human: "human";
67
67
  }>;
68
68
  schemaVersion: z.ZodLiteral<1>;
@@ -105,8 +105,8 @@ declare const boundaryReviewProofSchema: z.ZodObject<{
105
105
  }>;
106
106
  producer: z.ZodString;
107
107
  rung: z.ZodEnum<{
108
- oracle: "oracle";
109
108
  "independent-model": "independent-model";
109
+ oracle: "oracle";
110
110
  human: "human";
111
111
  }>;
112
112
  schemaVersion: z.ZodLiteral<1>;
@@ -537,6 +537,7 @@ declare const factoryProjectProfileSchema: z.ZodObject<{
537
537
  targets: z.ZodArray<z.ZodObject<{
538
538
  importers: z.ZodArray<z.ZodString>;
539
539
  name: z.ZodString;
540
+ paths: z.ZodOptional<z.ZodArray<z.ZodString>>;
540
541
  }, z.core.$strict>>;
541
542
  }, z.core.$strict>>;
542
543
  proof: z.ZodObject<{
@@ -553,8 +554,8 @@ declare const factoryProjectProfileSchema: z.ZodObject<{
553
554
  name: z.ZodString;
554
555
  scope: z.ZodOptional<z.ZodObject<{
555
556
  classifications: z.ZodArray<z.ZodEnum<{
556
- trivial: "trivial";
557
557
  "docs/process-only": "docs/process-only";
558
+ trivial: "trivial";
558
559
  "non-trivial": "non-trivial";
559
560
  }>>;
560
561
  }, z.core.$strict>>;
@@ -1133,8 +1134,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1133
1134
  baseSha: z.ZodString;
1134
1135
  blockingReasons: z.ZodArray<z.ZodString>;
1135
1136
  classification: z.ZodEnum<{
1136
- trivial: "trivial";
1137
1137
  "docs/process-only": "docs/process-only";
1138
+ trivial: "trivial";
1138
1139
  "non-trivial": "non-trivial";
1139
1140
  }>;
1140
1141
  externalChecks: z.ZodOptional<z.ZodArray<z.ZodObject<{
@@ -1147,8 +1148,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1147
1148
  reason: z.ZodOptional<z.ZodString>;
1148
1149
  scope: z.ZodOptional<z.ZodObject<{
1149
1150
  classifications: z.ZodArray<z.ZodEnum<{
1150
- trivial: "trivial";
1151
1151
  "docs/process-only": "docs/process-only";
1152
+ trivial: "trivial";
1152
1153
  "non-trivial": "non-trivial";
1153
1154
  }>>;
1154
1155
  }, z.core.$strict>>;
@@ -1198,15 +1199,15 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1198
1199
  unknown: "unknown";
1199
1200
  critical: "critical";
1200
1201
  high: "high";
1201
- low: "low";
1202
1202
  medium: "medium";
1203
+ low: "low";
1203
1204
  }>>;
1204
1205
  highestOpenSeverity: z.ZodOptional<z.ZodEnum<{
1205
1206
  unknown: "unknown";
1206
1207
  critical: "critical";
1207
1208
  high: "high";
1208
- low: "low";
1209
1209
  medium: "medium";
1210
+ low: "low";
1210
1211
  }>>;
1211
1212
  maxReviewCycles: z.ZodOptional<z.ZodNumber>;
1212
1213
  nonBlockingFindings: z.ZodNumber;
@@ -1234,8 +1235,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1234
1235
  }>;
1235
1236
  stage: z.ZodEnum<{
1236
1237
  gate: "gate";
1237
- interior: "interior";
1238
1238
  "interior-complete": "interior-complete";
1239
+ interior: "interior";
1239
1240
  }>;
1240
1241
  }, z.core.$strip>>;
1241
1242
  reviewRuns: z.ZodOptional<z.ZodArray<z.ZodType<PrReviewResult, unknown, z.core.$ZodTypeInternals<PrReviewResult, unknown>>>>;
@@ -1522,11 +1523,11 @@ declare function runPrReady(args: PrReadyArgs, dependencies?: PrReadyDependencie
1522
1523
  */
1523
1524
  declare const impactStampSchema: z.ZodObject<{
1524
1525
  basis: z.ZodEnum<{
1525
- "lockfile-scoped": "lockfile-scoped";
1526
+ "target-scoped": "target-scoped";
1526
1527
  conservative: "conservative";
1527
1528
  }>;
1528
1529
  reasons: z.ZodArray<z.ZodString>;
1529
- stampVersion: z.ZodLiteral<1>;
1530
+ stampVersion: z.ZodLiteral<2>;
1530
1531
  targets: z.ZodArray<z.ZodObject<{
1531
1532
  basis: z.ZodString;
1532
1533
  impact: z.ZodEnum<{
@@ -1560,7 +1561,7 @@ interface ImpactScopeDecision {
1560
1561
  * external proof demand (#542 wave 2), a verification-battery command, or a
1561
1562
  * preview (Alchemy) lifecycle stack (#430 wave 3).
1562
1563
  *
1563
- * The only release path is a `lockfile-scoped` stamp of exactly this build's
1564
+ * The only release path is a `target-scoped` stamp of exactly this build's
1564
1565
  * stamp version whose target entry for exactly this name is provably
1565
1566
  * `not-affected`. Every other input — no trusted stamp, an unknown stamp
1566
1567
  * version, a conservative stamp, a name the stamp does not classify, or an
@@ -1588,8 +1589,8 @@ declare const EVIDENCE_CHECK_TYPES: readonly ["review", "verify"];
1588
1589
  type EvidenceCheckType = (typeof EVIDENCE_CHECK_TYPES)[number];
1589
1590
  declare const requiredCheckScopeSchema: z.ZodObject<{
1590
1591
  classifications: z.ZodArray<z.ZodEnum<{
1591
- trivial: "trivial";
1592
1592
  "docs/process-only": "docs/process-only";
1593
+ trivial: "trivial";
1593
1594
  "non-trivial": "non-trivial";
1594
1595
  }>>;
1595
1596
  }, z.core.$strict>;
@@ -1646,8 +1647,8 @@ declare const evidenceEnvelopeBaseSchema: z.ZodObject<{
1646
1647
  producer: z.ZodString;
1647
1648
  requestId: z.ZodOptional<z.ZodString>;
1648
1649
  rung: z.ZodOptional<z.ZodEnum<{
1649
- oracle: "oracle";
1650
1650
  "independent-model": "independent-model";
1651
+ oracle: "oracle";
1651
1652
  human: "human";
1652
1653
  }>>;
1653
1654
  schemaVersion: z.ZodLiteral<1>;
@@ -1673,8 +1674,8 @@ declare const evidenceEnvelopeSchema: z.ZodObject<{
1673
1674
  producer: z.ZodString;
1674
1675
  requestId: z.ZodOptional<z.ZodString>;
1675
1676
  rung: z.ZodOptional<z.ZodEnum<{
1676
- oracle: "oracle";
1677
1677
  "independent-model": "independent-model";
1678
+ oracle: "oracle";
1678
1679
  human: "human";
1679
1680
  }>>;
1680
1681
  schemaVersion: z.ZodLiteral<1>;
@@ -2294,8 +2295,8 @@ declare const boundaryCheckProofSchema: z.ZodObject<{
2294
2295
  wave: z.ZodString;
2295
2296
  }, z.core.$strip>>;
2296
2297
  demandedRung: z.ZodOptional<z.ZodEnum<{
2297
- oracle: "oracle";
2298
2298
  "independent-model": "independent-model";
2299
+ oracle: "oracle";
2299
2300
  human: "human";
2300
2301
  }>>;
2301
2302
  epicIssue: z.ZodNumber;
@@ -2310,8 +2311,8 @@ declare const boundaryCheckProofSchema: z.ZodObject<{
2310
2311
  commentUrl: z.ZodOptional<z.ZodString>;
2311
2312
  producer: z.ZodString;
2312
2313
  rung: z.ZodEnum<{
2313
- oracle: "oracle";
2314
2314
  "independent-model": "independent-model";
2315
+ oracle: "oracle";
2315
2316
  human: "human";
2316
2317
  }>;
2317
2318
  sessionId: z.ZodOptional<z.ZodString>;
@@ -3338,8 +3339,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3338
3339
  slotResolution: z.ZodObject<{
3339
3340
  effort: z.ZodEnum<{
3340
3341
  high: "high";
3341
- low: "low";
3342
3342
  medium: "medium";
3343
+ low: "low";
3343
3344
  xhigh: "xhigh";
3344
3345
  }>;
3345
3346
  engine: z.ZodString;
@@ -3351,18 +3352,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3351
3352
  }>;
3352
3353
  model: z.ZodString;
3353
3354
  tier: z.ZodEnum<{
3355
+ oracle: "oracle";
3354
3356
  review: "review";
3355
3357
  delegate: "delegate";
3356
- oracle: "oracle";
3357
3358
  workhorse: "workhorse";
3358
3359
  scout: "scout";
3359
3360
  }>;
3360
3361
  }, z.core.$strict>;
3361
3362
  startedAt: z.ZodString;
3362
3363
  tier: z.ZodEnum<{
3364
+ oracle: "oracle";
3363
3365
  review: "review";
3364
3366
  delegate: "delegate";
3365
- oracle: "oracle";
3366
3367
  workhorse: "workhorse";
3367
3368
  scout: "scout";
3368
3369
  }>;
@@ -3417,8 +3418,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3417
3418
  slotResolution: z.ZodObject<{
3418
3419
  effort: z.ZodEnum<{
3419
3420
  high: "high";
3420
- low: "low";
3421
3421
  medium: "medium";
3422
+ low: "low";
3422
3423
  xhigh: "xhigh";
3423
3424
  }>;
3424
3425
  engine: z.ZodString;
@@ -3430,18 +3431,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3430
3431
  }>;
3431
3432
  model: z.ZodString;
3432
3433
  tier: z.ZodEnum<{
3434
+ oracle: "oracle";
3433
3435
  review: "review";
3434
3436
  delegate: "delegate";
3435
- oracle: "oracle";
3436
3437
  workhorse: "workhorse";
3437
3438
  scout: "scout";
3438
3439
  }>;
3439
3440
  }, z.core.$strict>;
3440
3441
  startedAt: z.ZodString;
3441
3442
  tier: z.ZodEnum<{
3443
+ oracle: "oracle";
3442
3444
  review: "review";
3443
3445
  delegate: "delegate";
3444
- oracle: "oracle";
3445
3446
  workhorse: "workhorse";
3446
3447
  scout: "scout";
3447
3448
  }>;
@@ -3487,8 +3488,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3487
3488
  slotResolution: z.ZodObject<{
3488
3489
  effort: z.ZodEnum<{
3489
3490
  high: "high";
3490
- low: "low";
3491
3491
  medium: "medium";
3492
+ low: "low";
3492
3493
  xhigh: "xhigh";
3493
3494
  }>;
3494
3495
  engine: z.ZodString;
@@ -3500,18 +3501,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3500
3501
  }>;
3501
3502
  model: z.ZodString;
3502
3503
  tier: z.ZodEnum<{
3504
+ oracle: "oracle";
3503
3505
  review: "review";
3504
3506
  delegate: "delegate";
3505
- oracle: "oracle";
3506
3507
  workhorse: "workhorse";
3507
3508
  scout: "scout";
3508
3509
  }>;
3509
3510
  }, z.core.$strict>;
3510
3511
  startedAt: z.ZodString;
3511
3512
  tier: z.ZodEnum<{
3513
+ oracle: "oracle";
3512
3514
  review: "review";
3513
3515
  delegate: "delegate";
3514
- oracle: "oracle";
3515
3516
  workhorse: "workhorse";
3516
3517
  scout: "scout";
3517
3518
  }>;
@@ -3556,15 +3557,15 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3556
3557
  observedAt: string;
3557
3558
  purpose: "review" | "code";
3558
3559
  slotResolution: {
3559
- effort: "high" | "low" | "medium" | "xhigh";
3560
+ effort: "high" | "medium" | "low" | "xhigh";
3560
3561
  engine: string;
3561
3562
  layerSource: "committedDefault" | "operatorSession" | "userConfig";
3562
3563
  model: string;
3563
- tier: "review" | "delegate" | "oracle" | "workhorse" | "scout";
3564
+ tier: "oracle" | "review" | "delegate" | "workhorse" | "scout";
3564
3565
  fallbackTier?: "delegate" | undefined;
3565
3566
  };
3566
3567
  startedAt: string;
3567
- tier: "review" | "delegate" | "oracle" | "workhorse" | "scout";
3568
+ tier: "oracle" | "review" | "delegate" | "workhorse" | "scout";
3568
3569
  tokenUsage: {
3569
3570
  source: "cursor-csv" | "codex-json" | "cursor-json" | "claude-json";
3570
3571
  status: "available";
@@ -3923,8 +3924,8 @@ declare const boundaryManifestBaseSchema: z.ZodObject<{
3923
3924
  boundary: z.ZodString;
3924
3925
  closeout: z.ZodOptional<z.ZodObject<{
3925
3926
  review: z.ZodEnum<{
3926
- oracle: "oracle";
3927
3927
  "independent-model": "independent-model";
3928
+ oracle: "oracle";
3928
3929
  human: "human";
3929
3930
  }>;
3930
3931
  }, z.core.$loose>>;
@@ -3932,8 +3933,8 @@ declare const boundaryManifestBaseSchema: z.ZodObject<{
3932
3933
  prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
3933
3934
  schemaVersion: z.ZodLiteral<1>;
3934
3935
  topology: z.ZodEnum<{
3935
- flagged: "flagged";
3936
3936
  "each-to-main": "each-to-main";
3937
+ flagged: "flagged";
3937
3938
  stacked: "stacked";
3938
3939
  }>;
3939
3940
  waves: z.ZodArray<z.ZodObject<{
@@ -3941,8 +3942,8 @@ declare const boundaryManifestBaseSchema: z.ZodObject<{
3941
3942
  issues: z.ZodArray<z.ZodNumber>;
3942
3943
  name: z.ZodString;
3943
3944
  review: z.ZodEnum<{
3944
- oracle: "oracle";
3945
3945
  "independent-model": "independent-model";
3946
+ oracle: "oracle";
3946
3947
  human: "human";
3947
3948
  }>;
3948
3949
  }, z.core.$loose>>;
@@ -3951,8 +3952,8 @@ declare const boundaryManifestSchema: z.ZodObject<{
3951
3952
  boundary: z.ZodString;
3952
3953
  closeout: z.ZodOptional<z.ZodObject<{
3953
3954
  review: z.ZodEnum<{
3954
- oracle: "oracle";
3955
3955
  "independent-model": "independent-model";
3956
+ oracle: "oracle";
3956
3957
  human: "human";
3957
3958
  }>;
3958
3959
  }, z.core.$loose>>;
@@ -3960,8 +3961,8 @@ declare const boundaryManifestSchema: z.ZodObject<{
3960
3961
  prs: z.ZodOptional<z.ZodRecord<z.ZodString, z.ZodString>>;
3961
3962
  schemaVersion: z.ZodLiteral<1>;
3962
3963
  topology: z.ZodEnum<{
3963
- flagged: "flagged";
3964
3964
  "each-to-main": "each-to-main";
3965
+ flagged: "flagged";
3965
3966
  stacked: "stacked";
3966
3967
  }>;
3967
3968
  waves: z.ZodArray<z.ZodObject<{
@@ -3969,8 +3970,8 @@ declare const boundaryManifestSchema: z.ZodObject<{
3969
3970
  issues: z.ZodArray<z.ZodNumber>;
3970
3971
  name: z.ZodString;
3971
3972
  review: z.ZodEnum<{
3972
- oracle: "oracle";
3973
3973
  "independent-model": "independent-model";
3974
+ oracle: "oracle";
3974
3975
  human: "human";
3975
3976
  }>;
3976
3977
  }, z.core.$loose>>;
@@ -4055,7 +4056,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4055
4056
  ledger: {
4056
4057
  baseSha: string;
4057
4058
  blockingReasons: string[];
4058
- classification: "trivial" | "docs/process-only" | "non-trivial";
4059
+ classification: "docs/process-only" | "trivial" | "non-trivial";
4059
4060
  github: {
4060
4061
  draft: boolean;
4061
4062
  mergeStateStatus: string;
@@ -4104,7 +4105,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4104
4105
  status: "satisfied" | "unmet" | "out-of-scope";
4105
4106
  reason?: string | undefined;
4106
4107
  scope?: {
4107
- classifications: ("trivial" | "docs/process-only" | "non-trivial")[];
4108
+ classifications: ("docs/process-only" | "trivial" | "non-trivial")[];
4108
4109
  } | undefined;
4109
4110
  }[] | undefined;
4110
4111
  mergeBaseSha?: string | undefined;
@@ -4120,8 +4121,8 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4120
4121
  nonBlockingFindings: number;
4121
4122
  openFindings: number;
4122
4123
  windowExhausted: boolean;
4123
- highestBlockingSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
4124
- highestOpenSeverity?: "unknown" | "critical" | "high" | "low" | "medium" | undefined;
4124
+ highestBlockingSeverity?: "unknown" | "critical" | "high" | "medium" | "low" | undefined;
4125
+ highestOpenSeverity?: "unknown" | "critical" | "high" | "medium" | "low" | undefined;
4125
4126
  maxReviewCycles?: number | undefined;
4126
4127
  reviewCycle?: number | undefined;
4127
4128
  staleRepeatFindings?: number | undefined;
@@ -4132,7 +4133,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
4132
4133
  interior: number;
4133
4134
  };
4134
4135
  nextAction: "run-gate-cycle" | "accept-nonblocking-findings" | "escalate-to-triage" | "ready-for-human" | "advance-to-gate" | "run-interior-cycle";
4135
- stage: "gate" | "interior" | "interior-complete";
4136
+ stage: "gate" | "interior-complete" | "interior";
4136
4137
  forcedTransition?: "gate-cap-exhausted" | "interior-cap-reached" | undefined;
4137
4138
  } | undefined;
4138
4139
  reviewRuns?: PrReviewResult[] | undefined;
package/dist/index.js CHANGED
@@ -17,7 +17,7 @@ import picomatch from "picomatch";
17
17
  import { parse } from "yaml";
18
18
  import { promisify } from "node:util";
19
19
  //#region package.json
20
- var version = "1.0.0-alpha.0";
20
+ var version = "1.0.0-alpha.1";
21
21
  //#endregion
22
22
  //#region src/review-rungs.ts
23
23
  const EVIDENCE_REVIEW_RUNGS$1 = [
@@ -894,8 +894,15 @@ function isValidGlob(pattern) {
894
894
  return false;
895
895
  }
896
896
  }
897
+ function isRepoRelativePath(value) {
898
+ const segments = value.split("/");
899
+ return value.length > 0 && !value.startsWith("/") && !/^[A-Za-z]:\//u.test(value) && !value.includes("\\") && !segments.some((segment) => segment === "" || segment === "." || segment === "..");
900
+ }
901
+ function isValidRepoRelativeGlob(pattern) {
902
+ return isRepoRelativePath(pattern) && !(pattern.startsWith("!") && !pattern.startsWith("!(")) && isValidGlob(pattern);
903
+ }
897
904
  function matchesAnyGlob(patterns, file) {
898
- return patterns.length > 0 && picomatch.isMatch(file, patterns);
905
+ return patterns.length > 0 && picomatch.isMatch(file, [...patterns]);
899
906
  }
900
907
  //#endregion
901
908
  //#region src/diff-classification.ts
@@ -939,8 +946,9 @@ const LEGACY_PROFILE_SCHEMA_VERSIONS = [
939
946
  3,
940
947
  4
941
948
  ];
942
- const PROFILE_JSON_SCHEMA_DESCRIPTION = "Editor validation covers the profile input shape and straightforward cross-field constraints. Zod remains the load-time enforcement authority: verification command names must be distinct and requiredChecks entries must have distinct names; these invariants are enforced at load time, not by this schema.";
949
+ const PROFILE_JSON_SCHEMA_DESCRIPTION = "Editor validation covers the profile input shape and straightforward cross-field constraints. Zod remains the load-time enforcement authority: verification command names must be distinct and requiredChecks entries must have distinct names; these invariants are enforced at load time, not by this schema. Impact target path patterns encode repo-relative safety in JSON Schema, while complete picomatch syntax validity remains a runtime-only refinement.";
943
950
  const globSchema = z.string().min(1).refine(isValidGlob, "must be a valid glob");
951
+ const repoRelativeGlobSchema = z.string().min(1).regex(/^(?!(?:\/[\s\S]*|[A-Za-z]:\/[\s\S]*|!(?!\()[\s\S]*|[\s\S]*\\[\s\S]*|[\s\S]*\/\/[\s\S]*|[\s\S]*\/$|\.{1,2}(?:\/|$)[\s\S]*|[\s\S]*\/\.{1,2}(?:\/|$)[\s\S]*)).+$/u, "must be a repo-relative positive glob").refine(isValidRepoRelativeGlob, "must be a valid repo-relative glob");
944
952
  const commandSchema = z.object({
945
953
  command: z.string().min(1),
946
954
  description: z.string().min(1),
@@ -972,7 +980,8 @@ const requiredCheckSchema = z.object({
972
980
  }).strict();
973
981
  const impactTargetSchema = z.object({
974
982
  importers: z.array(z.string().min(1)).min(1),
975
- name: z.string().min(1)
983
+ name: z.string().min(1),
984
+ paths: z.array(repoRelativeGlobSchema).min(1).optional()
976
985
  }).strict();
977
986
  const impactConfigSchema = z.object({ targets: z.array(impactTargetSchema).min(1).superRefine((targets, context) => {
978
987
  const names = targets.map((target) => target.name);
@@ -3845,9 +3854,9 @@ const impactStampTargetSchema = z.object({
3845
3854
  * input widened every target to full impact.
3846
3855
  */
3847
3856
  const impactStampSchema = z.object({
3848
- basis: z.enum(["lockfile-scoped", "conservative"]),
3857
+ basis: z.enum(["target-scoped", "conservative"]),
3849
3858
  reasons: z.array(z.string()),
3850
- stampVersion: z.literal(1),
3859
+ stampVersion: z.literal(2),
3851
3860
  targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
3852
3861
  const names = targets.map((target) => target.name);
3853
3862
  if (new Set(names).size !== names.length) context.addIssue({
@@ -3886,7 +3895,7 @@ const IMPACT_SCOPE_SURFACES = {
3886
3895
  * external proof demand (#542 wave 2), a verification-battery command, or a
3887
3896
  * preview (Alchemy) lifecycle stack (#430 wave 3).
3888
3897
  *
3889
- * The only release path is a `lockfile-scoped` stamp of exactly this build's
3898
+ * The only release path is a `target-scoped` stamp of exactly this build's
3890
3899
  * stamp version whose target entry for exactly this name is provably
3891
3900
  * `not-affected`. Every other input — no trusted stamp, an unknown stamp
3892
3901
  * version, a conservative stamp, a name the stamp does not classify, or an
@@ -3905,11 +3914,11 @@ const impactStampScopeDecision = ({ stamp, surface, targetName }) => {
3905
3914
  reason: `no trusted identity-bound impact stamp covers this candidate; ${floor}`,
3906
3915
  scoped: false
3907
3916
  };
3908
- if (stamp.stampVersion !== 1) return {
3909
- reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 1; ${floor}`,
3917
+ if (stamp.stampVersion !== 2) return {
3918
+ reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 2; ${floor}`,
3910
3919
  scoped: false
3911
3920
  };
3912
- if (stamp.basis !== "lockfile-scoped") return {
3921
+ if (stamp.basis !== "target-scoped") return {
3913
3922
  reason: `impact stamp is conservative (full impact assumed); ${floor}`,
3914
3923
  scoped: false
3915
3924
  };
@@ -3951,7 +3960,7 @@ const impactStampDemandRelease = ({ checkName, stamp }) => {
3951
3960
  const conservativeImpactStamp = (targets, reasons) => ({
3952
3961
  basis: "conservative",
3953
3962
  reasons,
3954
- stampVersion: 1,
3963
+ stampVersion: 2,
3955
3964
  targets: targets.map((target) => ({
3956
3965
  basis: CONSERVATIVE_TARGET_BASIS,
3957
3966
  impact: "affected",
@@ -4087,11 +4096,10 @@ const targetImpactForLockfileDelta = (target, delta) => {
4087
4096
  /**
4088
4097
  * Compute the impact stamp for one candidate: every declared target recorded
4089
4098
  * as affected/not-affected with its exact basis. `not-affected` is only ever
4090
- * returned for a provably-scoped pnpm-lock.yaml (v9) delta; every other input
4091
- * non-lockfile changed files, an empty diff, a missing, unparseable, or
4092
- * structurally malformed lockfile side, an unsupported lockfile version, or a
4093
- * delta outside the importers/packages/snapshots graph sections widens to
4094
- * full impact.
4099
+ * returned only when every changed input is owned by a declared source glob or
4100
+ * is an analyzable pnpm-lock.yaml (v9) delta. Source and lockfile reachability
4101
+ * are unioned per target. Any invalid, unmatched, unreadable, unsupported, or
4102
+ * otherwise doubtful input widens the whole stamp to full impact.
4095
4103
  *
4096
4104
  * Total by contract: this function never throws. Any unexpected failure in
4097
4105
  * reading, parsing, or traversal is itself a doubt path and returns the
@@ -4154,22 +4162,73 @@ const readLockfileSides = (readLockfile) => {
4154
4162
  const assertDeclaredImporterRoots = (targets, sides) => {
4155
4163
  for (const target of targets) for (const root of target.importers) if (!sides.some((lockfile) => Object.keys(lockfile.importers).some((importerPath) => importerMatchesTarget(importerPath, [root])))) throw new ImpactStampConfigError(`impact target "${target.name}" declares importer root "${root}", but no importer under that root exists in pnpm-lock.yaml on either side of the delta; fix the profile's impact.targets declaration`);
4156
4164
  };
4157
- const computeImpactStampOrThrow = ({ changedFiles, readLockfile }, targets) => {
4165
+ const assertDeclaredPathGlobs = (targets) => {
4166
+ for (const target of targets) for (const pattern of target.paths ?? []) if (!isValidRepoRelativeGlob(pattern)) throw new ImpactStampConfigError(`impact target "${target.name}" declares invalid repo-relative path glob "${pattern}"; fix the profile's impact.targets declaration`);
4167
+ };
4168
+ const analyzeSourcePaths = (sourceFiles, targets, profilePath) => {
4169
+ for (const file of sourceFiles) {
4170
+ if (!isRepoRelativePath(file)) return {
4171
+ kind: "conservative",
4172
+ reason: `changed source path "${file}" is not a valid repo-relative path; fail closed to full impact`
4173
+ };
4174
+ if (file === profilePath) return {
4175
+ kind: "conservative",
4176
+ reason: `changed source path "${file}" is the impact profile being evaluated; fail closed to full impact`
4177
+ };
4178
+ if (!targets.some((target) => matchesAnyGlob(target.paths ?? [], file))) return {
4179
+ kind: "conservative",
4180
+ reason: `changed source path "${file}" has no declared impact target owner; fail closed to full impact`
4181
+ };
4182
+ }
4183
+ return {
4184
+ kind: "ok",
4185
+ targets: targets.map((target) => {
4186
+ const matched = sourceFiles.find((file) => matchesAnyGlob(target.paths ?? [], file));
4187
+ return matched === void 0 ? {
4188
+ basis: "no changed source path matches this target's declared paths",
4189
+ impact: "not-affected",
4190
+ name: target.name
4191
+ } : {
4192
+ basis: `changed source path "${matched}" matches this target's declared paths`,
4193
+ impact: "affected",
4194
+ name: target.name
4195
+ };
4196
+ })
4197
+ };
4198
+ };
4199
+ const unionTargetImpact = (source, lockfile) => {
4200
+ if (source.impact === "affected") return source;
4201
+ if (lockfile.impact === "affected") return lockfile;
4202
+ return {
4203
+ basis: `${source.basis}; ${lockfile.basis}`,
4204
+ impact: "not-affected",
4205
+ name: source.name
4206
+ };
4207
+ };
4208
+ const computeImpactStampOrThrow = ({ changedFiles, profilePath = "software-factory.profile.json", readLockfile }, targets) => {
4158
4209
  if (targets.length === 0) return {
4159
4210
  basis: "conservative",
4160
4211
  reasons: ["no impact targets declared in the profile; every surface keeps full demand"],
4161
- stampVersion: 1,
4212
+ stampVersion: 2,
4162
4213
  targets: []
4163
4214
  };
4215
+ assertDeclaredPathGlobs(targets);
4164
4216
  const sides = readLockfileSides(readLockfile);
4165
4217
  if (sides.kind === "ok") assertDeclaredImporterRoots(targets, sides.parsed);
4166
4218
  if (changedFiles.length === 0) return conservativeStamp(targets, ["no changed files detected; full impact assumed"]);
4167
- const unmodelledFiles = changedFiles.filter((file) => file !== LOCKFILE_PATH);
4168
- if (unmodelledFiles.length > 0) return conservativeStamp(targets, [`changed files outside the lockfile classifier's model (${unmodelledFiles.toSorted().join(", ")}); full impact assumed`]);
4219
+ const sourceFiles = changedFiles.filter((file) => file !== LOCKFILE_PATH);
4220
+ const source = analyzeSourcePaths(sourceFiles, targets, profilePath);
4221
+ if (source.kind === "conservative") return conservativeStamp(targets, [source.reason]);
4169
4222
  if (sides.kind === "threw") throw sides.error;
4170
4223
  if (sides.kind === "unreadable") return conservativeStamp(targets, [`pnpm-lock.yaml is unreadable at the ${sides.side} side; fail closed to full impact`]);
4171
4224
  if (sides.kind === "unparseable") return conservativeStamp(targets, [`pnpm-lock.yaml is unparseable or structurally malformed at the ${sides.side} side; fail closed to full impact`]);
4172
4225
  if (sides.kind === "unsupported-version") return conservativeStamp(targets, [`pnpm-lock.yaml is not lockfileVersion ${SUPPORTED_LOCKFILE_VERSION}; only pnpm v9 lockfiles are modelled; fail closed to full impact`]);
4226
+ if (!changedFiles.includes("pnpm-lock.yaml")) return {
4227
+ basis: "target-scoped",
4228
+ reasons: ["all changed source paths have declared target ownership; impact scoped by repo-relative path globs"],
4229
+ stampVersion: 2,
4230
+ targets: source.targets
4231
+ };
4173
4232
  const [baseLock, headLock] = sides.parsed;
4174
4233
  if (stableStringify$1(nonGraphSections(baseLock)) !== stableStringify$1(nonGraphSections(headLock))) return conservativeStamp(targets, ["pnpm-lock.yaml delta touches sections outside importers/packages/snapshots (e.g. settings, overrides, patchedDependencies); fail closed to full impact"]);
4175
4234
  const delta = {
@@ -4178,11 +4237,13 @@ const computeImpactStampOrThrow = ({ changedFiles, readLockfile }, targets) => {
4178
4237
  changedSnapshots: changedSectionKeys(baseLock.snapshots, headLock.snapshots),
4179
4238
  sides: [baseLock, headLock]
4180
4239
  };
4240
+ const lockfileTargets = targets.map((target) => targetImpactForLockfileDelta(target, delta));
4241
+ const mixed = sourceFiles.length > 0;
4181
4242
  return {
4182
- basis: "lockfile-scoped",
4183
- reasons: ["pnpm-lock.yaml is the only changed file; impact scoped by importer/snapshot graph analysis"],
4184
- stampVersion: 1,
4185
- targets: targets.map((target) => targetImpactForLockfileDelta(target, delta))
4243
+ basis: "target-scoped",
4244
+ reasons: mixed ? ["all changed source paths have declared target ownership and pnpm-lock.yaml is analyzable; impact is the union of path ownership and importer/snapshot graph analysis"] : ["pnpm-lock.yaml is the only changed file; impact scoped by importer/snapshot graph analysis"],
4245
+ stampVersion: 2,
4246
+ targets: mixed ? source.targets.map((sourceTarget, index) => unionTargetImpact(sourceTarget, lockfileTargets[index])) : lockfileTargets
4186
4247
  };
4187
4248
  };
4188
4249
  //#endregion
@@ -4361,7 +4422,7 @@ const assertBatteryCompleteness = (proof, context) => {
4361
4422
  * so authorization is bound to the same identities the proof binds. The
4362
4423
  * predicate is the shared {@link impactStampScopeDecision}, not a second
4363
4424
  * reading of the stamp: an entry is authorized exactly when the stamp would
4364
- * have released that target's command in the first place (lockfile-scoped
4425
+ * have released that target's command in the first place (target-scoped
4365
4426
  * basis, this build's stamp version, exactly one row for the target,
4366
4427
  * `not-affected`). A conservative stamp, an unknown version, a
4367
4428
  * self-contradictory stamp, an unclassified name, or an `affected` verdict all
@@ -11167,6 +11228,7 @@ function runPrVerify(args, dependencies = {}) {
11167
11228
  impactStamp = computeImpactStamp({
11168
11229
  changedFiles: files,
11169
11230
  profile,
11231
+ profilePath: relativeProfile,
11170
11232
  readLockfile: (side) => readLockfileAtRef(cwd, side === "base" ? proofMergeBaseSha : headSha, path.resolve(cwd, LOCKFILE_PATH))
11171
11233
  });
11172
11234
  } catch (error) {
package/dist/schemas.d.ts CHANGED
@@ -493,11 +493,11 @@ interface PrReadyProof {
493
493
  */
494
494
  declare const impactStampSchema: z.ZodObject<{
495
495
  basis: z.ZodEnum<{
496
- "lockfile-scoped": "lockfile-scoped";
496
+ "target-scoped": "target-scoped";
497
497
  conservative: "conservative";
498
498
  }>;
499
499
  reasons: z.ZodArray<z.ZodString>;
500
- stampVersion: z.ZodLiteral<1>;
500
+ stampVersion: z.ZodLiteral<2>;
501
501
  targets: z.ZodArray<z.ZodObject<{
502
502
  basis: z.ZodString;
503
503
  impact: z.ZodEnum<{
@@ -1362,11 +1362,11 @@ declare const prVerifyProofSchema: z.ZodObject<{
1362
1362
  headSha: z.ZodString;
1363
1363
  impactStamp: z.ZodOptional<z.ZodObject<{
1364
1364
  basis: z.ZodEnum<{
1365
- "lockfile-scoped": "lockfile-scoped";
1365
+ "target-scoped": "target-scoped";
1366
1366
  conservative: "conservative";
1367
1367
  }>;
1368
1368
  reasons: z.ZodArray<z.ZodString>;
1369
- stampVersion: z.ZodLiteral<1>;
1369
+ stampVersion: z.ZodLiteral<2>;
1370
1370
  targets: z.ZodArray<z.ZodObject<{
1371
1371
  basis: z.ZodString;
1372
1372
  impact: z.ZodEnum<{
package/dist/schemas.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { z } from "zod";
2
2
  import "yaml";
3
+ import "picomatch";
3
4
  //#region src/review-rungs.ts
4
5
  const EVIDENCE_REVIEW_RUNGS$1 = [
5
6
  "independent-model",
@@ -239,9 +240,9 @@ const impactStampTargetSchema = z.object({
239
240
  * input widened every target to full impact.
240
241
  */
241
242
  const impactStampSchema = z.object({
242
- basis: z.enum(["lockfile-scoped", "conservative"]),
243
+ basis: z.enum(["target-scoped", "conservative"]),
243
244
  reasons: z.array(z.string()),
244
- stampVersion: z.literal(1),
245
+ stampVersion: z.literal(2),
245
246
  targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
246
247
  const names = targets.map((target) => target.name);
247
248
  if (new Set(names).size !== names.length) context.addIssue({
@@ -267,7 +268,7 @@ const IMPACT_SCOPE_SURFACES = {
267
268
  * external proof demand (#542 wave 2), a verification-battery command, or a
268
269
  * preview (Alchemy) lifecycle stack (#430 wave 3).
269
270
  *
270
- * The only release path is a `lockfile-scoped` stamp of exactly this build's
271
+ * The only release path is a `target-scoped` stamp of exactly this build's
271
272
  * stamp version whose target entry for exactly this name is provably
272
273
  * `not-affected`. Every other input — no trusted stamp, an unknown stamp
273
274
  * version, a conservative stamp, a name the stamp does not classify, or an
@@ -286,11 +287,11 @@ const impactStampScopeDecision = ({ stamp, surface, targetName }) => {
286
287
  reason: `no trusted identity-bound impact stamp covers this candidate; ${floor}`,
287
288
  scoped: false
288
289
  };
289
- if (stamp.stampVersion !== 1) return {
290
- reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 1; ${floor}`,
290
+ if (stamp.stampVersion !== 2) return {
291
+ reason: `impact stamp version ${String(stamp.stampVersion)} is not this build's version 2; ${floor}`,
291
292
  scoped: false
292
293
  };
293
- if (stamp.basis !== "lockfile-scoped") return {
294
+ if (stamp.basis !== "target-scoped") return {
294
295
  reason: `impact stamp is conservative (full impact assumed); ${floor}`,
295
296
  scoped: false
296
297
  };
@@ -435,7 +436,7 @@ const assertBatteryCompleteness = (proof, context) => {
435
436
  * so authorization is bound to the same identities the proof binds. The
436
437
  * predicate is the shared {@link impactStampScopeDecision}, not a second
437
438
  * reading of the stamp: an entry is authorized exactly when the stamp would
438
- * have released that target's command in the first place (lockfile-scoped
439
+ * have released that target's command in the first place (target-scoped
439
440
  * basis, this build's stamp version, exactly one row for the target,
440
441
  * `not-affected`). A conservative stamp, an unknown version, a
441
442
  * self-contradictory stamp, an unclassified name, or an `affected` verdict all
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/software-factory",
3
- "version": "1.0.0-alpha.0",
3
+ "version": "1.0.0-alpha.1",
4
4
  "description": "Shared Patronage software factory CLI and project-profile validation tools",
5
5
  "license": "MIT",
6
6
  "repository": {