@nanobpm/nano-workforce 0.48.2 → 0.49.0
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/CHANGELOG.md +7 -0
- package/app/plan.ts +12 -0
- package/db/migrations/022_plan_wave_progress.sql +24 -0
- package/package.json +2 -2
- package/pages/epic-detail.page.json +314 -0
- package/pages/epic.page.json +8 -235
- package/pages/home.page.json +219 -47
- package/scripts/pages-contract.test.ts +8 -5
- package/workers/record-plan/worker.test.ts +89 -0
- package/workers/record-plan/worker.ts +8 -0
- package/workers/record-wave/worker.test.ts +56 -0
- package/workers/record-wave/worker.ts +14 -0
- package/workers/select-wave/worker.test.ts +33 -2
- package/workers/select-wave/worker.ts +23 -1
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
// Emitting an empty `waveTasks` is fine: the MI activity over an empty collection completes
|
|
16
16
|
// immediately (the same 0-task path the flat fan-out already relied on).
|
|
17
17
|
import type { AppJobHandler } from "@nanobpm/urban";
|
|
18
|
-
import { planTaskDeps, planTasks } from "../../app/plan.ts";
|
|
18
|
+
import { plans, planTaskDeps, planTasks } from "../../app/plan.ts";
|
|
19
19
|
|
|
20
20
|
interface In extends Record<string, unknown> {
|
|
21
21
|
planKey: string;
|
|
@@ -47,6 +47,28 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
|
|
|
47
47
|
const statusById = new Map<string, string>();
|
|
48
48
|
for (const r of rows) statusById.set(r.task_id, r.status);
|
|
49
49
|
|
|
50
|
+
// Operator-visibility projection (issue #137): mark this as the wave the fleet is now
|
|
51
|
+
// implementing, so the epics-index can show wave X/N at a glance. wave_count is derivable from
|
|
52
|
+
// the levelized rows (max wave + 1), so the "X/N" label stays correct even if a re-levelize
|
|
53
|
+
// changed the total. Best-effort + idempotent (a retry re-writes the same value) and
|
|
54
|
+
// display-only — it must never gate control flow, which stays driven by the process
|
|
55
|
+
// `currentWave`/`waveCount`/`gate_wave` state.
|
|
56
|
+
const waveCount = rows.reduce((m, r) => Math.max(m, r.wave ?? 0), -1) + 1;
|
|
57
|
+
try {
|
|
58
|
+
await plans(app.data).update(planKey, {
|
|
59
|
+
// Keep the three progress fields consistent: with no levelized rows (waveCount 0) there is
|
|
60
|
+
// no wave to implement, so current_wave is NULL too — never a stray index against NULL N.
|
|
61
|
+
current_wave: waveCount > 0 ? currentWave : null,
|
|
62
|
+
wave_count: waveCount > 0 ? waveCount : null,
|
|
63
|
+
wave_label: waveCount > 0 ? `${currentWave + 1}/${waveCount}` : null,
|
|
64
|
+
updated_at: ts,
|
|
65
|
+
});
|
|
66
|
+
} catch (err) {
|
|
67
|
+
app.log.error(`select-wave: projecting current_wave failed for ${planKey}`, {
|
|
68
|
+
err: String(err),
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
50
72
|
const deps = await planTaskDeps(app.data).find({ plan_key: planKey });
|
|
51
73
|
const depsByTask = new Map<string, string[]>();
|
|
52
74
|
for (const d of deps) {
|