229: Friction: aggregate recompute at end of run (.pi-loop/friction/) + console Friction: line #244
Loading…
Reference in a new issue
No description provided.
Delete branch "feature/issue-229/friction-aggregate-recompute-at-end-of-run-pi-loop"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
229: Friction: aggregate recompute at end of run (.pi-loop/friction/) + console Friction: line
Implements 229.
Generated in 26m 53s.
Review findings
Now I have a good picture. Let me check a few more details before editing — the artifacts barrel, current typecheck state, and any tests around the recompute logging.Now let me check which friction modules import from the artifacts barrel (relevant for the import cycle), and verify the runPipeline.ts imports:The cycle analysis is clear:
friction/index.tsalready pulls inartifacts/index.tsviapersistFriction/aggregateFriction, so switching the validators to the barrel completes the cycle. BecausevalidateFriction.tsdereferencesFRICTION_CATEGORIESat module top level, the barrel must evaluatetypes/friction.jsbefore the services that pull inartifacts. I'll reorder the barrel accordingly.Let me apply the fixes, starting with the shared ISO-8601 helper and the two validators (High + Low #6/#7):Now reorder the friction barrel so
types/friction.jsis evaluated before the services that pull inartifacts(keeping the completedartifacts ↔ frictioncycle out of TDZ):Let me verify the cycle is safe before continuing (import both barrels and run the friction/artifacts tests):Both barrels load from either entry direction. Now thebuildFinalSummary.tspluralization fix (Medium #4) and exporting the line formatter for reuse in the no-code-change path (Low #5):Now therunPipeline.tschanges — importformatFrictionSummaryLine, thread the aggregate into the no-code-change summary (Low #5), and use a dedicatedsummarylog stage (Low #9). First let me check the existing import ofbuildFinalSummary:Now add thesummarystage to theLogStageunion:Now therunPipeline.test.tsfixes (Medium #2 mock typing + the pluralization expectation):Now theaggregateFriction.test.tscast fix (Medium #3):Now the ADR-016 wording alignment (Low #8):Now let me verify the new typecheck errors are resolved:All new typecheck errors are resolved. Let me confirm no new errors were introduced (compare total error count vs. the pre-fix baseline):RemainingrunPipeline.test.tserrors are all pre-existing (lines 331–1541, untouched code). Now let me run the test suite and lint:All 1936 tests pass. Now lint and prettier check on the changed files:Lint passes. Let me see what prettier wants to change (to avoid reformatting unrelated pre-existing code):The ADR was already non-conforming at baseline; the 4 TS files became non-conforming due to my edits. Let me fix those with prettier and inspect the ADR issue:The ADR's prettier complaints are all pre-existing (em-dash/comment-alignment style) — my edited line is conformant, so I'll leave the file alone to avoid out-of-scope reformatting. Now let me review the full diff of my changes:The diff looks good. Let me strengthen the two tests that verify my Low #5 and #9 fixes — assert the no-code-change summary now renders theFriction:line, and that the recompute warning uses thesummarystage label:All gates pass. Let me do a final review of the modifiedrecomputeFrictionAggregateregion and the no-code-change path to confirm consistency:All fixes are complete and verified. Here is the final state:Summary of remediation (single pass)
Gates:
npm run build✅ ·npm test✅ (139 files / 1936 tests) ·npm run lint✅ · prettier on all changed TS files ✅ ·npm run typecheck— the two new error groups this diff introduced (runPipeline.test.ts:2301-2305,aggregateFriction.test.ts:268) are fixed; the pre-existing errors on main in untouched files remain untouched.Fixed (all 9 findings):
frictionbarrel. To keep the completedartifacts ↔ frictioncycle safe at runtime (the validators dereferenceFRICTION_CATEGORIES/FRICTION_STAGESat module top level), I reorderedsrc/friction/index.tssotypes/friction.jsis evaluated before the services that pull in theartifactsbarrel. Verified both barrels load from either entry direction.aggregateFrictionmocks via a localAggregateFrictionInputalias; cast the schema-invalid test categoryas never; fixed the1 categoriespluralization (implementation + both test expectations).Friction:line (via exportedformatFrictionSummaryLine, with a test assertion);VALID_STAGESderives fromFRICTION_STAGES;ISO_8601extracted to sharedsrc/artifacts/helpers/iso8601.ts; ADR-016 §6 wording aligned to the implementation; recompute warnings now use a dedicated'summary'log stage (added toLogStage, with a test asserting the[HH:mm summary] WARNprefix).Review Findings — 229
Summary
The change delivers the issue's two goals — recomputing the cross-run friction aggregate (
.pi-loop/friction/friction.json+FRICTION.md) at the end of every run and rendering aFriction:line in the final run summary — with solid deterministic-seam test coverage and no artifact leakage (.pi-loop/remains gitignored). Gates run:npm run build✅,npm test✅ (139 files / 1936 tests),npm run lint✅, prettier on changed files ✅.npm run typecheck(full, incl. tests) ❌ — it already failed on main in untouched test files; the 5 new typecheck errors this diff added are fixed in this pass (see Medium, all resolved). No Critical findings; the core acceptance criteria (aggregate recompute on success/failure/no-code-change/resume paths + consoleFriction:line) are met.Critical
High
src/artifacts/helpers/validateFriction.ts:8— Cross-module deep import: the import was changed from thefrictionbarrel ('../../friction/index.js') to'../../friction/types/friction.js', and the newvalidateFrictionAggregate.ts:8also deep-imports'../../friction/types/friction.js'forFRICTION_AGGREGATE_RECENT_LIMIT/FRICTION_CATEGORIES/FRICTION_STAGES. This bypasses thefrictionmodule barrel, violating CODING_STANDARDS §4 / DOMAIN.md "Import boundary / Deep import" (and §6 classifies broken module boundaries as High). The friction barrel already exports every symbol needed, and the underlyingartifacts ↔ frictionre-export cycle is safe at runtime (ESM live bindings; all cross-module value uses are at call time — the passing build/tests confirm). Suggested fix: import from'../../friction/index.js'in both files (reverting the regression invalidateFriction.ts), or — if the cycle is deemed unacceptable — move the friction schema/constants/types intosrc/shared/and import those from both modules.Medium
src/orchestrator/services/runPipeline.test.ts:2301-2305— Newnpm run typecheckfailures:aggregateFriction.mock.calls[0]?.[0]is typed asneverbecause thevi.fn(() => makeFrictionAggregate())mock has no declared parameter (errors TS2493/TS2339 at lines 2301–2305). Suggested fix: type the mock explicitly, e.g.vi.fn((_input: { runsRoot: string; aggregateDir: string; now: () => string; logWarn: (message: string) => void }) => makeFrictionAggregate()).src/friction/services/aggregateFriction.test.ts:268— Newnpm run typecheckfailure:category: 'network-outage'is not assignable toFrictionCategory(TS2322). The test intentionally writes a schema-invalid artifact; the siblingvalidateFrictionAggregate.test.tshandles this withas never. Suggested fix: cast the override, e.g.{ ...makeEntry(), category: 'network-outage' as never }, or build the invalid JSON via a plain object typedunknown.src/orchestrator/helpers/buildFinalSummary.ts:129— Pluralization defect in the user-facing console line: rendersFriction: 3 events (1 categories)when only one category is present (test atbuildFinalSummary.test.ts:285asserts the ungrammatical string). Suggested fix: render1 categorywhenObject.keys(aggregate.byCategory).length === 1(e.g.... (${n} ${n === 1 ? 'category' : 'categories'})) and update the test expectation.Low
src/orchestrator/services/runPipeline.ts:529— On the no-code-change completion path the aggregate is recomputed but the returned value is discarded andbuildNoCodeChangeSummaryrenders noFriction:line, so the console friction one-liner never appears for those (valid,ok: true) runs. Suggested fix: thread the aggregate into the no-code-change summary (e.g. append aFriction:line) or drop the recompute there and note that the path intentionally omits it.src/artifacts/helpers/validateFriction.ts:15—VALID_STAGESis still hardcoded to['implement', 'remediate']even though the newFRICTION_STAGESconstant (src/friction/types/friction.ts:46) is documented as the single source of truth and is exported from the friction barrel. Suggested fix: import and useFRICTION_STAGES(asVALID_CATEGORIES/VALID_RESOLUTIONSalready use their constants).src/artifacts/helpers/validateFrictionAggregate.ts:25— TheISO_8601regex is a copy of the one invalidateFriction.ts:28, but the comment claims it is "shared with the per-run validator". Suggested fix: extract the regex to a shared helper (e.g.src/friction/helpers/) or correct the comment to avoid implying a shared constant.src/orchestrator/helpers/buildFinalSummary.ts:129/docs/adr/016-friction-logging.md§6 — The ADR specifies the console one-liner asn friction events (m new categories); the implementation rendersFriction: n events (m categories). Suggested fix: align the wording (either update the ADR to match the implementation, or render "new categories").src/orchestrator/services/runPipeline.ts:447,452— The end-of-run aggregate recompute logs warnings throughlogger.forStage('setup'), sorun.logshows a[HH:mm setup] WARNline at the very end of a run that failed elsewhere. Suggested fix: use a neutral stage label (e.g.logger.forStage('mr')or a dedicatedsummarystage) so the log attribution matches when the warning occurs.Documentation
Updated files: