Files

12 KiB

Optional business tracker (Q247) implementation plan — row 324 / #1820

Goldie, 2026-10-23. Base 860afc2b66, approved row298 candidate. New worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row324, branch goldie/row324. Spec: git object f375f4e92:docs/plans/2026-10-23_optional-tracker-brief.md, section Brief: tracker becomes optional in the business file. Sage-3836 and mos-1555/Q247 authorize optional tracker; Duckworth reviews credential registration.

Goal and design

A raw business document may omit tracker. Normalize omission to tracker:null, consistent with existing optional launch/role-tracker representations. A present tracker (including null, false, malformed objects) must continue through checkTracker and its existing refusals; distinguish absence with Object.hasOwn rather than a truthiness check. Keep checkTracker unchanged. Required top-level keys no longer include tracker. Only compare role bots with the sync bot when a checked business tracker exists. Role definitions needing vikunja still require their own tracker and credentials unchanged.

CLI validate collects sync credential refs only when tracker is present. bootConfig's trackerFor returns null before interpreting tracker variables when business tracker is absent, so an old tracker.baseUrl variable cannot accidentally create sync/task adapter configuration. Console businessView emits no sync credential rows for absent tracker. Runtime registration, fixed-configuration reload comparison, instance-settings bot collision checks and broker credential display already use optional chaining on the row298 base; preserve those checks. The tasks adapter remains fail-closed for an explicitly inconsistent raw adapter config, and no-tracker bootConfig never configures it for this business.

Reader audit (before implementation)

Command: git grep -n -E 'tracker.sync|.tracker\b' -- 'packages//src/'. Every result is grouped here, with base line numbers:

Reader Absent business tracker behavior / action
business/src/business.mjs:85-96,296-300 checkTracker remains exact for present input; add omission normalization and guard own-sync-bot comparison.
business/src/business.mjs:143-150 Per-role tracker, independent of business tracker. Keep requirement/collision checks unchanged.
business/src/cli.mjs:85 Guard Object.values sync credential refs; absent contributes zero.
business/src/instance-settings.mjs:108,120,126-127,133,223 Role add/change and bot collision/retirement: old.tracker?.sync already skips absent sync; role trackers unchanged. No edit needed.
business/src/launch-settings.mjs:161,235-236 Per-role tracker assistant view/update, independent of business tracker; no edit.
business/src/resolve.mjs:93 Per-role tracker in resolved output; no edit.
bus/src/runtime.mjs:131 Existing b.tracker?.sync?.credentials check registers no @sync when absent; preserve and exercise with new boot test.
bus/src/runtime.mjs:243,247 Reload compares per-role tracker/null and canonical business sync/null; safe already, no edit.
bus/src/broker.mjs:3969-3970 Optional per-role tracker bot map; independent, no edit.
bus/src/broker.mjs:4497 Optional per-role tracker returned from instance write; independent, no edit.
bus/src/broker.mjs:6242 Credential display resolves optional sync bot; no @sync row exists on trackerless boot. Safe already.
cli/src/config.mjs:57-60,137-138 (trackerFor; not caught by literal grep) Add business-tracker absence guard; do not configure tracker adapter even if tracker.baseUrl remains set.
cli/src/instances.mjs:118 Builds per-role tracker update request only when user supplies bot; unrelated to business tracker.
tasks/src/adapter.mjs:25,28-30,35,45,81 settings rejects missing sync with controlled tracker-config before dereferencing labels/botId; files uses optional chaining. Factory iterates only configured trackers; absent business never configured by bootConfig. Preserve explicit-inconsistent-config refusal.
webui/src/reads.mjs:164,166 Role credentials independent; guard sync credential display at166 so trackerless Console does not throw.
webui/src/public/bus.js:785 Per-role editor tracker patch; independent of business tracker.
conversation/src/controller.mjs:443,502,748,807,820,831,888,935,961,997,1008,1021,1048,1056,1268,1308,1330,1334,1413,1447 Different execution-tracking object/class, unrelated to business/Vikunja config. No change.

Positive control: grep finds business.mjs:296 and webui reads:166, the unsafe business-tracker readers. Review focus: present null/malformed tracker still refuses; per-role Vikunja trackers still required; stale tracker variables do not enable sync; Console credential view remains usable; no @sync registration during runtime boot/reload.

Steps and evidence plan

  1. Document design and full reader list (this file); claim row324 in-progress as Goldie.
  2. Add new tests for absent tracker loader+CLI validate, malformed/present preservation, per-role tracker requirement, stale-variable bootConfig behavior, actual bus boot with empty credential list/no @sync, and Console business view. New test files only unless Mos expressly approves old-test change below.
  3. Make the minimal four-source-file change (business loader, business CLI, cli config, webui reads). Existing runtime/instance-settings/broker optional checks require coverage, not gratuitous edits.
  4. Static diff checks. Push untested candidate only as expressly ordered by sage-3836, then ask Sage for CLEAR. No tests before CLEAR.
  5. On CLEAR, new test files whole, applicable existing whole files/suites, then a mutant removing loader presence check. Restore and check source bytes. Record outputs; full landing gate is Sage's. Duckworth review follows.

Pre-existing test boundary

