Document · doc-0003

Wave operating model

The fan-out protocol doc is the model — run contract, routing, lane briefs, the goal-file template, the pre-flight checklist. Nothing from it is restated here. This document carries only what is true of this repository: the rules it added, the defects that recur in this codebase, its lane boundaries and its one exclusive resource.

backlog doc list --plain shows both.

The gate, and the trap in it

pnpm lint                                                   # eslint, currently 0 errors
PUBLIC_ENTRA_CLIENT_ID=00000000-0000-0000-0000-000000000000 pnpm check   # svelte-check, 0 errors
PUBLIC_ENTRA_CLIENT_ID=00000000-0000-0000-0000-000000000000 pnpm build   # vite + adapter-cloudflare

pnpm check and pnpm build fail without PUBLIC_ENTRA_CLIENT_ID set, with Module '"$env/static/public"' has no exported member 'PUBLIC_ENTRA_CLIENT_ID' — a SvelteKit $env/static/public failure, not a code error. Any GUID works; nothing authenticates at build time. A lane that reports this as a type error has misread it, and a lane that “fixes” it by touching src/lib/auth/config.ts has broken working code.

There is no test framework. Verification is the three gates plus, for anything user-visible, a real browser: pnpm dev, then drive it through the Chrome MCP server and require a clean console. Do not invent a test harness mid-wave to satisfy a habit — say the change was gate-verified and browser-verified, or say it was not.

Recurring defects in this codebase

Each of these has actually happened here. They are the first things to check when a lane’s change misbehaves, and the first things to review in its diff.

Duplicate {#each} keys silently kill the parent’s reactivity. Graph API collections — deviceCompliancePolicyStates, deviceConfigurationStates — return repeated ids for one device. An each_key_duplicate thrown in a child component takes out the whole parent update cycle: the symptom is a tab indicator that moves while the content does not, and $state writes that look ignored. It is not a rendering bug and it will not be found by reading the component that appears broken. Key by `${id}-${index}` wherever the ids come from Graph data.

$effect calling an async function tracks everything that function reads. Read the dependencies you actually want tracked explicitly, then wrap the call in untrack(). Skipping this produces effect loops that present as runaway Graph requests.

Svelte 5 conventions are enforced, not stylistic: SvelteMap/SvelteSet over native Map/Set, const for $derived, no svelte:component. A lane using the Svelte 4 idiom will pass pnpm build and be wrong.

Graph assign endpoints REPLACE the whole assignment list. Anything touching src/lib/graph/execute.ts must preserve the fetch → merge → apply order. Skipping the fetch silently deletes every assignment the app did not know about — the single highest-blast-radius path in the product, and the reason that file is never a parallel lane’s incidental edit.

Many /beta Intune endpoints are removed or return 500 while still being documented. Do not design a feature around an endpoint without confirming it responds. The verified working and non-working set is in the Graph endpoint findings doc; add to it rather than re-probing.

Colours are never invented here. src/app.css is a token layer generated from the m7kni design system; read the system with DesignSync and change the token, not the call site. rg over src/ returns zero hex colours and zero Tailwind palette classes, and that property is worth preserving — it is why an accent change costs one file. Two issues (#93, #94) exist because it was not obvious.

Lane boundaries

Natural, non-overlapping lanes in this codebase:

Single-owner files — never edited by two lanes, and never in parallel:

File Why
src/routes/+layout.svelte the sidebar registry; every new area appends to it
src/lib/graph/client.ts the shared request/retry/batch seam
src/lib/types/graph.ts, schemas.ts, wizard.ts the frozen type seam every lane codes against
src/app.css the design token layer
src/lib/graph/execute.ts the assignment-merge path, per above
backlog/config.yml tracker config

Freeze the type seam and the token names before fan-out. A wave that lets two lanes each add a field to wizard.ts will produce a clean-looking merge that drops one of them.

The one exclusive resource

There is exactly one live Intune tenant and one browser MSAL session. Only one lane at a time may be driving pnpm dev + Chrome MCP, and only one may be issuing writes against the tenant. Graph write operations against a real tenant are not sandboxed and not reversible by the app. Sequence browser validation at the wiring checkpoint rather than letting each lane reach for it, and keep tenant writes on the root agent’s thread where a human message authorises them.

Read-only Graph exploration is fine concurrently, but Graph applies a per-tenant throttle: parallel lanes hammering fetchAll will produce 429s that look like application bugs.

Ownership and the escape hatch

A lane owns its files and its task. It does not own: the type seam, the token layer, the tenant, git commit, or the decision to widen its own scope.

The escape hatch: when a lane hits a decision its brief does not cover, it stops and returns the question. It does not guess, and it does not quietly narrow the task to the part it can do. A boundary with no escape hatch is a stop condition wearing a safety label. One round trip is cheaper than the rewrite — and in this codebase the expensive class of wrong guess is a Graph endpoint chosen without verifying it responds, which produces working-looking code that fails only against a real tenant.

Run-end against this tracker

Task state is the record.

Finalize in one call so an interrupted run cannot leave finished work looking unfinished:

backlog task edit ASM-0007 --check-ac 1 --check-ac 2 -s Done

The closing terminal message is a covering note — what the run learned that no single task captures — and nothing durable may live only there. Writing it is the last unit of work, not a reply to a request.

View the source file on GitHub