From 8b5fa34454a616b0f720681cd4f333b3360f0cb8 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 10:03:06 +0000 Subject: [PATCH 1/3] fix(cpm): cap Late Finish at project duration so SS/FF/SF long-poles stay critical MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The CPM backward pass seeded the successor `reduce` with `Infinity`, so a node with successors took its Late Finish purely from successor constraints and was never bounded by the project duration. Standard CPM (PMBOK float definitions; Kelley/Walker) initializes every activity's LF to the project finish, then tightens it with successors: `LF = min(projectDuration, min over successors)`. Under pure FS networks a successor bound is always <= project duration, so the missing cap was invisible (every prior test used FS chains). But an SS/FF/SF successor can impose a looser bound than the project end, letting a predecessor's LF exceed the project duration and giving a genuinely-critical activity false total float — and an empty critical path. Reproduction: `computeCpm([{id:'A',duration:10},{id:'B',duration:2,predecessors:'ASS'}])` reported A (the sole 10-day long pole) as lf=18 (> project 10), slack=8, critical=false, criticalPath=[]. A planner would wrongly see 8 days of buffer on the one activity that drives the finish. After the fix: A lf=10, slack=0, critical=true, criticalPath=["A"]; B keeps its real 8d float. Fix: seed the reduce with `projectDurationDays` instead of `Infinity`. Adds a regression test to tests/unit/dep-types.test.mjs. Verified: full pure-math suite (13 suites) + property fuzz (14) pass; existing FS and SS-chain assertions unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH --- analytics.js | 7 ++++++- tests/unit/dep-types.test.mjs | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) diff --git a/analytics.js b/analytics.js index 5d86500f..b32ca052 100644 --- a/analytics.js +++ b/analytics.js @@ -169,7 +169,12 @@ export function computeCpm(tasks, opts = {}) { else if (l.type === 'SF') c = slf - l.lag + d; else c = sls - l.lag; // FS return Math.min(m, c); - }, Infinity) + // Seed with projectDurationDays (not Infinity): standard CPM caps every + // activity's Late Finish at project completion, then tightens it with + // successor constraints. With Infinity, a node whose only successor links + // are SS/FF/SF could take an LF looser than the project end, giving a + // truly-critical activity false total float (and an empty critical path). + }, projectDurationDays) : projectDurationDays; lf.set(id, finish); ls.set(id, finish - d); diff --git a/tests/unit/dep-types.test.mjs b/tests/unit/dep-types.test.mjs index 8943fac9..cbc0ff9a 100644 --- a/tests/unit/dep-types.test.mjs +++ b/tests/unit/dep-types.test.mjs @@ -61,4 +61,22 @@ const T = (id, duration, predecessors = '') => ({ id, duration, predecessors }); assert.equal(r.cycleDetected, true); } +// backward pass caps Late Finish at the project duration. An SS long-pole with a +// short terminal follow-on: A (10d) alone drives the schedule end; B (2d) starts +// with A but finishes early. A must be critical with zero total float. Regression: +// seeding the successor reduce with Infinity left A's LF uncapped at 18 (> project +// duration 10), giving A a false slack of 8, marking it non-critical, and returning +// an EMPTY critical path for a schedule that plainly has one. +{ + const r = computeCpm([T('A', 10), T('B', 2, 'ASS')]); + assert.equal(r.projectDurationDays, 10, 'A(0-10) is the long pole'); + assert.equal(r.perTask.A.lf, 10, 'LF capped at project duration, not 18'); + assert.equal(r.perTask.A.slack, 0, 'sole long-pole activity has zero total float'); + assert.ok(r.perTask.A.critical, 'A is critical'); + assert.deepEqual(r.criticalPath, ['A'], 'critical path is A, not empty'); + // B keeps its real slack and stays non-critical. + assert.equal(r.perTask.B.slack, 8, 'B(0-2) has 8d float to the project end'); + assert.equal(r.perTask.B.critical, false); +} + console.log('✓ dependency-type (SS/FF/SF+lag) tests passed'); From 20111a62c172b3561ee4c5bb8b5dfdab9c2529df Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 23:50:14 +0000 Subject: [PATCH 2/3] fix(security): bump @hono/node-server 1.19.14 -> 2.0.12 (GHSA-frvp-7c67-39w9) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit @hono/node-server <2.0.5 has a moderate path-traversal in `serveStatic` on Windows via an encoded backslash (%5C). The org Security Scan (trivy-fs/OSV) flags it repo-wide. Patched in 2.0.5+; bump to ^2.0.12. Scope of the "breaking" 1.x -> 2.x major is minimal here: server.mjs imports only `serve` (not the vulnerable `serveStatic`), and `serve({ fetch, port }, info => ...)` is unchanged across the major. hono stays ^4.12.27 (deduped; 2.x supports hono 4). No runtime dependency added — the two-dep contract holds. Verified: npm audit → 0 vulnerabilities; test:unit (13/13 files), test:api (smoke + rate-limit) all pass — the real server boots and serves correctly on 2.x. (test:e2e:cloud is browser-revision-blocked locally: the sandbox ships Chromium build 1194 while @playwright/test 1.61.1 wants 1228; CI runs that gate with the matching browser.) Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH --- package-lock.json | 10 +++++----- package.json | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 21575e82..7abe7165 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "name": "scopeweave", "version": "1.0.0", "dependencies": { - "@hono/node-server": "^1.19.14", + "@hono/node-server": "^2.0.12", "hono": "^4.12.27" }, "devDependencies": { @@ -17,12 +17,12 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.14", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", - "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", + "version": "2.0.12", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-2.0.12.tgz", + "integrity": "sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==", "license": "MIT", "engines": { - "node": ">=18.14.1" + "node": ">=20" }, "peerDependencies": { "hono": "^4" diff --git a/package.json b/package.json index 9ae8b292..2f90e2a3 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ "fuzz": "node --test tests/fuzz/*.mjs" }, "dependencies": { - "@hono/node-server": "^1.19.14", + "@hono/node-server": "^2.0.12", "hono": "^4.12.27" }, "devDependencies": { From 5ff195bc54de8418ce56a26f8279f4e6253509b2 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 30 Jul 2026 23:51:49 +0000 Subject: [PATCH 3/3] fix(security): sync pnpm-lock.yaml @hono/node-server 1.19.14 -> 2.0.12 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The repo carries two lockfiles (CI caches npm/package-lock.json, but a pnpm-lock.yaml also exists). The previous commit updated only package-lock.json, so trivy-fs — which scans every lockfile in the tree, not just the CI one — still flagged @hono/node-server 1.19.14 (GHSA-frvp-7c67-39w9) via pnpm-lock.yaml. Bring pnpm-lock.yaml in sync so both lockfiles pin the patched 2.0.12. Minimal diff: only the @hono/node-server entries change; hono stays 4.12.28, lockfileVersion unchanged. Co-Authored-By: Claude Opus 4.8 Claude-Session: https://claude.ai/code/session_01HdCssGnNMhKHNu3TXFstWH --- pnpm-lock.yaml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bffabf92..5d7b7de5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9,8 +9,8 @@ importers: .: dependencies: '@hono/node-server': - specifier: ^1.19.14 - version: 1.19.14(hono@4.12.28) + specifier: ^2.0.12 + version: 2.0.12(hono@4.12.28) hono: specifier: ^4.12.27 version: 4.12.28 @@ -24,9 +24,9 @@ importers: packages: - '@hono/node-server@1.19.14': - resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} - engines: {node: '>=18.14.1'} + '@hono/node-server@2.0.12': + resolution: {integrity: sha512-eWpQYr67tqJLeaSUl0Q+TquuYfUdTibpOJlUMV2FfUP7+KqCC5TufnwnlXL6mobZBJbGAYRd7ZvEBDCbLInjhg==} + engines: {node: '>=20'} peerDependencies: hono: ^4 @@ -63,7 +63,7 @@ packages: snapshots: - '@hono/node-server@1.19.14(hono@4.12.28)': + '@hono/node-server@2.0.12(hono@4.12.28)': dependencies: hono: 4.12.28