-
Notifications
You must be signed in to change notification settings - Fork 0
fix: widen the ts-ci PR filter to match its own push filter; consume contractViolations #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -968,9 +968,19 @@ export interface VendorFreshnessAnalysis { | |
| risks: ContractCrossingRisk[] | ||
| /** Blast radius per producing repository, sorted by repo name. */ | ||
| blastRadius: BlastRadiusReport[] | ||
| /** Pins whose DECLARED disposition contradicts the DERIVED state — the only hard violation. */ | ||
| /** Pins whose DECLARED disposition contradicts the DERIVED state. */ | ||
| dispositionViolations: string[] | ||
| /** Re-vendor proposals. Emitted, never executed; empty unless `requestedAt` is supplied. */ | ||
| /** | ||
| * Proposals WITHHELD because they did not validate against the vendored contract, as | ||
| * `${pinKey}: ${violation}`. `RevendorProposal.contractViolations` says a violating proposal is | ||
| * not emitted; this is where the ones that were not emitted are named, so the withholding is | ||
| * visible in the seal instead of being a silent omission. | ||
| */ | ||
| contractViolations: string[] | ||
| /** | ||
| * Re-vendor proposals. Emitted, never executed; empty unless `requestedAt` is supplied. | ||
| * Contains only proposals that validated — see `contractViolations` for the rest. | ||
| */ | ||
|
Comment on lines
+973
to
+983
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fair — the doc comment should say so. |
||
| proposals: RevendorProposal[] | ||
| /** sha256 over everything above (proof-carrying). */ | ||
| hash: string | ||
|
|
@@ -1026,7 +1036,7 @@ export function analyzeVendorFreshness(store: HellGraphStore, opts: AnalyzeOptio | |
| const dispositionViolations = pins.filter((p) => !p.dispositionAgrees) | ||
| .map((p) => `${p.pinKey}: declared '${p.disposition}' but derived '${p.freshnessState}' (${p.rationale})`) | ||
|
|
||
| const proposals = opts.requestedAt === undefined ? [] : pins | ||
| const candidates = opts.requestedAt === undefined ? [] : pins | ||
| .filter((p) => p.freshnessState === 'stale') | ||
| .map((p) => { | ||
| const repo = store.out(store.out(p.pinId, VFP_EDGE.pinnedAt)[0]?.id ?? '', VFP_EDGE.producedBy)[0] | ||
|
|
@@ -1038,6 +1048,26 @@ export function analyzeVendorFreshness(store: HellGraphStore, opts: AnalyzeOptio | |
| }) | ||
| }) | ||
|
|
||
| // `RevendorProposal.contractViolations` has always said "non-empty means the proposal is NOT | ||
| // emitted". Nothing implemented that sentence: every candidate was sealed regardless, and the | ||
| // array had no reader anywhere in the repo — no branch, no `.length`, no throw. A violation | ||
| // sealed into a receipt nobody reads is worse than no check, because the receipt then carries | ||
| // evidence that the system looked. | ||
| // | ||
| // So the declared rule is now the actual rule: a violating proposal is WITHHELD, and its | ||
| // violations are named at the top level the way `dispositionViolations` already is. Withholding | ||
| // alone would be a silent drop, and naming alone would leave a consumer free to act on a | ||
| // proposal the contract says was never emitted; it takes both. | ||
| // | ||
| // Reported, not thrown. `analyzeVendorFreshness` is documented PURE and deterministic, and one | ||
| // malformed pin must not destroy the verdicts for every other pin in the run — the same reason | ||
| // `dispositionViolations` is a list and not an exception. The engine proposes; a membrane gate | ||
| // outside it decides. | ||
| const proposals = candidates.filter((p) => p.contractViolations.length === 0) | ||
| const contractViolations = candidates | ||
| .filter((p) => p.contractViolations.length > 0) | ||
| .flatMap((p) => p.contractViolations.map((v) => `${p.effectRequest.target.identifier}: ${v}`)) | ||
|
|
||
| return sealed({ | ||
| method: METHOD, | ||
| contract: { ...EFFECT_REQUEST_CONTRACT }, | ||
|
|
@@ -1046,6 +1076,7 @@ export function analyzeVendorFreshness(store: HellGraphStore, opts: AnalyzeOptio | |
| risks, | ||
| blastRadius, | ||
| dispositionViolations, | ||
| contractViolations, | ||
| proposals, | ||
| }) | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Keeping the guard — both concerns resolve to a false RED under reformatting, never a false pass, so it fails safe. (1) If
on.push.pathswere rewritten as unquoted scalars,declaredwould be empty and the very next lineif not declared: sys.exit('::error::...has gone blind')fails the step loudly — it does not silently pass. (2)end = next(...)raisingStopIterationifpush:were the laston:key is likewise an uncaught error → red job, not a skipped build. The guard's job is to catch drift betweenon.push.pathsand the PR change-detector, and it has teeth: verified that adding adocs/**push entry theTS_PATHSregex doesn't cover makes it exit 1 with "would report build-and-verify-dist green without building anything." Falling back tolen(wf)and accepting unquoted items is reasonable hardening, but there is no false-green to close here.