@nanobpm/nano-workforce 0.143.0 → 0.145.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.
@@ -15,7 +15,6 @@
15
15
  // and, crucially, so a later wave's `dependsOn` can reference the PR keys earlier waves produced.
16
16
  import type { AppJobHandler } from "@nanobpm/urban";
17
17
  import { appendEntry } from "../../app/blackboard.ts";
18
- import { EPIC_PHASE, implementingPhase } from "../../app/epicPhase.ts";
19
18
  import { fetchPrFiles, fetchPrHead } from "../../app/github.ts";
20
19
  import { deriveExclusions, recordExclusions } from "../../app/mergeExclusion.ts";
21
20
  import { loadMergeProtocol } from "../../app/mergeProtocol.ts";
@@ -302,25 +301,11 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
302
301
  const nextWave = stillPendingCurrentWave ? currentWave : currentWave + 1;
303
302
  const hasMoreWaves = stillPendingCurrentWave || nextWave < waveCount;
304
303
 
305
- // Wave index the epic is now on (issue #137): while more waves remain, the wave about to run; on
306
- // the final wave, pinned to the last index so a finished epic reads N/N (nextWave would be
307
- // waveCount, one past the last band). This is a LOCAL value only it is used below to derive the
308
- // `epic_phase` (Implementing wave n/t) and the domain phase.
309
- const projectedCurrentWave = hasMoreWaves ? nextWave : Math.max(0, waveCount - 1);
310
- // Operator-visibility wave progress (current_wave / wave_label) was RETIRED as a stored projection
311
- // (epic #412) — it is now derived from `plan_tasks` by the `plan_wave_label` / `plan_read_model`
312
- // VIEWs (060/061), so this worker no longer denormalises it (select-wave no longer writes it
313
- // either). `projectedCurrentWave` above is not persisted; it only feeds the phase derivation.
314
-
315
- // Domain-phase projection (#261): the wave landed — stamp the phase the epic is ENTERING next,
316
- // which is data-dependent here (unlike the structural spine writers). A trial merge runs → Trial
317
- // merging; another wave follows → Implementing (next wave n/t); otherwise the finalizer runs →
318
- // Finalizing (record-results then advances to the Dispatched terminal).
319
- const epicPhase = runTrialMerge
320
- ? EPIC_PHASE.TRIAL_MERGING
321
- : hasMoreWaves
322
- ? implementingPhase(projectedCurrentWave, waveCount)
323
- : EPIC_PHASE.FINALIZING;
304
+ // The epic's domain phase is no longer stamped here (S8, #542) it is a pure read-model derivation
305
+ // off the live element-instance model (`pollEpicPhase` `deriveEpicPhaseLive`, app/epicPhase.ts),
306
+ // which reads the live token position (a running `implement` fan-out, `trial-merge`, or the
307
+ // finalizer) directly rather than this worker projecting the phase it is ABOUT to enter. This write
308
+ // now only arms the durable wave-merge barrier marker.
324
309
 
325
310
  // Wave-merge barrier: when another wave follows, park the plan-fanout instance at the
326
311
  // `wait-wave-merged` catch event until THIS wave's opened PRs have MERGED (not merely opened).
@@ -334,7 +319,6 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
334
319
  try {
335
320
  await plans(app.data).update(planKey, {
336
321
  gate_wave: hasMoreWaves ? currentWave : null,
337
- epic_phase: epicPhase,
338
322
  updated_at: ts,
339
323
  });
340
324
  } catch (err) {
@@ -83,9 +83,10 @@ test("select-wave dispatches the active wave without writing wave-progress colum
83
83
  assertEquals(plans[0].current_wave, undefined);
84
84
  assertEquals(plans[0].wave_count, undefined);
85
85
  assertEquals(plans[0].wave_label, undefined);
86
- // Domain-phase projection (#261): dispatching the wave marks the epic Implementing (wave n/t),
87
- // derived from this worker's BPMN element id + the levelize records.
88
- assertEquals(plans[0].epic_phase, "Implementing (wave 2/2)");
86
+ // Domain-phase projection is no longer stamped by this worker (S8, #542) the epic phase is now a
87
+ // pure read-model derivation off the live element-instance model (`pollEpicPhase`, app/service.ts),
88
+ // so select-wave introduces no `epic_phase` onto the plan row.
89
+ assertEquals(plans[0].epic_phase, undefined);
89
90
  });
90
91
 