Inspection finds packages/business/tests/business.test.mjs:98 loops over [human,arbiters,projects,tracker,roles] and :99 asserts deletion of each refuses. Q247 directly invalidates the tracker iteration. Proposed sole old-test edit: remove just the tracker string from that required-key list, leaving all other iterations/assertions intact, and add explicit tracker-omission coverage in a new test file. This requires Mos OK under mos-1272; stop implementation and report before editing existing tests. No tests have run.

Implementation draft and old-test ruling

Sage-3840 relays Mos mos-1556: mos-1272 OK to remove only tracker from business.test.mjs's required-key list, preserving the four other keys and the assertion. Done exactly, one addition/one deletion at original line98; no other old test edited. All positive/negative coverage is in two new files: business/tests/optional-tracker.test.mjs and cli/tests/optional-tracker.test.mjs. The latter covers Console businessView as required by sage-3840.

Design now implemented in the four source files identified above; README explains omission versus literal null. New tests cover loader/CLI absence, present value preservation, malformed/present undefined or null refusal, unchanged role-bot requirement, stale business/project tracker variables, actual runtime startup with no credential files or @sync reference, Console view, and continued missing-token refusal for a present tracker. Runtime test uses useFile on unknown @sync to distinguish an unregistered reference from a registered but unavailable credential; callback must never run. Fixture project tracker.project is set, so removing trackerFor's presence guard would actually configure sync instead of passing accidentally through the no-project guard.

No tests run. New node_modules symlink points to canonical checkout dependencies and is ignored; it will not be committed. Sage-3836 expressly requests push before any tests; that specific sequencing overrides the usual green-before-commit default only for this draft push. Status is untested until CLEAR. Claim wrote goldie-0324-start-1 revision3759, briefed→in-progress. Row323 remains paused at the product boundary until324 lands.

Static verification: node --check passed for all four edited source modules and both new test files; git diff --check passed. No node --test or shell suite was run. reader-audit.txt captures the requested grep on the draft. MANIFEST.sha256 pins README, four source files, the one existing test and both new tests. mutant.py prepares the required omission-presence mutant and restores/checks bytes in finally; it has not run.

Proposed CLEAR run order: new business optional-tracker.test.mjs whole (5 tests), new cli optional-tracker.test.mjs whole (4 tests), existing business.test.mjs whole, then the targeted presence mutant and restored new tests. Broaden to business/CLI/Console/bus package gates only when Sage assigns that scope/slot. All test runs use timeout -k 10 and an explicitly allocated TMPDIR under /mnt/storage/scratch/tmp.

Rebased verification — sage-3850 CLEAR

Rebased c4b14952e onto pushed refactor 3569bf54f53870fcd4e92cdbbb95e3412f629f3a as 7e59ba8b3d6f63c30a0fa308c0607dfc1e47dd12. The landing range has no changes in this row's business/config/Console source paths. Only BUILD-LOG.md and docs/SESSIONS.md conflicted: retained the entire landed file then appended this row's original suffix. All eight manifest-pinned files remain byte-equal to the draft; range-diff differences are log context only. To obey no-force-push while preserving the published draft, the rebased candidate is published on goldie/row324-r2; original remote goldie/row324 remains at c4b14952e.

Scratch TMPDIR was created by mktemp -d /mnt/storage/scratch/tmp/goldieXXXXXX: /mnt/storage/scratch/tmp/goldieOdgIjr. All runs were serialized, with timeout -k 10 (120s new files/mutant driver, 180s existing whole file). Node v26.8.1. No test used /tmp or a real host configuration/credential.

Order Whole file / check Result Evidence
1 packages/business/tests/optional-tracker.test.mjs 5/5 pass, exit0 test-business-optional.log
2 packages/cli/tests/optional-tracker.test.mjs 4/4 pass, exit0 test-cli-optional.log
3 packages/business/tests/business.test.mjs 21/21 pass, exit0 test-business-existing.log
4 Presence-check mutant, targeted omission test 0/1 pass, exit1, killed as expected mutant.log, mutant-driver.log
5 Restored new business file whole 5/5 pass, exit0 test-business-restored.log
6 Restored new CLI file whole 4/4 pass, exit0 test-cli-restored.log

No unexpected first-run red or old-test red. The Console absent-tracker read test is the third test in the new CLI file and passed on both whole-file runs. There is no separate edited webui test file; the only test files touched by this row are the three whole files above. Existing fixture preservation is covered by the whole business.test.mjs, including its untouched fixture, role bot, credential, sync bot collision and example checks. Broader package/script gates remain Sage's.

Required mutant diff (business/src/business.mjs:296):

-  const tracker = Object.hasOwn(document, "tracker") ? checkTracker(document.tracker, file) : null;
+  const tracker = checkTracker(document.tracker, file);

The new omission test fails with BusinessError tracker must be a JSON object. mutant.py restores in finally and checks exact bytes. Restored business.mjs SHA256: e76db42158f396ec6a44f97320355d6c2254dccb5fc53cd04e8ac42843b0d60a. Full manifest check passes after restoration. git diff --check passes. The full reader list remains above; reader-audit.txt is its raw grep receipt.

The only pre-existing test delta still is exactly one list entry removed at business.test.mjs:98, under mos-1556: tracker is removed; human/arbiters/projects/roles and the assertion remain. No production/test source changed during verification. Only evidence and append-only log notes follow the tested rebased commit. Slot is returned in the completion report; Duckworth review and Sage's full landing gate remain pending.

Evidence log trailing whitespace is normalized for git diff --check; test results and output content are unchanged.