@patronage/software-factory 1.0.0-alpha.16 → 1.0.0-alpha.17

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/CONTEXT.md CHANGED
@@ -20,7 +20,7 @@ The shared Patronage software factory: typed lifecycle state, proof gates, and o
20
20
 
21
21
  ### Impact
22
22
 
23
- **Impact**: The candidate-bound classification of which declared product targets a changed path or lockfile graph can reach. Path impact is opt-in: a target is affected if and only if a valid changed path matches one of its declared globs. A valid path matched by no target is **unsubscribed** and demands no target-scoped work, while remaining explicit in the stamp basis and human summary. Shared paths affect every target that subscribes to them. Invalid paths and unreadable or malformed required inputs still fail closed. Impact may withdraw target-scoped work; it never certifies a merge target, whose push-triggered verification stays full. Defined by ADR 0027. _Avoid_: affectedness; ownership (the declaration is a subscription, not an implicit filesystem owner).
23
+ **Impact**: The candidate-bound classification of which declared product targets a changed path or lockfile graph can reach. Path impact is opt-in: a target is affected if and only if a valid changed path matches one of its declared globs. A valid path matched by no target is **unsubscribed** and demands no target-scoped work, while remaining explicit in the stamp basis and human summary. A valid path that matches `impact.inert` and no target is **inert-owned**: the stamp records it in `inertPaths`, distinct from `unsubscribedPaths`. A path matching both a target subscription and an inert glob is product-owned and appears in neither list. `pnpm-lock.yaml` is a graph input and is never inert. Shared paths affect every target that subscribes to them. Invalid paths and unreadable or malformed required inputs still fail closed. Impact may withdraw target-scoped work; it never certifies a merge target, whose push-triggered verification stays full. Defined by ADR 0027. _Avoid_: affectedness; implicit filesystem ownership (target `paths` are subscriptions; inert-owned is the explicit `impact.inert` declaration).
24
24
 
25
25
  ### Planning intake
26
26
 