91
92
  test("select-wave captures the preflight's resolvedArtifacts onto plans.bound_artifacts (#292 S4)", async () => {
@@ -16,7 +16,6 @@
16
16
  // immediately (the same 0-task path the flat fan-out already relied on).
17
17
  import type { AppJobHandler } from "@nanobpm/urban";
18
18
  import type { CapabilityNeed } from "../../app/capabilityNeed.ts";
19
- import { deriveEpicPhase } from "../../app/epicPhase.ts";
20
19
  import { plans, planTaskDeps, planTaskNeeds, planTasks } from "../../app/plan.ts";
21
20
  import type { WorkerInputs } from "../../nano-generated/worker-io.d.ts";
22
21
 
@@ -52,20 +51,6 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
52
51
  const statusById = new Map<string, string>();
53
52
  for (const r of rows) statusById.set(r.task_id, r.status);
54
53
 
55
- // Operator-visibility projection (issue #137): mark this as the wave the fleet is now
56
- // implementing, so the epics-index can show wave X/N at a glance. wave_count is derivable from
57
- // the levelized rows (max wave + 1), so the "X/N" label stays correct even if a re-levelize
58
- // changed the total. Best-effort + idempotent (a retry re-writes the same value) and
59
- // display-only — it must never gate control flow, which stays driven by the process
60
- // `currentWave`/`waveCount`/`gate_wave` state.
61
- const waveCount = rows.reduce((m, r) => Math.max(m, r.wave ?? 0), -1) + 1;
62
- // Domain-phase projection (#261): select-wave dispatches this wave and is the last host write
63
- // before the write-silent `implement` MI, so it durably marks the implementation phase for the
64
- // wave it launches — `Implementing (wave n/t)` from the levelize records (job.elementId +
65
- // current/total waves). A null derivation (element id absent) must not clobber the phase.
66
- const epicPhase = waveCount > 0
67
- ? deriveEpicPhase(job.elementId, { current: currentWave, total: waveCount })
68
- : null;
69
54
  // Inter-epic gate projection (#292 slice S4): reaching select-wave proves this epic's leading
70
55
  // capability PREFLIGHT (S3) already went GREEN, so capture the `resolvedArtifacts` the preflight
71
56
  // bound — the exact `pkg@version`s first carrying each producer's awaited capability — onto the
@@ -76,20 +61,21 @@ const handler: AppJobHandler<In, Out> = async (job, app) => {
76
61
  const boundArtifacts = Array.isArray(job.variables.resolvedArtifacts)
77
62
  ? job.variables.resolvedArtifacts.filter((v): v is string => typeof v === "string" && v.trim().length > 0)
78
63
  : [];
79
- try {
80
- await plans(app.data).update(planKey, {
81
- // Operator-visibility wave progress (current_wave / wave_count / wave_label) was RETIRED as a
82
- // stored projection (epic #412) — it is now derived from `plan_tasks` by the `plan_wave_label`
83
- // / `plan_read_model` VIEWs (060/061), so select-wave no longer denormalises it. This write
84
- // still stamps the derived domain phase and the inter-epic gate's bound artifacts.
85
- ...(epicPhase ? { epic_phase: epicPhase } : {}),
86
- ...(boundArtifacts.length > 0 ? { bound_artifacts: JSON.stringify(boundArtifacts) } : {}),
87
- updated_at: ts,
88
- });
89
- } catch (err) {
90
- app.log.error(`select-wave: projecting plan row (epic phase / bound artifacts) failed for ${planKey}`, {
91
- err: String(err),
92
- });
64
+ // The epic's Implementing (wave n/t) phase is no longer stamped here (S8, #542) — it is a pure
65
+ // read-model derivation off the live element-instance model (`pollEpicPhase`, app/service.ts), which
66
+ // reads the running `implement` fan-out directly. This write now only carries the inter-epic gate's
67
+ // bound artifacts, so it is skipped entirely when there are none.
68
+ if (boundArtifacts.length > 0) {
69
+ try {
70
+ await plans(app.data).update(planKey, {
71
+ bound_artifacts: JSON.stringify(boundArtifacts),
72
+ updated_at: ts,
73
+ });
74
+ } catch (err) {
75
+ app.log.error(`select-wave: projecting plan row (bound artifacts) failed for ${planKey}`, {
76
+ err: String(err),
77
+ });
78
+ }
93
79
  }
94
80
 
95
81
  const deps = await planTaskDeps(app.data).find({ plan_key: planKey });