package/dist/index.d.ts CHANGED
@@ -16,8 +16,8 @@ declare const boundaryManifestBaseSchema: z.ZodObject<{
16
16
  boundary: z.ZodString;
17
17
  closeout: z.ZodOptional<z.ZodObject<{
18
18
  review: z.ZodEnum<{
19
- "independent-model": "independent-model";
20
19
  oracle: "oracle";
20
+ "independent-model": "independent-model";
21
21
  human: "human";
22
22
  }>;
23
23
  }, z.core.$loose>>;
@@ -35,8 +35,8 @@ declare const boundaryManifestBaseSchema: z.ZodObject<{
35
35
  issues: z.ZodArray<z.ZodNumber>;
36
36
  name: z.ZodString;
37
37
  review: z.ZodEnum<{
38
- "independent-model": "independent-model";
39
38
  oracle: "oracle";
39
+ "independent-model": "independent-model";
40
40
  human: "human";
41
41
  }>;
42
42
  }, z.core.$loose>>;
@@ -45,8 +45,8 @@ declare const boundaryManifestSchema: z.ZodObject<{
45
45
  boundary: z.ZodString;
46
46
  closeout: z.ZodOptional<z.ZodObject<{
47
47
  review: z.ZodEnum<{
48
- "independent-model": "independent-model";
49
48
  oracle: "oracle";
49
+ "independent-model": "independent-model";
50
50
  human: "human";
51
51
  }>;
52
52
  }, z.core.$loose>>;
@@ -64,8 +64,8 @@ declare const boundaryManifestSchema: z.ZodObject<{
64
64
  issues: z.ZodArray<z.ZodNumber>;
65
65
  name: z.ZodString;
66
66
  review: z.ZodEnum<{
67
- "independent-model": "independent-model";
68
67
  oracle: "oracle";
68
+ "independent-model": "independent-model";
69
69
  human: "human";
70
70
  }>;
71
71
  }, z.core.$loose>>;
@@ -126,8 +126,8 @@ declare const boundaryReviewProofBaseSchema: z.ZodObject<{
126
126
  mergedSha: z.ZodOptional<z.ZodString>;
127
127
  pr: z.ZodNumber;
128
128
  state: z.ZodEnum<{
129
- merged: "merged";
130
129
  open: "open";
130
+ merged: "merged";
131
131
  }>;
132
132
  wave: z.ZodOptional<z.ZodString>;
133
133
  }, z.core.$loose>>;
@@ -150,8 +150,8 @@ declare const boundaryReviewProofBaseSchema: z.ZodObject<{
150
150
  }>;
151
151
  producer: z.ZodString;
152
152
  rung: z.ZodEnum<{
153
- "independent-model": "independent-model";
154
153
  oracle: "oracle";
154
+ "independent-model": "independent-model";
155
155
  human: "human";
156
156
  }>;
157
157
  schemaVersion: z.ZodLiteral<1>;
@@ -170,8 +170,8 @@ declare const boundaryReviewProofSchema: z.ZodObject<{
170
170
  mergedSha: z.ZodOptional<z.ZodString>;
171
171
  pr: z.ZodNumber;
172
172
  state: z.ZodEnum<{
173
- merged: "merged";
174
173
  open: "open";
174
+ merged: "merged";
175
175
  }>;
176
176
  wave: z.ZodOptional<z.ZodString>;
177
177
  }, z.core.$loose>>;
@@ -194,8 +194,8 @@ declare const boundaryReviewProofSchema: z.ZodObject<{
194
194
  }>;
195
195
  producer: z.ZodString;
196
196
  rung: z.ZodEnum<{
197
- "independent-model": "independent-model";
198
197
  oracle: "oracle";
198
+ "independent-model": "independent-model";
199
199
  human: "human";
200
200
  }>;
201
201
  schemaVersion: z.ZodLiteral<1>;
@@ -645,6 +645,7 @@ declare const impactStampSchema: z.ZodObject<{
645
645
  "target-scoped": "target-scoped";
646
646
  conservative: "conservative";
647
647
  }>;
648
+ inertPaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
648
649
  reasons: z.ZodArray<z.ZodString>;
649
650
  stampVersion: z.ZodLiteral<3>;
650
651
  targets: z.ZodArray<z.ZodObject<{
@@ -732,7 +733,6 @@ declare const factoryProjectProfileSchema: z.ZodObject<{
732
733
  scope: z.ZodOptional<z.ZodObject<{
733
734
  classifications: z.ZodArray<z.ZodEnum<{
734
735
  "docs/process-only": "docs/process-only";
735
- trivial: "trivial";
736
736
  "non-trivial": "non-trivial";
737
737
  }>>;
738
738
  }, z.core.$strict>>;
@@ -922,8 +922,8 @@ declare const mergeFreezeStateSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
922
922
  generationId: z.ZodNumber;
923
923
  headSha: z.ZodString;
924
924
  outcome: z.ZodEnum<{
925
- active: "active";
926
925
  stale: "stale";
926
+ active: "active";
927
927
  }>;
928
928
  reason: z.ZodString;
929
929
  recordedAt: z.ZodISODateTime;
@@ -1416,10 +1416,10 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1416
1416
  reviewedHeadSha: z.ZodOptional<z.ZodString>;
1417
1417
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1418
1418
  status: z.ZodEnum<{
1419
- stale: "stale";
1420
1419
  blocked: "blocked";
1421
1420
  "not-required": "not-required";
1422
1421
  current: "current";
1422
+ stale: "stale";
1423
1423
  missing: "missing";
1424
1424
  }>;
1425
1425
  }, z.core.$strip>;
@@ -1428,10 +1428,10 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1428
1428
  reviewedHeadSha: z.ZodOptional<z.ZodString>;
1429
1429
  reviewedPatchId: z.ZodOptional<z.ZodString>;
1430
1430
  status: z.ZodEnum<{
1431
- stale: "stale";
1432
1431
  blocked: "blocked";
1433
1432
  "not-required": "not-required";
1434
1433
  current: "current";
1434
+ stale: "stale";
1435
1435
  missing: "missing";
1436
1436
  }>;
1437
1437
  }, z.core.$strip>>;
@@ -1442,8 +1442,8 @@ declare const managedReadinessLedgerSchema: z.ZodObject<{
1442
1442
  docsOnlyDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
1443
1443
  docsOnlyVerifiedHeadSha: z.ZodOptional<z.ZodString>;
1444
1444
  prVerify: z.ZodEnum<{
1445
- stale: "stale";
1446
1445
  passed: "passed";
1446
+ stale: "stale";
1447
1447
  missing: "missing";
1448
1448
  }>;
1449
1449
  trivialDeltaAccepted: z.ZodOptional<z.ZodBoolean>;
@@ -1743,8 +1743,8 @@ declare const evidenceEnvelopeBaseSchema: z.ZodObject<{
1743
1743
  producer: z.ZodString;
1744
1744
  requestId: z.ZodOptional<z.ZodString>;
1745
1745
  rung: z.ZodOptional<z.ZodEnum<{
1746
- "independent-model": "independent-model";
1747
1746
  oracle: "oracle";
1747
+ "independent-model": "independent-model";
1748
1748
  human: "human";
1749
1749
  }>>;
1750
1750
  schemaVersion: z.ZodLiteral<1>;
@@ -1770,8 +1770,8 @@ declare const evidenceEnvelopeSchema: z.ZodObject<{
1770
1770
  producer: z.ZodString;
1771
1771
  requestId: z.ZodOptional<z.ZodString>;
1772
1772
  rung: z.ZodOptional<z.ZodEnum<{
1773
- "independent-model": "independent-model";
1774
1773
  oracle: "oracle";
1774
+ "independent-model": "independent-model";
1775
1775
  human: "human";
1776
1776
  }>>;
1777
1777
  schemaVersion: z.ZodLiteral<1>;
@@ -1874,10 +1874,10 @@ declare const loadEvidenceEnvelopes: (cwd: string) => LoadedEvidenceEnvelope[];
1874
1874
  //#region src/review-proof-applicability.d.ts
1875
1875
  declare const REVIEW_STATUS_VALUES: readonly ["not-required", "current", "stale", "missing", "blocked"];
1876
1876
  declare const reviewStatusSchema: z.ZodEnum<{
1877
- stale: "stale";
1878
1877
  blocked: "blocked";
1879
1878
  "not-required": "not-required";
1880
1879
  current: "current";
1880
+ stale: "stale";
1881
1881
  missing: "missing";
1882
1882
  }>;
1883
1883
  type ReviewStatus = z.infer<typeof reviewStatusSchema>;
@@ -2020,6 +2020,16 @@ interface PrVerifyNotRequiredCommand {
2020
2020
  impactTarget: string;
2021
2021
  name: string;
2022
2022
  }
2023
+ /**
2024
+ * Epic #758 (#762): a consumer package a stamp-scoped selection skipped, and
2025
+ * why. Bound to the proof's stamp identity — the field is v4-only (the same
2026
+ * window that can carry `impactStamp`) and refused when the proof records no
2027
+ * stamp. The factory does not write this list; consumers do.
2028
+ */
2029
+ interface PrVerifyNotDemandedRecord {
2030
+ basis: string;
2031
+ name: string;
2032
+ }
2023
2033
  interface PrVerifyProof {
2024
2034
  schemaVersion: PrVerifySchemaVersion;
2025
2035
  authoringSession?: string;
@@ -2043,6 +2053,7 @@ interface PrVerifyProof {
2043
2053
  impactStamp?: ImpactStamp;
2044
2054
  mode: ResolvedPrVerifyMode;
2045
2055
  mergeBaseSha?: string;
2056
+ notDemandedRecords?: PrVerifyNotDemandedRecord[];
2046
2057
  notRequiredCommands?: PrVerifyNotRequiredCommand[];
2047
2058
  outcome?: "aborted" | "passed";
2048
2059
  patchId?: string;
@@ -2065,14 +2076,14 @@ declare const boundaryCheckProofSchema: z.ZodObject<{
2065
2076
  pr: z.ZodNumber;
2066
2077
  sha: z.ZodString;
2067
2078
  state: z.ZodEnum<{
2068
- merged: "merged";
2069
2079
  open: "open";
2080
+ merged: "merged";
2070
2081
  }>;
2071
2082
  wave: z.ZodString;
2072
2083
  }, z.core.$strip>>;
2073
2084
  demandedRung: z.ZodOptional<z.ZodEnum<{
2074
- "independent-model": "independent-model";
2075
2085
  oracle: "oracle";
2086
+ "independent-model": "independent-model";
2076
2087
  human: "human";
2077
2088
  }>>;
2078
2089
  epicIssue: z.ZodNumber;
@@ -2087,8 +2098,8 @@ declare const boundaryCheckProofSchema: z.ZodObject<{
2087
2098
  commentUrl: z.ZodOptional<z.ZodString>;
2088
2099
  producer: z.ZodString;
2089
2100
  rung: z.ZodEnum<{
2090
- "independent-model": "independent-model";
2091
2101
  oracle: "oracle";
2102
+ "independent-model": "independent-model";
2092
2103
  human: "human";
2093
2104
  }>;
2094
2105
  sessionId: z.ZodOptional<z.ZodString>;
@@ -2227,7 +2238,7 @@ interface PrReviewDependencies {
2227
2238
  }
2228
2239
  declare function runPrReview(args: PrReviewArgs, dependencies?: PrReviewDependencies): Promise<PrReviewProof>;
2229
2240
  declare namespace verification_proof_d_exports {
2230
- export { PrVerifyBaselineFullProof, PrVerifyCommandResult, PrVerifyExecutedCommand, PrVerifyNotRequiredCommand, PrVerifyProof, PrVerifySchemaVersion, ResolvedPrVerifyMode, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, VerificationHeadShas, VerificationProofState, VerifiedHeadShas, prVerifyFullHeadShasForDiff, prVerifyProofHeadShas, readPrVerifyProof, resolveVerifyProofApplicability, trustedImpactStamp, validatePrVerifyProof, verificationProofApplicabilityFor, verificationProofBlockingReason, verificationProofForReadiness, verificationProofStateFor, verifyProofIdentityMatches, verifyProofPassed };
2241
+ export { PrVerifyBaselineFullProof, PrVerifyCommandResult, PrVerifyExecutedCommand, PrVerifyNotDemandedRecord, PrVerifyNotRequiredCommand, PrVerifyProof, PrVerifySchemaVersion, ResolvedPrVerifyMode, SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS, VerificationHeadShas, VerificationProofState, VerifiedHeadShas, prVerifyFullHeadShasForDiff, prVerifyProofHeadShas, readPrVerifyProof, resolveVerifyProofApplicability, trustedImpactStamp, validatePrVerifyProof, verificationProofApplicabilityFor, verificationProofBlockingReason, verificationProofForReadiness, verificationProofStateFor, verifyProofIdentityMatches, verifyProofPassed };
2231
2242
  }
2232
2243
  interface VerifiedHeadShas {
2233
2244
  docsOnlyVerifiedHeadShas: string[];
@@ -2383,7 +2394,7 @@ interface CleanInstallResolutionDependencies {
2383
2394
  }
2384
2395
  //#endregion
2385
2396
  //#region src/pr-verify.d.ts
2386
- type PrVerifyMode = "auto" | "docs-only" | "trivial" | "full";
2397
+ type PrVerifyMode = "auto" | "docs-only" | "full";
2387
2398
  interface VerificationCommandOutcome {
2388
2399
  counts?: {
2389
2400
  testFiles?: number;
@@ -3223,8 +3234,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3223
3234
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3224
3235
  observedAt: z.ZodString;
3225
3236
  purpose: z.ZodEnum<{
3226
- code: "code";
3227
3237
  review: "review";
3238
+ code: "code";
3228
3239
  }>;
3229
3240
  slotResolution: z.ZodObject<{
3230
3241
  effort: z.ZodEnum<{
@@ -3242,18 +3253,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3242
3253
  }>;
3243
3254
  model: z.ZodString;
3244
3255
  tier: z.ZodEnum<{
3245
- oracle: "oracle";
3246
3256
  review: "review";
3247
3257
  delegate: "delegate";
3258
+ oracle: "oracle";
3248
3259
  workhorse: "workhorse";
3249
3260
  scout: "scout";
3250
3261
  }>;
3251
3262
  }, z.core.$strict>;
3252
3263
  startedAt: z.ZodString;
3253
3264
  tier: z.ZodEnum<{
3254
- oracle: "oracle";
3255
3265
  review: "review";
3256
3266
  delegate: "delegate";
3267
+ oracle: "oracle";
3257
3268
  workhorse: "workhorse";
3258
3269
  scout: "scout";
3259
3270
  }>;
@@ -3302,8 +3313,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3302
3313
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3303
3314
  observedAt: z.ZodString;
3304
3315
  purpose: z.ZodEnum<{
3305
- code: "code";
3306
3316
  review: "review";
3317
+ code: "code";
3307
3318
  }>;
3308
3319
  slotResolution: z.ZodObject<{
3309
3320
  effort: z.ZodEnum<{
@@ -3321,18 +3332,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3321
3332
  }>;
3322
3333
  model: z.ZodString;
3323
3334
  tier: z.ZodEnum<{
3324
- oracle: "oracle";
3325
3335
  review: "review";
3326
3336
  delegate: "delegate";
3337
+ oracle: "oracle";
3327
3338
  workhorse: "workhorse";
3328
3339
  scout: "scout";
3329
3340
  }>;
3330
3341
  }, z.core.$strict>;
3331
3342
  startedAt: z.ZodString;
3332
3343
  tier: z.ZodEnum<{
3333
- oracle: "oracle";
3334
3344
  review: "review";
3335
3345
  delegate: "delegate";
3346
+ oracle: "oracle";
3336
3347
  workhorse: "workhorse";
3337
3348
  scout: "scout";
3338
3349
  }>;
@@ -3372,8 +3383,8 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3372
3383
  interiorCycle: z.ZodOptional<z.ZodNumber>;
3373
3384
  observedAt: z.ZodString;
3374
3385
  purpose: z.ZodEnum<{
3375
- code: "code";
3376
3386
  review: "review";
3387
+ code: "code";
3377
3388
  }>;
3378
3389
  slotResolution: z.ZodObject<{
3379
3390
  effort: z.ZodEnum<{
@@ -3391,18 +3402,18 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3391
3402
  }>;
3392
3403
  model: z.ZodString;
3393
3404
  tier: z.ZodEnum<{
3394
- oracle: "oracle";
3395
3405
  review: "review";
3396
3406
  delegate: "delegate";
3407
+ oracle: "oracle";
3397
3408
  workhorse: "workhorse";
3398
3409
  scout: "scout";
3399
3410
  }>;
3400
3411
  }, z.core.$strict>;
3401
3412
  startedAt: z.ZodString;
3402
3413
  tier: z.ZodEnum<{
3403
- oracle: "oracle";
3404
3414
  review: "review";
3405
3415
  delegate: "delegate";
3416
+ oracle: "oracle";
3406
3417
  workhorse: "workhorse";
3407
3418
  scout: "scout";
3408
3419
  }>;
@@ -3445,17 +3456,17 @@ declare const FACTORY_TRACE_EVENT_DEFINITIONS: readonly [DefinedTraceEvent<"revi
3445
3456
  exitCode: number | null;
3446
3457
  harness: string;
3447
3458
  observedAt: string;
3448
- purpose: "code" | "review";
3459
+ purpose: "review" | "code";
3449
3460
  slotResolution: {
3450
3461
  effort: "high" | "low" | "medium" | "xhigh";
3451
3462
  engine: string;
3452
3463
  layerSource: "committedDefault" | "operatorSession" | "userConfig";
3453
3464
  model: string;
3454
- tier: "oracle" | "review" | "delegate" | "workhorse" | "scout";
3465
+ tier: "review" | "delegate" | "oracle" | "workhorse" | "scout";
3455
3466
  fallbackTier?: "delegate" | undefined;
3456
3467
  };
3457
3468
  startedAt: string;
3458
- tier: "oracle" | "review" | "delegate" | "workhorse" | "scout";
3469
+ tier: "review" | "delegate" | "oracle" | "workhorse" | "scout";
3459
3470
  tokenUsage: {
3460
3471
  source: "cursor-csv" | "codex-json" | "cursor-json" | "claude-json";
3461
3472
  status: "available";
@@ -3792,13 +3803,13 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
3792
3803
  reviews: {
3793
3804
  correctness: {
3794
3805
  required: boolean;
3795
- status: "stale" | "blocked" | "not-required" | "current" | "missing";
3806
+ status: "blocked" | "not-required" | "current" | "stale" | "missing";
3796
3807
  reviewedHeadSha?: string | undefined;
3797
3808
  reviewedPatchId?: string | undefined;
3798
3809
  };
3799
3810
  security?: {
3800
3811
  required: boolean;
3801
- status: "stale" | "blocked" | "not-required" | "current" | "missing";
3812
+ status: "blocked" | "not-required" | "current" | "stale" | "missing";
3802
3813
  reviewedHeadSha?: string | undefined;
3803
3814
  reviewedPatchId?: string | undefined;
3804
3815
  } | undefined;
@@ -3806,7 +3817,7 @@ declare const evaluateReadiness: (input: EvaluationInput) => {
3806
3817
  schemaVersion: 1;
3807
3818
  verification: {
3808
3819
  command: "patronage-factory pr:verify";
3809
- prVerify: "stale" | "passed" | "missing";
3820
+ prVerify: "passed" | "stale" | "missing";
3810
3821
  docsOnlyDeltaAccepted?: boolean | undefined;
3811
3822
  docsOnlyVerifiedHeadSha?: string | undefined;
3812
3823
  trivialDeltaAccepted?: boolean | undefined;
@@ -4174,6 +4185,34 @@ declare const appendReviewLadderStageTraceEvent: ({
4174
4185
  }) => AppendFactoryTraceEventResult;
4175
4186
  declare const tryAppendReviewLadderStageTraceEvent: (input: Parameters<typeof appendReviewLadderStageTraceEvent>[0]) => ReviewLadderStageTraceWriteResult;
4176
4187
  //#endregion
4188
+ //#region src/verification-run-context.d.ts
4189
+ declare const FACTORY_BASE_REF_ENV = "FACTORY_BASE_REF";
4190
+ declare const FACTORY_CHANGED_FILES_ENV = "FACTORY_CHANGED_FILES";
4191
+ declare const FACTORY_CHANGED_FILES_FILE_ENV = "FACTORY_CHANGED_FILES_FILE";
4192
+ declare const EMPTY_TREE_OBJECT_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
4193
+ declare const DEFAULT_ROOT_SHARED_GLOBS: string[];
4194
+ interface VerificationRunContextInput {
4195
+ baseRef: string;
4196
+ changedFiles: string[];
4197
+ fullSuiteBaseRef?: string;
4198
+ rootSharedGlobs?: string[];
4199
+ inlineLimitBytes?: number;
4200
+ tmpDir?: string;
4201
+ }
4202
+ interface VerificationEnv {
4203
+ env: Record<string, string>;
4204
+ cleanup: () => void;
4205
+ }
4206
+ interface VerificationRunContext {
4207
+ readonly baseRef: string;
4208
+ readonly changedFiles: string[];
4209
+ readonly forcesFullSuite: boolean;
4210
+ readonly fullSuiteReasons: string[];
4211
+ buildEnv: () => VerificationEnv;
4212
+ runSummaryLines: () => string[];
4213
+ }
4214
+ declare function createVerificationRunContext(input: VerificationRunContextInput): VerificationRunContext;
4215
+ //#endregion
4177
4216
  //#region src/verification-battery.d.ts
4178
4217
  type BatteryDisposition = "executed" | "not-required";
4179
4218
  /**
@@ -4370,4 +4409,4 @@ interface CreateProgramOptions {
4370
4409
  declare function createProgram(options?: CreateProgramOptions): Command;
4371
4410
  declare function run(argv?: string[]): Promise<void>;
4372
4411
  //#endregion
4373
- export { type AppendFactoryTraceEventResult, type AssembledReviewPrompt, type BatteryCommandDisposition, type BatteryDisposition, type BoundaryCheckArgs, type BoundaryCheckDependencies, type BoundaryCheckProofRecord, type BuildEpicStructureEventInput, type CloudflareAccessServiceToken, CreateProgramOptions, DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, type DagDocument, type DemandWaiveArgs, type DemandWaiveDependencies, DemandWaiveRefusalError, type DemandWaiver, type DemandWaiverStore, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, type EpicStructureEvent, type EpicStructureGraphPayload, type EpicStructureNodeStatus, EpicStructureValidationError, type EvidenceEmitArgs, type EvidenceEmitDependencies, type EvidenceEmitResult, type FactoryCliInvocation, FactoryCliInvocationSchema, type FactoryProjectProfile, type FactoryTraceDiagnostic, type FactoryTraceEnvelope, type FactoryTraceEvent, type FindingDisposition, type FindingLedgerEntry, type FollowUpAction, FollowUpActionSchema, type ImpactScopeDecision, type ImpactScopeSurface, type IssueReviewFocus, type LadderDispositionDeclaration, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, type MergeFreezeState, type PrPublishArgs, type PrPublishDependencies, PrPublishFollowUpError, type PrPublishFollowUpOutcome, type PrPublishHandoff, type PrPublishResult, type PrReadyArgs, type PrReadyProof, type PreviewDisposition, type PreviewLifecyclePlan, type PreviewTargetPlan, type PublishEpicStructureArgs, type PublishEpicStructureResult, type PublishFollowUpPlan, REVIEW_FOCUS_SECTION, type ReviewGateNotRequiredProof, type ReviewGateTraceIdentity, type ReviewLadderCycle, type ReviewLadderEvaluation, type ReviewLadderPolicy, type ReviewLadderStageEvent, type ReviewLadderTraceIdentity, type ReviewPromptSection, type ReviewPromptSectionProvenance, type ScanFactoryTraceDiagnosticsOptions, type ScanFactoryTraceOptions, type ScanFactoryTraceResult, type ScopedOutCommand, type TraceMirrorDiagnostic, type TraceSink, type TraceWriteResult, type TraceWriteSinks, type VerificationBatteryPlan, type VerificationReuse, type WaivedDemand, WorkerCheckoutGuardError, type WorkerCloseoutLessonsTraceEvent, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_d_exports as boundaryManifest, boundary_review_proof_d_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_d_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_d_exports as prReadinessEvaluation, external_evidence_d_exports as prReadinessExternalEvidence, pr_body_renderer_d_exports as prReadinessPrBodyRenderer, proof_identity_d_exports as prReadinessProofIdentity, review_proof_d_exports as prReadinessReviewProof, status_check_rollup_d_exports as prReadinessStatusChecks, verification_proof_d_exports as prReadinessVerificationProof, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_d_exports as worktreeScratchFiles };
4412
+ export { type AppendFactoryTraceEventResult, type AssembledReviewPrompt, type BatteryCommandDisposition, type BatteryDisposition, type BoundaryCheckArgs, type BoundaryCheckDependencies, type BoundaryCheckProofRecord, type BuildEpicStructureEventInput, type CloudflareAccessServiceToken, CreateProgramOptions, DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, DEFAULT_ROOT_SHARED_GLOBS, type DagDocument, type DemandWaiveArgs, type DemandWaiveDependencies, DemandWaiveRefusalError, type DemandWaiver, type DemandWaiverStore, EMPTY_TREE_OBJECT_HASH, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, type EpicStructureEvent, type EpicStructureGraphPayload, type EpicStructureNodeStatus, EpicStructureValidationError, type EvidenceEmitArgs, type EvidenceEmitDependencies, type EvidenceEmitResult, FACTORY_BASE_REF_ENV, FACTORY_CHANGED_FILES_ENV, FACTORY_CHANGED_FILES_FILE_ENV, type FactoryCliInvocation, FactoryCliInvocationSchema, type FactoryProjectProfile, type FactoryTraceDiagnostic, type FactoryTraceEnvelope, type FactoryTraceEvent, type FindingDisposition, type FindingLedgerEntry, type FollowUpAction, FollowUpActionSchema, type ImpactScopeDecision, type ImpactScopeSurface, type IssueReviewFocus, type LadderDispositionDeclaration, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, type MergeFreezeState, type PrPublishArgs, type PrPublishDependencies, PrPublishFollowUpError, type PrPublishFollowUpOutcome, type PrPublishHandoff, type PrPublishResult, type PrReadyArgs, type PrReadyProof, type PreviewDisposition, type PreviewLifecyclePlan, type PreviewTargetPlan, type PublishEpicStructureArgs, type PublishEpicStructureResult, type PublishFollowUpPlan, REVIEW_FOCUS_SECTION, type ReviewGateNotRequiredProof, type ReviewGateTraceIdentity, type ReviewLadderCycle, type ReviewLadderEvaluation, type ReviewLadderPolicy, type ReviewLadderStageEvent, type ReviewLadderTraceIdentity, type ReviewPromptSection, type ReviewPromptSectionProvenance, type ScanFactoryTraceDiagnosticsOptions, type ScanFactoryTraceOptions, type ScanFactoryTraceResult, type ScopedOutCommand, type TraceMirrorDiagnostic, type TraceSink, type TraceWriteResult, type TraceWriteSinks, type VerificationBatteryPlan, type VerificationReuse, type VerificationRunContext, type VerificationRunContextInput, type WaivedDemand, WorkerCheckoutGuardError, type WorkerCloseoutLessonsTraceEvent, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_d_exports as boundaryManifest, boundary_review_proof_d_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_d_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, createVerificationRunContext, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_d_exports as prReadinessEvaluation, external_evidence_d_exports as prReadinessExternalEvidence, pr_body_renderer_d_exports as prReadinessPrBodyRenderer, proof_identity_d_exports as prReadinessProofIdentity, review_proof_d_exports as prReadinessReviewProof, status_check_rollup_d_exports as prReadinessStatusChecks, verification_proof_d_exports as prReadinessVerificationProof, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_d_exports as worktreeScratchFiles };
package/dist/index.js CHANGED
@@ -4,7 +4,7 @@ import { appendFileSync, constants, cpSync, existsSync, mkdirSync, mkdtempSync,
4
4
  import { pathToFileURL } from "node:url";
5
5
  import { Command, InvalidArgumentError } from "commander";
6
6
  import path from "node:path";
7
- import crypto, { createHash, randomUUID } from "node:crypto";
7
+ import crypto, { createHash, randomBytes, randomUUID } from "node:crypto";
8
8
  import { z } from "zod";
9
9
  import { execFileSync, spawnSync } from "node:child_process";
10
10
  import { link, lstat, mkdir, open, readFile, readdir, realpath, rename, stat, unlink } from "node:fs/promises";
@@ -17,7 +17,7 @@ import { parse } from "yaml";
17
17
  import { FACTORY_PROOF_GATE_STEP_NAME, GitHubApiError, mintInstallationToken, productionImpactTargetOutput, readVitestProfileDocument } from "@patronage/factory-ci";
18
18
  import { promisify } from "node:util";
19
19
  //#region package.json
20
- var version = "1.0.0-alpha.16";
20
+ var version = "1.0.0-alpha.17";
21
21
  //#endregion
22
22
  //#region src/review-rungs.ts
23
23
  const EVIDENCE_REVIEW_RUNGS$1 = [
@@ -1178,9 +1178,10 @@ const hqIngestConfigSchema = z.object({
1178
1178
  }
1179
1179
  }, "Must be an HTTPS origin without a path, query, fragment, or embedded credentials").describe("Repository-declared HQ origin (scheme and host only; the factory composes the ingest path). Credential delivery additionally requires exact-origin authorization in the operator user config hqAllowedOrigins list.")
1180
1180
  }).strict();
1181
+ const profileRequiredCheckScopeSchema = requiredCheckScopeSchema$1.extend({ classifications: z.array(z.enum(["docs/process-only", "non-trivial"])).min(1) }).strict();
1181
1182
  const requiredCheckSchema = z.object({
1182
1183
  name: z.string().min(1),
1183
- scope: requiredCheckScopeSchema$1.optional()
1184
+ scope: profileRequiredCheckScopeSchema.optional()
1184
1185
  }).strict();
1185
1186
  const impactTargetSchema = z.object({
1186
1187
  importers: z.array(z.string().min(1)).min(1),
@@ -1212,15 +1213,6 @@ const factoryProjectProfileSchema = z.object({
1212
1213
  code: "custom",
1213
1214
  message: "requiredChecks entries must have distinct names."
1214
1215
  });
1215
- for (const [index, check] of checks.entries()) if (check.scope?.classifications.includes("trivial")) context.addIssue({
1216
- code: "custom",
1217
- message: `requiredChecks entry "${check.name}" scopes on classification "trivial", which the classifier no longer produces (#742); scope on "docs/process-only" or "non-trivial".`,
1218
- path: [
1219
- index,
1220
- "scope",
1221
- "classifications"
1222
- ]
1223
- });
1224
1216
  }).optional(),
1225
1217
  review: z.object({ conditional: z.array(conditionalReviewSchema).min(1) }).strict().optional(),
1226
1218
  schemaVersion: z.literal(6),
@@ -3797,6 +3789,7 @@ const impactStampTargetSchema = z.object({
3797
3789
  */
3798
3790
  const impactStampSchema = z.object({
3799
3791
  basis: z.enum(["target-scoped", "conservative"]),
3792
+ inertPaths: z.array(z.string()).default([]),
3800
3793
  reasons: z.array(z.string()),
3801
3794
  stampVersion: z.literal(3),
3802
3795
  targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
@@ -3813,6 +3806,11 @@ const unsubscribedPathsLine = (paths, reason = "legacy or malformed stamp") => {
3813
3806
  if (!Array.isArray(paths) || paths.some((one) => typeof one !== "string")) return `Unsubscribed changed paths: unavailable (${reason}).`;
3814
3807
  return paths.length === 0 ? "Unsubscribed changed paths: none." : `Unsubscribed changed paths (demand no target): ${paths.join(", ")}.`;
3815
3808
  };
3809
+ /** The one inert-owned path line (#761): candidate-impact console and summary. */
3810
+ const inertPathsLine = (paths, reason = "legacy or malformed stamp") => {
3811
+ if (!Array.isArray(paths) || paths.some((one) => typeof one !== "string")) return `Inert-owned changed paths: unavailable (${reason}).`;
3812
+ return paths.length === 0 ? "Inert-owned changed paths: none." : `Inert-owned changed paths: ${paths.join(", ")}.`;
3813
+ };
3816
3814
  /** Human-readable lines for every impact decision, including quiet releases. */
3817
3815
  const impactStampSummaryLines = (stamp) => [`Impact stamp: ${stamp.basis}; ${stamp.targets.filter(({ impact }) => impact === "affected").length}/${stamp.targets.length} target(s) affected.`, unsubscribedPathsLine(stamp.unsubscribedPaths)];
3818
3816
  /**
@@ -3907,8 +3905,9 @@ const impactStampDemandRelease = ({ checkName, stamp }) => {
3907
3905
  * every doubt path lands on, and the stamp a producer must fall back to if
3908
3906
  * stamp computation itself fails for any reason.
3909
3907
  */
3910
- const conservativeImpactStamp = (targets, reasons, unsubscribedPaths = []) => ({
3908
+ const conservativeImpactStamp = (targets, reasons, unsubscribedPaths = [], inertPaths = []) => ({
3911
3909
  basis: "conservative",
3910
+ inertPaths,
3912
3911
  reasons,
3913
3912
  stampVersion: 3,
3914
3913
  targets: targets.map((target) => ({
@@ -4048,9 +4047,13 @@ const targetImpactForLockfileDelta = (target, delta) => {
4048
4047
  * as affected/not-affected with its exact basis. `not-affected` is only ever
4049
4048
  * returned only when every changed input is a valid source path or an
4050
4049
  * analyzable pnpm-lock.yaml (v9) delta. Source and lockfile reachability are
4051
- * unioned per target. A valid source path matched by no target is recorded in
4052
- * `unsubscribedPaths` and affects none. Invalid, unreadable, unsupported, or
4053
- * otherwise doubtful input widens the whole stamp to full impact.
4050
+ * unioned per target. A valid source path matched by a target subscription is
4051
+ * product-owned and appears in neither path list. A valid source path matching
4052
+ * `impact.inert` and no target is recorded in `inertPaths`. A valid source
4053
+ * path with no declared owner is recorded in `unsubscribedPaths`.
4054
+ * `pnpm-lock.yaml` is a graph input and is never inert. Invalid, unreadable,
4055
+ * unsupported, or otherwise doubtful input widens the whole stamp to full
4056
+ * impact.
4054
4057
  *
4055
4058
  * Total by contract: this function never throws. Any unexpected failure in
4056
4059
  * reading, parsing, or traversal is itself a doubt path and returns the
@@ -4116,7 +4119,27 @@ const assertDeclaredImporterRoots = (targets, sides) => {
4116
4119
  const assertDeclaredPathGlobs = (targets) => {
4117
4120
  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`);
4118
4121
  };
4119
- const analyzeSourcePaths = (sourceFiles, targets, profilePath) => {
4122
+ const assertDeclaredInertGlobs = (inert) => {
4123
+ for (const pattern of inert) if (!isValidRepoRelativeGlob(pattern)) throw new ImpactStampConfigError(`impact.inert declares invalid repo-relative path glob "${pattern}"; fix the profile's impact.inert declaration`);
4124
+ };
4125
+ const matchesTargetSubscription = (targets, file) => targets.some((target) => matchesAnyGlob(target.paths ?? [], file));
4126
+ const partitionUnownedSourcePaths = (sourceFiles, targets, inertGlobs) => {
4127
+ const inertPaths = [];
4128
+ const unsubscribedPaths = [];
4129
+ for (const file of sourceFiles) {
4130
+ if (file === "pnpm-lock.yaml" || matchesTargetSubscription(targets, file)) continue;
4131
+ if (matchesAnyGlob(inertGlobs, file)) {
4132
+ inertPaths.push(file);
4133
+ continue;
4134
+ }
4135
+ unsubscribedPaths.push(file);
4136
+ }
4137
+ return {
4138
+ inertPaths,
4139
+ unsubscribedPaths
4140
+ };
4141
+ };
4142
+ const analyzeSourcePaths = (sourceFiles, targets, profilePath, inertGlobs) => {
4120
4143
  for (const file of sourceFiles) {
4121
4144
  if (!isRepoRelativePath(file)) return {
4122
4145
  kind: "conservative",
@@ -4129,6 +4152,7 @@ const analyzeSourcePaths = (sourceFiles, targets, profilePath) => {
4129
4152
  }
4130
4153
  return {
4131
4154
  kind: "ok",
4155
+ ...partitionUnownedSourcePaths(sourceFiles, targets, inertGlobs),
4132
4156
  targets: targets.map((target) => {
4133
4157
  const matched = sourceFiles.find((file) => matchesAnyGlob(target.paths ?? [], file));
4134
4158
  return matched === void 0 ? {
@@ -4140,8 +4164,7 @@ const analyzeSourcePaths = (sourceFiles, targets, profilePath) => {
4140
4164
  impact: "affected",
4141
4165
  name: target.name
4142
4166
  };
4143
- }),
4144
- unsubscribedPaths: sourceFiles.filter((file) => !targets.some((target) => matchesAnyGlob(target.paths ?? [], file)))
4167
+ })
4145
4168
  };
4146
4169
  };
4147
4170
  const unionTargetImpact = (source, lockfile) => {
@@ -4153,34 +4176,41 @@ const unionTargetImpact = (source, lockfile) => {
4153
4176
  name: source.name
4154
4177
  };
4155
4178
  };
4156
- const computeImpactStampOrThrow = ({ changedFiles, profilePath = "software-factory.profile.json", readLockfile }, targets) => {
4157
- if (targets.length === 0) return {
4158
- basis: "conservative",
4159
- reasons: ["no impact targets declared in the profile; every surface keeps full demand"],
4160
- stampVersion: 3,
4161
- targets: [],
4162
- unsubscribedPaths: changedFiles.filter((file) => file !== "pnpm-lock.yaml" && isRepoRelativePath(file))
4163
- };
4179
+ const computeImpactStampOrThrow = ({ changedFiles, profile, profilePath = "software-factory.profile.json", readLockfile }, targets) => {
4180
+ const inertGlobs = profile.impact?.inert ?? [];
4181
+ assertDeclaredInertGlobs(inertGlobs);
4182
+ if (targets.length === 0) {
4183
+ const { inertPaths, unsubscribedPaths } = partitionUnownedSourcePaths(changedFiles.filter((file) => file !== "pnpm-lock.yaml" && isRepoRelativePath(file)), [], inertGlobs);
4184
+ return {
4185
+ basis: "conservative",
4186
+ inertPaths,
4187
+ reasons: ["no impact targets declared in the profile; every surface keeps full demand"],
4188
+ stampVersion: 3,
4189
+ targets: [],
4190
+ unsubscribedPaths
4191
+ };
4192
+ }
4164
4193
  assertDeclaredPathGlobs(targets);
4165
4194
  const sides = readLockfileSides(readLockfile);
4166
4195
  if (sides.kind === "ok") assertDeclaredImporterRoots(targets, sides.parsed);
4167
4196
  if (changedFiles.length === 0) return conservativeImpactStamp(targets, ["no changed files detected; full impact assumed"]);
4168
4197
  const sourceFiles = changedFiles.filter((file) => file !== LOCKFILE_PATH);
4169
- const source = analyzeSourcePaths(sourceFiles, targets, profilePath);
4198
+ const source = analyzeSourcePaths(sourceFiles, targets, profilePath, inertGlobs);
4170
4199
  if (source.kind === "conservative") return conservativeImpactStamp(targets, [source.reason]);
4171
- if (sides.kind === "threw") return conservativeImpactStamp(targets, [`impact stamp computation failed (${sides.error instanceof Error ? sides.error.message : String(sides.error)}); fail closed to full impact`], source.unsubscribedPaths);
4172
- if (sides.kind === "unreadable") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is unreadable at the ${sides.side} side; fail closed to full impact`], source.unsubscribedPaths);
4173
- if (sides.kind === "unparseable") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is unparseable or structurally malformed at the ${sides.side} side; fail closed to full impact`], source.unsubscribedPaths);
4174
- if (sides.kind === "unsupported-version") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is not lockfileVersion ${SUPPORTED_LOCKFILE_VERSION}; only pnpm v9 lockfiles are modelled; fail closed to full impact`], source.unsubscribedPaths);
4200
+ if (sides.kind === "threw") return conservativeImpactStamp(targets, [`impact stamp computation failed (${sides.error instanceof Error ? sides.error.message : String(sides.error)}); fail closed to full impact`], source.unsubscribedPaths, source.inertPaths);
4201
+ if (sides.kind === "unreadable") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is unreadable at the ${sides.side} side; fail closed to full impact`], source.unsubscribedPaths, source.inertPaths);
4202
+ if (sides.kind === "unparseable") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is unparseable or structurally malformed at the ${sides.side} side; fail closed to full impact`], source.unsubscribedPaths, source.inertPaths);
4203
+ if (sides.kind === "unsupported-version") return conservativeImpactStamp(targets, [`pnpm-lock.yaml is not lockfileVersion ${SUPPORTED_LOCKFILE_VERSION}; only pnpm v9 lockfiles are modelled; fail closed to full impact`], source.unsubscribedPaths, source.inertPaths);
4175
4204
  if (!changedFiles.includes("pnpm-lock.yaml")) return {
4176
4205
  basis: "target-scoped",
4177
- reasons: [source.unsubscribedPaths.length === 0 ? "all changed source paths match at least one declared target subscription; impact scoped by repo-relative path globs" : `${source.unsubscribedPaths.length} changed source path(s) match no declared target subscription and affect no target`],
4206
+ inertPaths: source.inertPaths,
4207
+ reasons: [source.unsubscribedPaths.length === 0 ? "all changed source paths have a declared owner (target subscription or impact.inert); impact scoped by repo-relative path globs" : `${source.unsubscribedPaths.length} changed source path(s) match no declared target subscription and affect no target`],
4178
4208
  stampVersion: 3,
4179
4209
  targets: source.targets,
4180
4210
  unsubscribedPaths: source.unsubscribedPaths
4181
4211
  };
4182
4212
  const [baseLock, headLock] = sides.parsed;
4183
- if (stableStringify$1(nonGraphSections(baseLock)) !== stableStringify$1(nonGraphSections(headLock))) return conservativeImpactStamp(targets, ["pnpm-lock.yaml delta touches sections outside importers/packages/snapshots (e.g. settings, overrides, patchedDependencies); fail closed to full impact"], source.unsubscribedPaths);
4213
+ if (stableStringify$1(nonGraphSections(baseLock)) !== stableStringify$1(nonGraphSections(headLock))) return conservativeImpactStamp(targets, ["pnpm-lock.yaml delta touches sections outside importers/packages/snapshots (e.g. settings, overrides, patchedDependencies); fail closed to full impact"], source.unsubscribedPaths, source.inertPaths);
4184
4214
  const delta = {
4185
4215
  changedImporters: changedSectionKeys(baseLock.importers, headLock.importers),
4186
4216
  changedPackages: changedSectionKeys(baseLock.packages, headLock.packages),
@@ -4191,7 +4221,8 @@ const computeImpactStampOrThrow = ({ changedFiles, profilePath = "software-facto
4191
4221
  const mixed = sourceFiles.length > 0;
4192
4222
  return {
4193
4223
  basis: "target-scoped",
4194
- reasons: mixed ? [source.unsubscribedPaths.length === 0 ? "all changed source paths match at least one declared target subscription and pnpm-lock.yaml is analyzable; impact is the union of path subscriptions and importer/snapshot graph analysis" : `${source.unsubscribedPaths.length} changed source path(s) match no declared target subscription; subscribed path impact is unioned with importer/snapshot graph analysis`] : ["pnpm-lock.yaml is the only changed file; impact scoped by importer/snapshot graph analysis"],
4224
+ inertPaths: source.inertPaths,
4225
+ reasons: mixed ? [source.unsubscribedPaths.length === 0 ? "all changed source paths have a declared owner (target subscription or impact.inert) and pnpm-lock.yaml is analyzable; impact is the union of path subscriptions and importer/snapshot graph analysis" : `${source.unsubscribedPaths.length} changed source path(s) match no declared target subscription; subscribed path impact is unioned with importer/snapshot graph analysis`] : ["pnpm-lock.yaml is the only changed file; impact scoped by importer/snapshot graph analysis"],
4195
4226
  stampVersion: 3,
4196
4227
  targets: mixed ? source.targets.map((sourceTarget, index) => unionTargetImpact(sourceTarget, lockfileTargets[index])) : lockfileTargets,
4197
4228
  unsubscribedPaths: source.unsubscribedPaths
@@ -4455,6 +4486,16 @@ const assertPrVerifyProofRules = (proof, context) => {
4455
4486
  message: "notRequiredCommands is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
4456
4487
  path: ["notRequiredCommands"]
4457
4488
  });
4489
+ if (proof.schemaVersion < 4 && proof.notDemandedRecords !== void 0) context.addIssue({
4490
+ code: "custom",
4491
+ message: "notDemandedRecords is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
4492
+ path: ["notDemandedRecords"]
4493
+ });
4494
+ if (proof.notDemandedRecords !== void 0 && proof.impactStamp === void 0) context.addIssue({
4495
+ code: "custom",
4496
+ message: "notDemandedRecords requires impactStamp; package-level not-demanded records are bound to the stamp identity.",
4497
+ path: ["notDemandedRecords"]
4498
+ });
4458
4499
  assertBatteryCompleteness(proof, context);
4459
4500
  assertNotRequiredStampAuthorization(proof, context);
4460
4501
  };
@@ -4487,6 +4528,10 @@ const notRequiredCommandSchema = z.object({
4487
4528
  impactTarget: z.string().min(1),
4488
4529
  name: z.string().min(1)
4489
4530
  });
4531
+ const notDemandedRecordSchema = z.object({
4532
+ basis: z.string().min(1),
4533
+ name: z.string().min(1)
4534
+ });
4490
4535
  const prVerifySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.join(", ")}` });
4491
4536
  const prVerifyProofSchema = z.object({
4492
4537
  authoringSession: z.string().trim().min(1).optional(),
@@ -4518,6 +4563,7 @@ const prVerifyProofSchema = z.object({
4518
4563
  "trivial",
4519
4564
  "full"
4520
4565
  ]),
4566
+ notDemandedRecords: z.array(notDemandedRecordSchema).min(1).optional(),
4521
4567
  notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
4522
4568
  outcome: z.enum(["aborted", "passed"]).optional(),
4523
4569
  patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
@@ -6004,6 +6050,84 @@ function createBoundaryCheckCommand(output, action) {
6004
6050
  if (record.status !== "ready") throw new Error(`boundary:check refused: ${record.blockingReasons.join("; ")}`);
6005
6051
  })));
6006
6052
  }
6053
+ //#endregion
6054
+ //#region src/verification-run-context.ts
6055
+ const FACTORY_BASE_REF_ENV = "FACTORY_BASE_REF";
6056
+ const FACTORY_CHANGED_FILES_ENV = "FACTORY_CHANGED_FILES";
6057
+ const FACTORY_CHANGED_FILES_FILE_ENV = "FACTORY_CHANGED_FILES_FILE";
6058
+ const DEFAULT_CHANGED_FILES_INLINE_LIMIT_BYTES = 1e5;
6059
+ const EMPTY_TREE_OBJECT_HASH = "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
6060
+ const DEFAULT_ROOT_SHARED_GLOBS = [
6061
+ "pnpm-lock.yaml",
6062
+ "package-lock.json",
6063
+ "yarn.lock",
6064
+ "bun.lockb",
6065
+ "pnpm-workspace.yaml",
6066
+ "package.json",
6067
+ "tsconfig*.json",
6068
+ "*.config.*",
6069
+ ".npmrc",
6070
+ ".nvmrc"
6071
+ ];
6072
+ function fullSuiteReasonForFile(file) {
6073
+ return `${file}: root/shared change forces full suite`;
6074
+ }
6075
+ function noopCleanup() {}
6076
+ function createVerificationRunContext(input) {
6077
+ const rootSharedGlobs = input.rootSharedGlobs ?? DEFAULT_ROOT_SHARED_GLOBS;
6078
+ const fullSuiteBaseRef = input.fullSuiteBaseRef ?? "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
6079
+ const inlineLimitBytes = input.inlineLimitBytes ?? DEFAULT_CHANGED_FILES_INLINE_LIMIT_BYTES;
6080
+ const tmpDir = input.tmpDir ?? os.tmpdir();
6081
+ const matchingFiles = input.changedFiles.filter((file) => matchesAnyGlob(rootSharedGlobs, file));
6082
+ const forcesFullSuite = matchingFiles.length > 0 || input.changedFiles.length === 0;
6083
+ const fullSuiteReasons = input.changedFiles.length === 0 ? ["no changed files detected; forcing full suite"] : matchingFiles.map(fullSuiteReasonForFile);
6084
+ return {
6085
+ baseRef: input.baseRef,
6086
+ buildEnv: () => {
6087
+ const payload = input.changedFiles.join("\n");
6088
+ const env = {
6089
+ [FACTORY_BASE_REF_ENV]: forcesFullSuite ? fullSuiteBaseRef : input.baseRef,
6090
+ [FACTORY_CHANGED_FILES_ENV]: "",
6091
+ [FACTORY_CHANGED_FILES_FILE_ENV]: ""
6092
+ };
6093
+ if (Buffer.byteLength(payload, "utf-8") > inlineLimitBytes) {
6094
+ const dir = mkdtempSync(path.join(tmpDir, "factory-changed-files-"));
6095
+ const filePath = path.join(dir, "changed-files.txt");
6096
+ writeFileSync(filePath, `${payload}\n`, "utf-8");
6097
+ env[FACTORY_CHANGED_FILES_FILE_ENV] = filePath;
6098
+ return {
6099
+ cleanup: () => {
6100
+ rmSync(dir, {
6101
+ force: true,
6102
+ recursive: true
6103
+ });
6104
+ },
6105
+ env
6106
+ };
6107
+ }
6108
+ env[FACTORY_CHANGED_FILES_ENV] = payload;
6109
+ return {
6110
+ cleanup: noopCleanup,
6111
+ env
6112
+ };
6113
+ },
6114
+ changedFiles: input.changedFiles,
6115
+ forcesFullSuite,
6116
+ fullSuiteReasons,
6117
+ runSummaryLines: () => {
6118
+ const lines = [];
6119
+ if (input.changedFiles.length > 0) {
6120
+ lines.push("Changed files considered by pr:verify:");
6121
+ for (const file of input.changedFiles) lines.push(`- ${file}`);
6122
+ } else lines.push("No changed files detected.");
6123
+ if (forcesFullSuite) {
6124
+ lines.push("Forcing full verification suite:");
6125
+ for (const reason of fullSuiteReasons) lines.push(`- ${reason}`);
6126
+ }
6127
+ return lines;
6128
+ }
6129
+ };
6130
+ }
6007
6131
  const COMMIT_SHA_PATTERN$1 = /^[0-9a-f]{40}$/iu;
6008
6132
  const ZERO_SHA_PATTERN$1 = /^0{40}$/u;
6009
6133
  const demandedTargets$1 = (profile, reason) => (profile?.impact?.targets ?? []).map(({ name }) => ({
@@ -6020,6 +6144,7 @@ const refusal$1 = ({ base, head, profile, reason }) => ({
6020
6144
  },
6021
6145
  decision: "refused",
6022
6146
  ...head ? { headSha: head } : {},
6147
+ inertPaths: [],
6023
6148
  schemaVersion: 1,
6024
6149
  targets: demandedTargets$1(profile, reason),
6025
6150
  unsubscribedPaths: []
@@ -6056,13 +6181,23 @@ const assertReachableCommit$1 = (cwd, sha, name) => {
6056
6181
  throw new Error(`${name} commit ${sha} is not reachable in this checkout`);
6057
6182
  }
6058
6183
  };
6184
+ const githubOutputAssignment = (name, value) => {
6185
+ if (value.includes("\n") || value.includes("\r")) {
6186
+ const delimiter = `ghadelim_${randomBytes(8).toString("hex")}`;
6187
+ return `${name}<<${delimiter}\n${value}\n${delimiter}`;
6188
+ }
6189
+ return `${name}=${value}`;
6190
+ };
6059
6191
  const writeGithubOutputs$1 = (outputPath, decision) => {
6060
- appendFileSync(outputPath, `${[
6192
+ const lines = [
6061
6193
  `decision=${decision.decision}`,
6062
6194
  `basis=${JSON.stringify(decision.basis)}`,
6195
+ `inert_paths=${JSON.stringify(decision.inertPaths)}`,
6063
6196
  `unsubscribed_paths=${JSON.stringify(decision.unsubscribedPaths)}`,
6064
6197
  ...decision.targets.map((target) => `${productionImpactTargetOutput(target.name)}=${target.state}`)
6065
- ].join("\n")}\n`, "utf-8");
6198
+ ];
6199
+ if (decision.runContext) lines.push(githubOutputAssignment("factory_base_ref", decision.runContext.FACTORY_BASE_REF), githubOutputAssignment("factory_changed_files", decision.runContext.FACTORY_CHANGED_FILES), githubOutputAssignment("factory_changed_files_file", decision.runContext.FACTORY_CHANGED_FILES_FILE), githubOutputAssignment("merge_base_sha", decision.runContext.mergeBaseSha ?? ""));
6200
+ appendFileSync(outputPath, `${lines.join("\n")}\n`, "utf-8");
6066
6201
  };
6067
6202
  /**
6068
6203
  * Classify candidate impact from one exact base/head identity. The delta is
@@ -6076,6 +6211,7 @@ const runCandidateImpact = (args) => {
6076
6211
  const cwd = path.resolve(args.cwd ?? process.cwd());
6077
6212
  let profile;
6078
6213
  let profilePath;
6214
+ let relativeProfile;
6079
6215
  let decision;
6080
6216
  try {
6081
6217
  const { path: loadedPath, profile: loadedProfile } = loadProjectProfile({
@@ -6084,6 +6220,7 @@ const runCandidateImpact = (args) => {
6084
6220
  });
6085
6221
  profile = loadedProfile;
6086
6222
  profilePath = loadedPath;
6223
+ relativeProfile = path.relative(cwd, profilePath).split(path.sep).join("/");
6087
6224
  const base = assertCommitIdentity$1(args.base, "base");
6088
6225
  const head = assertCommitIdentity$1(args.head, "head");
6089
6226
  assertReachableCommit$1(cwd, base, "base");
@@ -6123,6 +6260,7 @@ const runCandidateImpact = (args) => {
6123
6260
  },
6124
6261
  decision: usable ? "usable" : "refused",
6125
6262
  headSha: head,
6263
+ inertPaths: stamp.inertPaths,
6126
6264
  mergeBaseSha: mergeBase,
6127
6265
  schemaVersion: 1,
6128
6266
  targets: stamp.targets.map((target) => ({
@@ -6140,6 +6278,29 @@ const runCandidateImpact = (args) => {
6140
6278
  reason: `candidate impact classification refused: ${error instanceof Error ? error.message : String(error)}`
6141
6279
  });
6142
6280
  }
6281
+ if (args.emitRunContext) {
6282
+ const fullSuiteBaseRef = emptyTreeHash(cwd);
6283
+ const context = createVerificationRunContext({
6284
+ baseRef: decision.mergeBaseSha ?? fullSuiteBaseRef,
6285
+ changedFiles: decision.basis.changedFiles,
6286
+ fullSuiteBaseRef,
6287
+ rootSharedGlobs: relativeProfile ? [...DEFAULT_ROOT_SHARED_GLOBS, relativeProfile] : [...DEFAULT_ROOT_SHARED_GLOBS]
6288
+ });
6289
+ const { env } = context.buildEnv();
6290
+ decision = {
6291
+ ...decision,
6292
+ runContext: {
6293
+ FACTORY_BASE_REF: env["FACTORY_BASE_REF"] ?? "",
6294
+ FACTORY_CHANGED_FILES: env["FACTORY_CHANGED_FILES"] ?? "",
6295
+ FACTORY_CHANGED_FILES_FILE: env["FACTORY_CHANGED_FILES_FILE"] ?? "",
6296
+ baseRef: context.baseRef,
6297
+ changedFiles: context.changedFiles,
6298
+ forcesFullSuite: context.forcesFullSuite,
6299
+ fullSuiteReasons: context.fullSuiteReasons,
6300
+ ...decision.mergeBaseSha ? { mergeBaseSha: decision.mergeBaseSha } : {}
6301
+ }
6302
+ };
6303
+ }
6143
6304
  if (args.githubOutput) writeGithubOutputs$1(path.resolve(cwd, args.githubOutput), decision);
6144
6305
  if (args.githubSummary) appendFileSync(path.resolve(cwd, args.githubSummary), `### Candidate impact\n\n${candidateImpactSummaryLines(decision).join("\n")}\n`, "utf-8");
6145
6306
  return decision;
@@ -6147,22 +6308,36 @@ const runCandidateImpact = (args) => {
6147
6308
  const candidateImpactSummaryLines = (decision) => decision.basis.classification === "refused" ? [
6148
6309
  "Candidate impact: refused; all routed work remains demanded.",
6149
6310
  ...decision.basis.reasons,
6311
+ inertPathsLine(void 0, "classification was refused"),
6150
6312
  unsubscribedPathsLine(void 0, "classification was refused")
6151
- ] : [`Candidate impact: ${decision.basis.classification}; ${decision.targets.filter(({ state }) => state === "demanded").length}/${decision.targets.length} target(s) demanded.`, unsubscribedPathsLine(decision.unsubscribedPaths)];
6313
+ ] : [
6314
+ `Candidate impact: ${decision.basis.classification}; ${decision.targets.filter(({ state }) => state === "demanded").length}/${decision.targets.length} target(s) demanded.`,
6315
+ inertPathsLine(decision.inertPaths),
6316
+ unsubscribedPathsLine(decision.unsubscribedPaths)
6317
+ ];
6152
6318
  //#endregion
6153
6319
  //#region src/commands/candidate-impact.ts
6154
6320
  function createCandidateImpactCommand(output) {
6155
- return markCwdOptionDefault(new Command("candidate:impact").description("Classify candidate targets from an exact pull-request base/head identity").option("--base <sha>", "candidate base commit SHA").option("--head <sha>", "candidate head commit SHA").option("--cwd <path>", "repository working directory", collectCwdOption).option("--profile <path>", "path to the project profile JSON file").option("--github-output <path>", "append stable decision outputs to a GitHub Actions output file").option("--github-summary <path>", "append a human-readable decision to a GitHub Actions summary file").option("--json", "print the complete decision as JSON").action((options) => {
6321
+ return markCwdOptionDefault(new Command("candidate:impact").description("Classify candidate targets from an exact pull-request base/head identity").option("--base <sha>", "candidate base commit SHA").option("--head <sha>", "candidate head commit SHA").option("--cwd <path>", "repository working directory", collectCwdOption).option("--profile <path>", "path to the project profile JSON file").option("--github-output <path>", "append stable decision outputs to a GitHub Actions output file").option("--github-summary <path>", "append a human-readable decision to a GitHub Actions summary file").option("--json", "print the complete decision as JSON").option("--emit-run-context", "emit the FACTORY_BASE_REF / FACTORY_CHANGED_FILES run-context contract").action((options) => {
6156
6322
  const decision = runCandidateImpact({
6157
6323
  base: options.base,
6158
6324
  cwd: resolveCwdOption(options.cwd),
6325
+ emitRunContext: options.emitRunContext,
6159
6326
  githubOutput: options.githubOutput,
6160
6327
  githubSummary: options.githubSummary,
6161
6328
  head: options.head,
6162
6329
  profilePath: options.profile
6163
6330
  });
6164
6331
  if (options.json) output.stdout.write(`${JSON.stringify(decision, null, 2)}\n`);
6165
- else output.stdout.write(`${candidateImpactSummaryLines(decision).join("\n")}\n`);
6332
+ else if (options.emitRunContext) {
6333
+ const context = decision.runContext;
6334
+ if (context === void 0) throw new Error("--emit-run-context produced no run context; refusing to print an empty contract");
6335
+ output.stdout.write(`${[
6336
+ `${FACTORY_BASE_REF_ENV}=${context.FACTORY_BASE_REF}`,
6337
+ `${FACTORY_CHANGED_FILES_ENV}=${context.FACTORY_CHANGED_FILES}`,
6338
+ `${FACTORY_CHANGED_FILES_FILE_ENV}=${context.FACTORY_CHANGED_FILES_FILE}`
6339
+ ].join("\n")}\n`);
6340
+ } else output.stdout.write(`${candidateImpactSummaryLines(decision).join("\n")}\n`);
6166
6341
  }));
6167
6342
  }
6168
6343
  /** `tool` discriminator every emitted report carries. */
@@ -9573,25 +9748,49 @@ function createFactoryCheckPublisher(output, dependencies = {}) {
9573
9748
  }
9574
9749
  //#endregion
9575
9750
  //#region src/pr-classification.ts
9576
- function ownershipForFile(profile, file) {
9577
- if (file === "pnpm-lock.yaml") return { kind: "graph-input" };
9578
- const subscribed = (profile.impact?.targets ?? []).find((target) => matchesAnyGlob(target.paths ?? [], file));
9579
- if (subscribed) return {
9751
+ const MISSING_STAMP_REASON = "impact stamp is missing; fail closed to non-trivial";
9752
+ function ownershipFromStamp(stamp, file) {
9753
+ if (stamp.inertPaths.includes(file)) return { kind: "inert" };
9754
+ if (stamp.unsubscribedPaths.includes(file)) return { kind: "unowned" };
9755
+ const named = stamp.targets.find((target) => target.impact === "affected" && target.basis.includes(`"${file}"`));
9756
+ if (named) return {
9757
+ kind: "target",
9758
+ target: named.name
9759
+ };
9760
+ const affected = stamp.targets.find((target) => target.impact === "affected");
9761
+ if (affected) return {
9580
9762
  kind: "target",
9581
- target: subscribed.name
9763
+ target: affected.name
9582
9764
  };
9583
- if (matchesAnyGlob(profile.impact?.inert ?? [], file)) return { kind: "inert" };
9584
9765
  return { kind: "unowned" };
9585
9766
  }
9586
9767
  function reasonForOwnership(file, ownership) {
9587
9768
  switch (ownership.kind) {
9588
- case "graph-input": return `${file}: dependency graph input; never inert`;
9589
- case "inert": return `${file}: repository-inert ownership (impact.inert)`;
9769
+ case "inert": return `${file}: repository-inert ownership (stamp.inertPaths)`;
9590
9770
  case "target": return `${file}: affects product target "${ownership.target}"`;
9591
9771
  default: return `${file}: no declared owner; fail closed to non-trivial`;
9592
9772
  }
9593
9773
  }
9594
- function classifyDiff(profile, files) {
9774
+ function relativeProfilePath(options) {
9775
+ return path.relative(options.cwd, options.profilePath).split(path.sep).join("/");
9776
+ }
9777
+ function applyProfileEditGuard(files, relativeProfile, profilePath, base) {
9778
+ if (!files.some((file) => file === relativeProfile || file === profilePath)) return base;
9779
+ return {
9780
+ classification: "non-trivial",
9781
+ reasons: [...base.reasons, `${relativeProfile}: edits the ownership declarations; forced non-trivial`]
9782
+ };
9783
+ }
9784
+ /**
9785
+ * Path ownership as the stamp recorded it. A missing stamp fails closed.
9786
+ * Does not glob, and does not refuse the docs rung on a conservative basis —
9787
+ * that gate belongs to {@link classifyDiffForRunWithStamp}.
9788
+ */
9789
+ function classifyDiff(files, stamp) {
9790
+ if (stamp === void 0) return {
9791
+ classification: "non-trivial",
9792
+ reasons: [MISSING_STAMP_REASON]
9793
+ };
9595
9794
  const sortedFiles = [...files].toSorted();
9596
9795
  if (sortedFiles.length === 0) return {
9597
9796
  classification: "non-trivial",
@@ -9599,7 +9798,7 @@ function classifyDiff(profile, files) {
9599
9798
  };
9600
9799
  const ownerships = sortedFiles.map((file) => ({
9601
9800
  file,
9602
- ownership: ownershipForFile(profile, file)
9801
+ ownership: ownershipFromStamp(stamp, file)
9603
9802
  }));
9604
9803
  const reasons = ownerships.map(({ file, ownership }) => reasonForOwnership(file, ownership));
9605
9804
  if (ownerships.every(({ ownership }) => ownership.kind === "inert")) return {
@@ -9620,14 +9819,13 @@ function classifyDiff(profile, files) {
9620
9819
  * fail-closed stamp can never coexist with a reduced verification battery
9621
9820
  * (#742 review cycle 3).
9622
9821
  *
9623
- * `classifyDiffForRun` below stays the pure path predicate. Its remaining
9624
- * stamp-less consumer is pr-ready's docs-only DELTA check, where the delta
9625
- * rides on an already fully verified baseline and a lockfile-bearing delta
9626
- * already refuses through the graph-input rule.
9822
+ * Ownership and reasons come from `stamp.targets`, `stamp.inertPaths`, and
9823
+ * `stamp.unsubscribedPaths`. A missing stamp fails closed; this function
9824
+ * never evaluates profile globs.
9627
9825
  */
9628
9826
  function classifyDiffForRunWithStamp(profile, files, stamp, options) {
9629
- const base = classifyDiffForRun(profile, files, options);
9630
- if (base.classification === "non-trivial") return base;
9827
+ const base = applyProfileEditGuard(files, relativeProfilePath(options), options.profilePath, classifyDiff(files, stamp));
9828
+ if (stamp === void 0 || base.classification === "non-trivial") return base;
9631
9829
  if (stamp.basis !== "target-scoped") return {
9632
9830
  classification: "non-trivial",
9633
9831
  reasons: [...base.reasons, "impact stamp is conservative; the docs/process-only rung needs a target-scoped stamp with zero affected targets"]
@@ -9639,15 +9837,20 @@ function classifyDiffForRunWithStamp(profile, files, stamp, options) {
9639
9837
  };
9640
9838
  return base;
9641
9839
  }
9840
+ /**
9841
+ * Path predicate for consumers that do not already hold a stamp (pr-ready's
9842
+ * docs-only delta). Computes one stamp through {@link computeImpactStamp} —
9843
+ * the only glob-evaluation site — then reads ownership from it.
9844
+ */
9642
9845
  function classifyDiffForRun(profile, files, options) {
9643
- const relativeProfile = path.relative(options.cwd, options.profilePath).split(path.sep).join("/");
9644
- const base = classifyDiff(profile, files);
9645
- if (base.classification === "non-trivial") return base;
9646
- if (files.some((file) => file === relativeProfile || file === options.profilePath)) return {
9647
- classification: "non-trivial",
9648
- reasons: [...base.reasons, `${relativeProfile}: edits the ownership declarations; forced non-trivial`]
9649
- };
9650
- return base;
9846
+ const relativeProfile = relativeProfilePath(options);
9847
+ const stamp = computeImpactStamp({
9848
+ changedFiles: files,
9849
+ profile,
9850
+ profilePath: relativeProfile,
9851
+ readLockfile: () => void 0
9852
+ });
9853
+ return applyProfileEditGuard(files, relativeProfile, options.profilePath, classifyDiff(files, stamp));
9651
9854
  }
9652
9855
  //#endregion
9653
9856
  //#region src/pr-readiness/verification-proof.ts
@@ -9855,83 +10058,6 @@ const verificationProofBlockingReason = (state) => {
9855
10058
  }
9856
10059
  };
9857
10060
  //#endregion
9858
- //#region src/verification-run-context.ts
9859
- const FACTORY_BASE_REF_ENV = "FACTORY_BASE_REF";
9860
- const FACTORY_CHANGED_FILES_ENV = "FACTORY_CHANGED_FILES";
9861
- const FACTORY_CHANGED_FILES_FILE_ENV = "FACTORY_CHANGED_FILES_FILE";
9862
- const DEFAULT_CHANGED_FILES_INLINE_LIMIT_BYTES = 1e5;
9863
- const DEFAULT_ROOT_SHARED_GLOBS = [
9864
- "pnpm-lock.yaml",
9865
- "package-lock.json",
9866
- "yarn.lock",
9867
- "bun.lockb",
9868
- "pnpm-workspace.yaml",
9869
- "package.json",
9870
- "tsconfig*.json",
9871
- "*.config.*",
9872
- ".npmrc",
9873
- ".nvmrc"
9874
- ];
9875
- function fullSuiteReasonForFile(file) {
9876
- return `${file}: root/shared change forces full suite`;
9877
- }
9878
- function noopCleanup() {}
9879
- function createVerificationRunContext(input) {
9880
- const rootSharedGlobs = input.rootSharedGlobs ?? DEFAULT_ROOT_SHARED_GLOBS;
9881
- const fullSuiteBaseRef = input.fullSuiteBaseRef ?? "4b825dc642cb6eb9a060e54bf8d69288fbee4904";
9882
- const inlineLimitBytes = input.inlineLimitBytes ?? DEFAULT_CHANGED_FILES_INLINE_LIMIT_BYTES;
9883
- const tmpDir = input.tmpDir ?? os.tmpdir();
9884
- const matchingFiles = input.changedFiles.filter((file) => matchesAnyGlob(rootSharedGlobs, file));
9885
- const forcesFullSuite = matchingFiles.length > 0 || input.changedFiles.length === 0;
9886
- const fullSuiteReasons = input.changedFiles.length === 0 ? ["no changed files detected; forcing full suite"] : matchingFiles.map(fullSuiteReasonForFile);
9887
- return {
9888
- baseRef: input.baseRef,
9889
- buildEnv: () => {
9890
- const payload = input.changedFiles.join("\n");
9891
- const env = {
9892
- [FACTORY_BASE_REF_ENV]: forcesFullSuite ? fullSuiteBaseRef : input.baseRef,
9893
- [FACTORY_CHANGED_FILES_ENV]: "",
9894
- [FACTORY_CHANGED_FILES_FILE_ENV]: ""
9895
- };
9896
- if (Buffer.byteLength(payload, "utf-8") > inlineLimitBytes) {
9897
- const dir = mkdtempSync(path.join(tmpDir, "factory-changed-files-"));
9898
- const filePath = path.join(dir, "changed-files.txt");
9899
- writeFileSync(filePath, `${payload}\n`, "utf-8");
9900
- env[FACTORY_CHANGED_FILES_FILE_ENV] = filePath;
9901
- return {
9902
- cleanup: () => {
9903
- rmSync(dir, {
9904
- force: true,
9905
- recursive: true
9906
- });
9907
- },
9908
- env
9909
- };
9910
- }
9911
- env[FACTORY_CHANGED_FILES_ENV] = payload;
9912
- return {
9913
- cleanup: noopCleanup,
9914
- env
9915
- };
9916
- },
9917
- changedFiles: input.changedFiles,
9918
- forcesFullSuite,
9919
- fullSuiteReasons,
9920
- runSummaryLines: () => {
9921
- const lines = [];
9922
- if (input.changedFiles.length > 0) {
9923
- lines.push("Changed files considered by pr:verify:");
9924
- for (const file of input.changedFiles) lines.push(`- ${file}`);
9925
- } else lines.push("No changed files detected.");
9926
- if (forcesFullSuite) {
9927
- lines.push("Forcing full verification suite:");
9928
- for (const reason of fullSuiteReasons) lines.push(`- ${reason}`);
9929
- }
9930
- return lines;
9931
- }
9932
- };
9933
- }
9934
- //#endregion
9935
10061
  //#region src/workspace-install-resolution.ts
9936
10062
  const readJson = (filePath, read) => {
9937
10063
  if (!existsSync(filePath)) return null;
@@ -10178,7 +10304,6 @@ function formatVerifyTimingSummary(proof) {
10178
10304
  function resolveMode({ classification, mode }) {
10179
10305
  if (mode === "auto") {
10180
10306
  if (classification === "docs/process-only") return "docs-only";
10181
- if (classification === "trivial") return "trivial";
10182
10307
  return "full";
10183
10308
  }
10184
10309
  return mode;
@@ -12938,7 +13063,6 @@ var pr_body_renderer_exports = /* @__PURE__ */ __exportAll({
12938
13063
  });
12939
13064
  const commandFor = (proof) => {
12940
13065
  if (proof.mode === "docs-only") return "patronage-factory pr:verify --docs-only";
12941
- if (proof.mode === "trivial") return "patronage-factory pr:verify --trivial";
12942
13066
  return "patronage-factory pr:verify";
12943
13067
  };
12944
13068
  const renderPrBodySectionParts = ({ reviewProof, verifyProof }) => {
@@ -15921,17 +16045,13 @@ function createPrReviewCommand(output, action) {
15921
16045
  //#endregion
15922
16046
  //#region src/commands/pr-verify.ts
15923
16047
  function createPrVerifyCommand(output, action = runPrVerify) {
15924
- return markCwdOptionDefault(new Command("pr:verify").description("Run project-profile verification commands and write typed proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to verify", collectCwdOption).option("--docs-only", "run the docs/process verification gate").option("--trivial", "run the trivial verification gate").option("--full", "run the full verification gate").option("--authoring-session <id>", "explicit authoring session identity (required for review evidence)").option("--json", "print the proof as JSON after verification").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--no-status", "skip posting the patronage-factory/pr-verify commit status").action(withGateTiming({
16048
+ return markCwdOptionDefault(new Command("pr:verify").description("Run project-profile verification commands and write typed proof").option("--base <ref>", "base branch or ref", "origin/main").option("--cwd <path>", "working directory to verify", collectCwdOption).option("--docs-only", "run the docs/process verification gate").option("--full", "run the full verification gate").option("--authoring-session <id>", "explicit authoring session identity (required for review evidence)").option("--json", "print the proof as JSON after verification").option("--output <path>", "write proof JSON to a file").option("--profile <path>", "path to the project profile JSON file").option("--no-status", "skip posting the patronage-factory/pr-verify commit status").action(withGateTiming({
15925
16049
  gate: "pr:verify",
15926
16050
  resolveLedgerRoot: (options) => resolveCwdOption(options.cwd),
15927
16051
  stderr: output.stderr
15928
16052
  }, (options) => {
15929
16053
  assertForegroundGate({ gate: "pr:verify" });
15930
- if ([
15931
- options.docsOnly,
15932
- options.trivial,
15933
- options.full
15934
- ].filter(Boolean).length > 1) throw new Error("Use at most one of --docs-only, --trivial, or --full");
16054
+ if ([options.docsOnly, options.full].filter(Boolean).length > 1) throw new Error("Use at most one of --docs-only or --full");
15935
16055
  const dependencies = options.status === false ? {} : { publishCheckRun: createFactoryCheckPublisher(output) };
15936
16056
  const proof = action({
15937
16057
  authoringSession: options.authoringSession,
@@ -15947,7 +16067,6 @@ function createPrVerifyCommand(output, action = runPrVerify) {
15947
16067
  }
15948
16068
  function modeFor(options) {
15949
16069
  if (options.docsOnly) return "docs-only";
15950
- if (options.trivial) return "trivial";
15951
16070
  if (options.full) return "full";
15952
16071
  return "auto";
15953
16072
  }
@@ -16412,4 +16531,4 @@ if (isDirectCliExecution()) try {
16412
16531
  process.exit(1);
16413
16532
  }
16414
16533
  //#endregion
16415
- export { DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, DemandWaiveRefusalError, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, EpicStructureValidationError, FactoryCliInvocationSchema, FollowUpActionSchema, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, PrPublishFollowUpError, REVIEW_FOCUS_SECTION, WorkerCheckoutGuardError, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_exports as boundaryManifest, boundary_review_proof_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_exports as prReadinessEvaluation, external_evidence_exports as prReadinessExternalEvidence, pr_body_renderer_exports as prReadinessPrBodyRenderer, proof_identity_exports as prReadinessProofIdentity, review_proof_exports as prReadinessReviewProof, status_check_rollup_exports as prReadinessStatusChecks, verification_proof_exports as prReadinessVerificationProof, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_exports as worktreeScratchFiles };
16534
+ export { DEFAULT_DEMAND_WAIVER_PATH, DEFAULT_FACTORY_REPOSITORY, DEFAULT_ROOT_SHARED_GLOBS, DemandWaiveRefusalError, EMPTY_TREE_OBJECT_HASH, EPIC_STRUCTURE_NODE_STATUSES, EPIC_STRUCTURE_SCHEMA_VERSION, EpicStructureValidationError, FACTORY_BASE_REF_ENV, FACTORY_CHANGED_FILES_ENV, FACTORY_CHANGED_FILES_FILE_ENV, FactoryCliInvocationSchema, FollowUpActionSchema, MERGE_FREEZE_APP_SLUG, MERGE_FREEZE_CHECK_NAME, PrPublishFollowUpError, REVIEW_FOCUS_SECTION, WorkerCheckoutGuardError, appendReviewLadderStageTraceEvent, appendWithTraceSinks, applyDemandWaiver, assembleReviewPrompt, assertWorkerCheckoutAllowed, authorizeDemandWaiver, batteryScopeSummaryLines, blockingLadderFindings, boundary_manifest_exports as boundaryManifest, boundary_review_proof_exports as boundaryReviewProof, buildEpicStructureEvent, buildEpicStructurePayload, buildEvidenceEnvelope, buildReviewGateNotRequiredTraceEvent, buildReviewLadderStageTraceEvent, comment_provenance_exports as commentProvenance, createLocalJsonlTraceSink, createProgram, createVerificationRunContext, doctorProjectProfile, epicStructureEventId, evaluateReviewLadder, evidenceEnvelopeFilename, findingKey, followUpFromArgv, impactStampScopeDecision, inferFixedInThreadDispositions, isProductionHqUrl, loadProjectProfile, normalizeIssueComments, openLadderFindings, planPreviewLifecycle, planPublishFollowUp, planVerificationBattery, readiness_evaluation_exports as prReadinessEvaluation, external_evidence_exports as prReadinessExternalEvidence, pr_body_renderer_exports as prReadinessPrBodyRenderer, proof_identity_exports as prReadinessProofIdentity, review_proof_exports as prReadinessReviewProof, status_check_rollup_exports as prReadinessStatusChecks, verification_proof_exports as prReadinessVerificationProof, previewLifecycleTargets, publishEpicStructure, readDemandWaivers, readPrReadyProof, readPrReviewProof, resolveFactoryRepository, resolveFindingBlocking, resolveReviewFindingCategory, resolveReviewFindingSeverity, resolveReviewLadderPolicy, resolveTraceWriteSinks, reviewCycleStateFor, reviewFocusFromIssueBody, reviewPromptSectionSchema, reviewPromptSectionsSchema, run, runBoundaryCheck, runDemandWaive, runEvidenceEmit, runPrPublish, runPrReady, runPrReview, runPrVerify, scanFactoryTraceDiagnostics, scanFactoryTraceEvents, selectWaiversForCandidate, staleRepeatLadderFindings, toFactoryTraceEnvelope, tryAppendReviewGateNotRequiredTraceEvent, tryAppendReviewLadderStageTraceEvent, validateBoundaryCheckProof, validateDagDocument, validateDemandWaiverStore, validateFactoryTraceEvent, validateMergeFreezeState, validatePrReadyProof, validatePrReviewProof, validatePrVerifyProof, waivedDemandNotice, waivedDemandSchema, worktree_scratch_files_exports as worktreeScratchFiles };
package/dist/schemas.d.ts CHANGED
@@ -125,6 +125,7 @@ declare const impactStampSchema: z.ZodObject<{
125
125
  "target-scoped": "target-scoped";
126
126
  conservative: "conservative";
127
127
  }>;
128
+ inertPaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
128
129
  reasons: z.ZodArray<z.ZodString>;
129
130
  stampVersion: z.ZodLiteral<3>;
130
131
  targets: z.ZodArray<z.ZodObject<{
@@ -584,6 +585,16 @@ interface PrVerifyNotRequiredCommand {
584
585
  impactTarget: string;
585
586
  name: string;
586
587
  }
588
+ /**
589
+ * Epic #758 (#762): a consumer package a stamp-scoped selection skipped, and
590
+ * why. Bound to the proof's stamp identity — the field is v4-only (the same
591
+ * window that can carry `impactStamp`) and refused when the proof records no
592
+ * stamp. The factory does not write this list; consumers do.
593
+ */
594
+ interface PrVerifyNotDemandedRecord {
595
+ basis: string;
596
+ name: string;
597
+ }
587
598
  interface PrVerifyProof {
588
599
  schemaVersion: PrVerifySchemaVersion;
589
600
  authoringSession?: string;
@@ -607,6 +618,7 @@ interface PrVerifyProof {
607
618
  impactStamp?: ImpactStamp;
608
619
  mode: ResolvedPrVerifyMode;
609
620
  mergeBaseSha?: string;
621
+ notDemandedRecords?: PrVerifyNotDemandedRecord[];
610
622
  notRequiredCommands?: PrVerifyNotRequiredCommand[];
611
623
  outcome?: "aborted" | "passed";
612
624
  patchId?: string;
@@ -659,6 +671,7 @@ declare const prVerifyProofSchema: z.ZodObject<{
659
671
  "target-scoped": "target-scoped";
660
672
  conservative: "conservative";
661
673
  }>;
674
+ inertPaths: z.ZodDefault<z.ZodArray<z.ZodString>>;
662
675
  reasons: z.ZodArray<z.ZodString>;
663
676
  stampVersion: z.ZodLiteral<3>;
664
677
  targets: z.ZodArray<z.ZodObject<{
@@ -677,6 +690,10 @@ declare const prVerifyProofSchema: z.ZodObject<{
677
690
  "docs-only": "docs-only";
678
691
  full: "full";
679
692
  }>;
693
+ notDemandedRecords: z.ZodOptional<z.ZodArray<z.ZodObject<{
694
+ basis: z.ZodString;
695
+ name: z.ZodString;
696
+ }, z.core.$strip>>>;
680
697
  notRequiredCommands: z.ZodOptional<z.ZodArray<z.ZodObject<{
681
698
  basis: z.ZodString;
682
699
  impactTarget: z.ZodString;
package/dist/schemas.js CHANGED
@@ -241,6 +241,7 @@ const impactStampTargetSchema = z.object({
241
241
  */
242
242
  const impactStampSchema = z.object({
243
243
  basis: z.enum(["target-scoped", "conservative"]),
244
+ inertPaths: z.array(z.string()).default([]),
244
245
  reasons: z.array(z.string()),
245
246
  stampVersion: z.literal(3),
246
247
  targets: z.array(impactStampTargetSchema).superRefine((targets, context) => {
@@ -514,6 +515,16 @@ const assertPrVerifyProofRules = (proof, context) => {
514
515
  message: "notRequiredCommands is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
515
516
  path: ["notRequiredCommands"]
516
517
  });
518
+ if (proof.schemaVersion < 4 && proof.notDemandedRecords !== void 0) context.addIssue({
519
+ code: "custom",
520
+ message: "notDemandedRecords is a schemaVersion>=4 field; a v1–v3 pr:verify proof must not carry it.",
521
+ path: ["notDemandedRecords"]
522
+ });
523
+ if (proof.notDemandedRecords !== void 0 && proof.impactStamp === void 0) context.addIssue({
524
+ code: "custom",
525
+ message: "notDemandedRecords requires impactStamp; package-level not-demanded records are bound to the stamp identity.",
526
+ path: ["notDemandedRecords"]
527
+ });
517
528
  assertBatteryCompleteness(proof, context);
518
529
  assertNotRequiredStampAuthorization(proof, context);
519
530
  };
@@ -546,6 +557,10 @@ const notRequiredCommandSchema = z.object({
546
557
  impactTarget: z.string().min(1),
547
558
  name: z.string().min(1)
548
559
  });
560
+ const notDemandedRecordSchema = z.object({
561
+ basis: z.string().min(1),
562
+ name: z.string().min(1)
563
+ });
549
564
  const prVerifySchemaVersionSchema = z.number().refine((value) => SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.includes(value), { message: `schemaVersion must be one of: ${SUPPORTED_PR_VERIFY_SCHEMA_VERSIONS.join(", ")}` });
550
565
  const prVerifyProofSchema = z.object({
551
566
  authoringSession: z.string().trim().min(1).optional(),
@@ -577,6 +592,7 @@ const prVerifyProofSchema = z.object({
577
592
  "trivial",
578
593
  "full"
579
594
  ]),
595
+ notDemandedRecords: z.array(notDemandedRecordSchema).min(1).optional(),
580
596
  notRequiredCommands: z.array(notRequiredCommandSchema).min(1).optional(),
581
597
  outcome: z.enum(["aborted", "passed"]).optional(),
582
598
  patchId: z.string().regex(/^[0-9a-f]{40}$/u).optional(),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@patronage/software-factory",
3
- "version": "1.0.0-alpha.16",
3
+ "version": "1.0.0-alpha.17",
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.16"
40
+ "@patronage/factory-ci": "1.0.0-alpha.17"
41
41
  },
42
42
  "devDependencies": {
43
43
  "@types/node": "24.13.3",