Skip to content

Taaktypes (werkregels en opgeslagen werk): ontwerp + bouwstappen 1–7 — gestapeld op de XER-branch - #101

Open
Nozzit wants to merge 171 commits into
mainfrom
claude/contour-engine-planner-mnrsy3
Open

Nozzit wants to merge 171 commits into
mainfrom
claude/contour-engine-planner-mnrsy3

Conversation

@Nozzit

@Nozzit Nozzit commented Sep 4, 2026

Copy link
Copy Markdown
Collaborator

Mergevolgorde: deze PR bevat een merge van claude/file-formats-support-phase-3-a0ebe2 (XER-import) en moet die PR mergen; daarna bevat de diff alleen nog de taaktypes-etappe. Een B1c-koppelpunt (clearLevelingGaps bij een duur uit de werkdriehoek, één regel in settleDurationAftermath) staat in docs/TODO.md voor de kant die als tweede merget.

What and why

Het ontwerp docs/superpowers/specs/2026-09-04-spec-taaktypes-opgeslagen-werk.md (opvolger van het voorstel van 2026-08-18; MS Project en P6 naast elkaar met bronnen, elke bewering gelabeld zeker/gemeten/afgeleid/beredeneerd/onbekend/besloten; eigenaarsbesluiten 1–10 in §3) en daaruit alle bouwstappen 1 t/m 8 volgens §10:

  • Stap 1 — datamodel, geen gedrag. Task.workRule (FIXED_DURATION_RATE = het gedrag van vandaag | FIXED_DURATION_WORK | FIXED_WORK | FIXED_RATE), Project.defaultWorkRule, drie optionele werkvelden per toewijzing; IFC-round-trip, documentcontract, extensiecontract alleen-lezen, alleen-lezen rasterkolommen, planner_get_task.
  • Stap 2 — importvertaling en export. MSPDI <Type>/<EffortDriven> + Work-velden; P6 XML <DurationType> (labelverwisseling van de twee Fixed-Duration-vormen gecorrigeerd, bron: Oracle XER Data Map Guide) + Units; XER duration_type + target_qty/act_*_qty/remain_qty. "Afwezig ⇒ afgeleid". Fidelity-poort blijft 0.
  • Stap 3 — pure werkdriehoek. src/engine/work/workTriangle.ts op de resterende toestand (W en I opgeslagen, R afgeleid en naar boven afgerond), meetlat work-triangle-cases.json (44 cases over 36 bewerkingen).
  • Stap 4 — bedrading. Brug workRuleApply.ts in taskSlice, resourceSlice (ook moveAssignment/removeResource), gridTransaction.ts en de MCP-tweeling; een duur uit de driehoek zet scheduleStale en loopt door settleDurationAftermath; "vorm blijft, hoogte zakt" voor contouren; assignmentDayUnits krijgt opgeslagen werk als bron.
  • Stap 5 — UI. Instelling Toon taaktypes (ops-showTaskTypes, default uit) óf documentontsluiting (taskTypesVisible, afgeleid bij laden, één melding met gids-link). TaskWorkRuleField in paneel en dialoog, kolom Werk (rest) met slotjes in de toewijzingstabel (commit op Enter/blur), rasterkolommen task.workRule en assignment.remainingWork (alleen wanneer ontsloten). i18n 14 locales, gids gids-taaktypes nl+en.
  • Stap 7 — MCP. Geen nieuwe tool; planner_update_tasks/planner_add_tasks fields.workRule, planner_manage_assignments add/update remainingWorkMinutes, planner_update_project defaultWorkRule; planner_get_project_info toont de projectstandaard.
  • Stap 8 — docs. Spec-status per stap, docs/superpowers/README.md, wiki-featurelijst, CLAUDE.md.
  • Twee eigenaarsbesluiten (2026-09-05, commit fae46f3e). (K2) Een kalenderwissel verandert de slotgrootte en loopt daarna door de werkregel: onder Vast werk/Vaste inzet wordt de taak langer bij minder uren per dag, onder Vaste duur en werk stijgt de inzet, onder de standaardregel blijft alles zoals vandaag; een project-/kalenderwijziging die duren verandert meldt hoeveel (meetlat 32–34). (Δ-rest) Een duurbewerking op een taak met expliciete restduur schuift die rest mee (geklemd op 0), naar Microsofts identiteit Remaining Duration = Duration − Actual Duration.
  • Derde reviewronde op die twee besluiten (f72a964e). Kalender én duur in één rasterpaste of één MCP-call verloren de duur resp. het verrichte deel (F1/F2: de kalenderstap is overal een eigen stap vóór de rest); de contour-as werd tegen de nieuwe in plaats van de oude slot herschaald (F3: captureCalendarChange/settleCalendarChange als één definitie voor zes aanroepers, inclusief herschaling wanneer de dagen niet veranderen); FIXED_RATE legde bij een slotwissel een werkveld vast (F5); 8-B en de reikwijdte van de Δ-regel gedocumenteerd (F6/F7); restUntouched-poort in de MCP-tweeling (F8); bungelende taakkalender volgt de projectkalender (F9); melding zonder dedupe-badge (F10); meetlat 35/36 (F11).
  • Vierde reviewronde op die fix (d3bfc956). De as-herschaling schaalde de contourhoogte onder de standaardregel dubbel (histogram 25 % te laag) en liet onder FIXED_RATE contour en toewijzing uiteenlopen (G1/G2: de hoogte wordt nu tegen het werkelijke werk per toewijzing verzoend, niet tegen een regelvlag); een state.assignments.filter in de plakloop kostte 746 → 129 ms bij 1000 × 1000 (G3); updateCalendar/setProjectCalendar meldden een gewist Z8-venster niet (G4); werkdriehoek en contourreferentie lezen nu de effectieve uren per dag zoals het raster (G5); docbloks en spec bijgewerkt, drie niet-bedrade randpaden in docs/TODO.md (G6/G7/G9/G10).
  • Eigenaarsbesluit F4, optie a (2026-09-06, c5fca94a). Zodra de brug de rest expliciet schrijft (Δ-regel of kalenderwissel op een gestarte taak) volgt completion daaruit als 1 − rest ÷ duur (syncCompletionToRemaining, dezelfde formule als een restbewerking in het raster), zodat Gantt-balk, solver en rapportage één waarheid delen. Voorbeeld: 10 d op 50 % → 6 u/dag onder Vast werk geeft 12 d, rest 7, 41,7 %; terug naar 8 u/dag weer 10 d, rest 5, 50 %. Let op: elke voortgangsinvoer en elke import schrijft remainingTime expliciet (applyProgressInvariants, importNormalize), dus een duurbewerking op vrijwel elke gestarte taak laat het verrichte deel staan en verandert het percentage (spec §6.5).

Vier onafhankelijke reviewrondes (bedrading; UI+MCP; K2/Δ-rest; de fix daarop), elk in een eigen fix-commit verwerkt (7d4f2bfc, 4016ebe8, f72a964e, d3bfc956).

Onder de standaardregel zonder werkvelden en met de instelling uit is de app byte-identiek aan vandaag, met één uitzondering sinds de besluiten van 2026-09-05/06: een duurbewerking op een gestarte taak houdt het verrichte deel vast en herrekent het percentage. Open punten (spec §12 / TODO): MS Project-meting van de meetlat (incl. 32–36 en de Δ-regel), K2 op drie randpaden (G9), per-toewijzing-spanne, projectstandaard in de UI, crashherstel zonder melding.

Buiten de etappe: check-milestone-duration-render.ts klikt op de getekende balk in plaats van een vaste x (weekend-flake).

How it was verified

  • npm run verify groen op de eindboom vóór commit fae46f3e (één browserspec, just-updated-dialog.spec.ts, faalt lokaal alleen op ERR_CERT_AUTHORITY_INVALID van de sandbox-proxy en raakt deze etappe niet); daarna per commit: typecheck, lint, verify:cycles, verify:store-boundaries, verify:gantt-boundaries, verify:i18n, verify:docs, planningssuite 560/560 + tijdzone-matrix, bibliotheek- en MCP-suite (40/40); browserspecs work-rule.spec.ts (3/3) + contour-dialog.spec.ts. Nieuwe checks: check-work-triangle (370), check-work-rule-mapping (37), check-work-rule-store (174), tests/mcp/cases-work-rule.ts (12).

Does this touch

  • Project data — ja: nieuwe optionele velden, round-trip via IFC (docs/ifc-round-trip.md-route gevolgd, canon + fixtures uitgebreid); taskTypesVisible is bewust sessie-afgeleid (rol none).
  • Scheduling logic — indirect: geen solverwijziging; de werkdriehoek verandert duur/inzet vóór de solve en markeert de planning verouderd.
  • User-visible text — 14 locales; MCP-toolbeschrijvingen.
  • @tauri-apps/* — nee.

Documentation

Spec (status en verwerkte reviewbevindingen per stap in §10; §6.4/§6.5 herzien voor de besluiten en de reviewrondes daarop), docs/superpowers/README.md, docs/TODO.md, CLAUDE.md-alinea bij de contour-engine, in-app gids gids-taaktypes (nl+en; 12 vertalingen volgen maandelijks), docs/wiki/Features.md.

🤖 Generated with Claude Code

https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA

Nozzit and others added 30 commits August 20, 2026 10:03
…ariteit/uurmodus als kernbesluit, SCHEDOPTIONS-defaults-programma, whitelist-invoerregel, projectselectie herzien, X-O5 getalnotatie, mutatiebewijzen per taak
…ti-project-import, baselines verplicht bewaard, float-vangrails bekrachtigd
…ijk (Hotel-paar via schema-vingerafdruk), X-O1/X-O2 doorvertaald (X4a/X4b-knip, per-project-meting, lege-projectregel, baseline-begrenzing), defaults op de meting, generieke enum-regel
…e-eis op drie plekken (docblok, 14-talige foutmelding, gidsparagraaf)
…ECT-rij terug in X4a, enum-regel gesplitst naar §4.7, X4b→X10-pijl, stale zinnen gelijkgetrokken
XER-etappe, taak X0 (SERIEEL VOORAF). Geen lezer, geen registry-entry — alleen
typecontract + corpustooling, conform de X0-brief en plan §4.1/§6.

- src/types/task.ts: P6DurationType/P6ActivityType + Task.p6DurationType/
  p6ActivityType/p6SuspendResume (veld-als-signaal naast mspTaskType, met de
  vastgelegde superset-afspraak voor de latere taaktypes-etappe; suspend/
  resume-herkomstvlag is een stub, X7 vult hem).
- Doorgevoerd op alle compile-gedekte plekken die anders stil hadden kunnen
  verouderen: extTypes.ts/extMappers.ts (Ext-contract, leeskant-alleen zoals
  mspTaskType/effortDriven), check-ext-contract.ts (sleutellijsten),
  moveProject.ts (TASK_VERDICTS, 'n/a'), check-ifc-roundtrip.ts (Required<Task>-
  getuige + TASK_CANON skip-cellen, IFC-wiring is X9), taskFields.ts
  (REJECT_HINTS, MCP-zetbaarheid).
- tests/planning/check-xer-field-whitelist.ts: de drie §4.1-bakken (whitelist/
  verboden/genegeerd) als getypeerde constanten + een eigen, onafhankelijke
  %T/%F-corpusscan (OPS_XER_CORPUS) die de union van alle TASK-kolommen tegen
  de bakken toetst — het gatenkaas-mechanisme. Skip-OK zonder corpus, geen
  CI-poort (zelfde conventie als OPS_MPP_CORPUS).
- tests/planning/xerFidelityTypes.ts + xer-fidelity-baseline.json +
  check-xer-fidelity-baseline-schema.ts: het harness-skelet — de §3-baselinevorm
  (zes assen, deviations/measurable) vastgelegd via een compile-locked
  sleutellijst + runtime-structuurvalidator, met een lege-lezer-run
  (createEmptyXerFidelityBaseline) die de committe lege baseline produceert.
  De dedup-logica zelf (§2) is X1-werk.
- run.sh: beide nieuwe checks bedraad naast check-mpp-fidelity.

Mutatiebewijzen (uitgevoerd, teruggedraaid): whitelist-veld schrappen ⇒
corpusscan ROOD; baseline-vorm muteren (JSON) ⇒ schema-check ROOD; veld uit
XerFidelityBaselineEntry schrappen ⇒ compile-fout.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Nozzit and others added 17 commits September 5, 2026 08:35
Her-review 2 (P2) liet zien dat een pagineersessie stil records van het verkeerde project
combineert: page 1 opvragen op document A, met switchDocument naar document B wisselen,
en page 2 levert dan gewoon een (mogelijk lege) pagina van B terug zonder enige
waarschuwing — een extensie die een lege pagina als "klaar" leest heeft in werkelijkheid
twee projecten door elkaar gehaald. Er was geen driftanker zoals het bestaande
expectedDocId in de MCP-laag.

getImportSourceCatalogPage(collection, options) accepteert nu een optionele
options.expectedSourceProjectId. Wijkt de bronselector van het ACTIEVE document af — ook
als dat document inmiddels helemaal geen XER-bron meer heeft — dan gooit de aanroep een
nieuwe ExtImportSourceDriftError in plaats van stil door te gaan. De check zit zowel in de
pure getExtImportSourceCatalogPage (archief aanwezig, ander project) als in de
extensionApi-wrapper (archief helemaal weg na een documentwissel), want de tweede route
riep de eerste anders nooit aan.

Meegenomen kleine P3-fixes uit dezelfde her-review: offset -0 canoniseert nu naar +0
(resolvePageOffset), en docs/extensions.md + docs/wiki/Extensions-Authoring.md noemen nu
expliciet dat deze bronroute apiVersion >= 1.1 vergt.

Mutatiebewijs: de assertNoImportSourceDrift-aanroep weghalen uit zowel
getExtImportSourceCatalogPage als de extensionApi-wrapper laat de nieuwe D1/D2-checks in
check-ext-contract.ts rood lopen (256 → 2 rood: "kreeg isDriftError:false" i.p.v. de
verwachte fout), exit 1; D1a/D3 (het bestaande, ongewijzigde gedrag zonder de optie)
blijven daarbij groen.

(cherry picked from commit 0816d57)
…flake)

check-milestone-duration-render.ts liet project.startDate en view.viewStartDate
allebei op de "vandaag"-default staan. Op zaterdag/zondag schoof de solver de
basistaak naar de eerstvolgende werkdag terwijl het view-origin op het
weekend bleef staan — de balk verschoof pixels naar rechts, terwijl de
hittest een vaste x = TTW + 20 gebruikt. Anker nu beide op dezelfde vaste,
altijd-werkende maandag (2026-01-05, in de stijl van check-bar-colors.ts),
zodat de fixture niet meer van de kalenderdag van de testrun afhangt.

Mutatiebewijs: met een gefakete Date ("vandaag" = za 2026-01-10) faalt de
ONGEWIJZIGDE check exact zoals gerapporteerd (getTaskBarBounds → null); met
de fix erin blijft hij groen onder diezelfde gefakete zaterdag én onder de
echte datum van vandaag.

(cherry picked from commit a133ae6)
Reviewbevindingen uit review-mcp-read.md (P1, twee stuks):

- planner_inspect_xer_provenance stond op batchable:true. Een planner_batch met
  alleen deze tool liep zo via de mutatie-executor: altijd een transactie, altijd
  runCPM/recomputeViewRows/recomputeResourceLoad — in strijd met de eigen
  "voert geen CPM uit"-belofte. Nu batchable:false, zodat checkExclusions
  (batchTool.ts) de stap al vóór enige transactie met VALIDATION afwijst.

- resourceCatalog/metadataCatalog/taskSourceRowsByProject gaven ruwe XER-cellen
  (namen, notities, willekeurige kolommen) zonder enige opt-in en zonder limiet
  per cel/rij/respons terug. Nieuw: `includeRawRows` (alleen geldig bij die drie
  sections). Zonder opt-in levert elke rij alleen `line`+`fieldCount`, geen
  cellen. Met opt-in: per-cel afkapping op 2.000 tekens, een verlaagde
  paginalimiet (100 i.p.v. 1000, zoals rawSource al deed met 8 chunks) en een
  responsgrens van 256 kB die met een pagineerhint faalt.

Tests in cases-xer-provenance.ts uitgebreid: batch-uitsluiting (geldige én
ongeldige args), default-projectie zonder cellen, afkapping bij opt-in, en een
losse responsgrens-fixture (100 rijen × 3 grote cellen) die de 256 kB-grens
bewust raakt. Alle vier nieuwe gedragingen zijn mutatiebewezen (fix uit ⇒ check
rood): batchable terug naar true, projectSourceRow zonder gating, en
finalizeBounded weggehaald gaven elk de verwachte faalregel.

(cherry picked from commit 4ab7c9e)
Reviewbevinding P2 (review-mcp-read.md #3): een ongeldige batch-stap gaf
terecht VALIDATION, maar de review zag isDirty toch naar true kantelen omdat
restoreSnapshot() (snapshot.ts) — voor de gewone undo/redo-tegenhanger — die
vlag altijd op true zet.

Op de huidige basis (na de store-contextrefactor) is dit al opgelost:
createMcpTransactions.ts's run() herstelt op het rollbackpad expliciet
`state.isDirty = previousDirty` NA restoreSnapshot. Dit is dus een algemene
batcheigenschap, geen zaak van de XER-tool zelf — de nieuwe test pint 'm met
een stub-tool met een strikt schema (additionalProperties:false + required),
zodat een toekomstige regressie in die rollback-volgorde hier meteen rood
wordt. Mutatiebewezen: de previousDirty-restore-regel tijdelijk verwijderd uit
createMcpTransactions.ts liet precies deze test falen (isDirty false → true).

(cherry picked from commit 21c4509)
Reviewbevinding P2 (review-mcp-read.md #4): CLAUDE.md beweerde nog "39
planner_*-tools" terwijl deze etappe planner_inspect_xer_provenance toevoegt
(40, bevestigd door scripts/verify-docs.ts Poort 7e en npm run verify:docs).

(cherry picked from commit 41180ff)
Herbeoordeling (review2-3d.md) op de vorige fix: die dichtte precies de drie
paden die het eerste rapport noemde, maar de EIGENSCHAP ("elke string uit een
bronrij gaat door één afkap-/opt-in-poort") stond nog los. Twee gaten eronder:

- [P1 #1] `diagnostics/documentViews` had géén `includeRawRows`-gating, géén
  projectie en géén responsgrens — `resources.assignments[].rawRow` gaf ruwe
  XER-cellen (bevestigd met een ECHTE `readXER`-fixture, geen stub: 50.000
  tekens vrije tekst kwam onafgekapt terug).
- [P1 #2] De gate hing aan `rawRow.cells`; vrije tekst BUITEN `rawRow`
  (`taskProjections.notes`, `roleSources.description`, `customFields`-waarden)
  ging ongefilterd mee, ook al heetten de collecties "genormaliseerd".

Fix: één generieke poort (`sanitizeProvenanceValue`) die recursief over een
sectierespons loopt. Een `{line,cells}`-vorm wordt STRUCTUREEL herkend
(werkt dus ook op plekken zonder eigen allowlist-regel, zoals geneste
documentViews-assignments). Een string onder een vrije-tekstsleutel
(`notes`/`description`/`customFields`) is zonder `includeRawRows` volledig
onzichtbaar, met opt-in afgekapt op 2.000 tekens. Elke andere string (namen,
labels, ids) blijft altijd zichtbaar maar hard afgekapt op 200 tekens — nooit
meer "genormaliseerd" gelijk aan "onbegrensd".

Twee kleinere P2/P3-bevindingen uit dezelfde ronde in dezelfde beweging
meegenomen:

- [P2 #3/#6] `finalizeBounded` nu ook om `diagnostics`/`importReport`; een
  `ByteBudget` charged tijdens de projectie en breekt af zodra het budget vol
  is (vóór de dure volledige serialisatie), plus een harde cap van 200
  cellen/velden per rij (de `%F`-kolomkop van het bronbestand bepaalde dat
  aantal voorheen ongebonden).
- [P2 #4] de responsgrens meet nu ECHTE UTF-8-bytes (`TextEncoder`) i.p.v.
  `String.length` (UTF-16-code-units) — voor CJK/Arabisch/emoji zat daar een
  factor 2–3 tussen.
- [P2 #5] `projectIds()` is nu de unie van `documentViews` en
  `taskSourceRowsByProject`: een project dat alleen TASK-rijen heeft (leeg of
  baseline-uitgesloten) werd door `summary` geadverteerd maar door de tool
  zelf met NOT_FOUND geweigerd.
- [P3 #7] `truncateAt` knipt nooit meer een surrogaatpaar door de helft.
- [P3 #8] een nieuwe test loopt via de ECHTE dispatch-weg (`handleMcpMessage`)
  i.p.v. uitsluitend `def.handler`.

Alle nieuwe/aangepaste tests in `tests/mcp/cases-xer-provenance.ts` zijn
mutatiebewezen (fix uit ⇒ exact de bijbehorende test rood): structurele
rawRow-detectie uit ⇒ 7 tests rood; vrije-tekstsleutels leeg ⇒ 2 tests rood;
cap+budget+finalizeBounded alledrie uit ⇒ 3 tests rood (los uitgezet blijven
de andere twee lagen het nog opvangen — bewuste defense-in-depth); bytes
terug naar code-units ⇒ 1 test rood; selectorbron terug naar alleen
documentViews ⇒ 2 tests rood.

(cherry picked from commit c3c8487)
…ixes

Her-check (review2-3d.md, ronde 3) op de vorige commit: de generieke rijdetectie
werkte, maar de vrije-tekstclassificatie was zelf nog een BLOCKLIST
(`notes`/`note`/`description`) — dezelfde soort blinde vlek als de drie-paden-
fout van ronde 1, alleen een niveau dieper. Vijf bevindingen, alle bevestigd
met probes door de reviewer:

- [P1 N1/N2] Blocklist omgekeerd naar DENY-BY-DEFAULT: `SAFE_LABEL_KEYS` is nu
  een allowlist van id-/code-/labelachtige sleutels die zonder opt-in zichtbaar
  blijven (afgekapt op 200 tekens). Alles wat er niet in staat — ook een
  onbekende toekomstige sleutel — is zonder `includeRawRows` volledig
  onzichtbaar. Dit dekt automatisch ook geneste vrije tekst: `Task['notes']`
  is een objectarray (`{id,text,done}[]`), geen string, en de oude blocklist
  keek alleen naar de buitenste sleutel `notes`, niet naar `text` binnenin.
  Zes alias-sleutels (`text`/`comment`/`memo`/`remark`/`title`/`longName`) die
  vorige ronde allemaal doorglipten, verdwijnen nu net als elke andere
  niet-toegestane sleutel — geen aparte regel per synoniem nodig.
- [P2 N3] `summary` (de DEFAULT-sectie) liep door geen van beide poorten:
  911.534 bytes gemeten, `numberFormat.currencyCode` onafgekapt (5.014
  tekens), en `importReport` was een levende alias naar het bevroren archief
  — het kopcommentaar belooft expliciet "nooit een alias". `summary` loopt nu
  door dezelfde `sanitizeProvenanceValue` + `finalizeBounded` (met
  `includeRawRows` hard op `false`, want summary zat nooit in
  `RAW_ROWS_SECTIONS`).
- [P2 N4] Cel-/veldNAMEN (de `%F`-kolomkop van het bronbestand) ontsnapten aan
  zowel afkapping als budget — een kolomnaam van 60.001 tekens kwam verbatim
  mee en telde als nul in de teller. Nu lopen sleutels door dezelfde
  `truncateLabel` + `budget.charge` als waarden, met botsingsveilige
  suffixering (`#1`, `#2`, …) na afkapping.
- [P2 N5] `isRawSourceRowLike` eiste exact twee sleutels en faalde daardoor
  OPEN zodra een rijvorm een derde veld droeg (`XerScheduleOptionsSourceRow`
  = `{table,line,cells}`): zo'n rij viel door naar de generieke objecttak,
  waar `cells` gewoon een record werd en elke celwaarde als zichtbaar label
  naar buiten kwam. Detectie is nu op VORM (`line`+string-`cells`), fail-
  closed; overige velden (`table`) projecteren gewoon mee via dezelfde poort.
- [P3 M4] Het `ByteBudget`-effect (begroten vóór serialisatie) was door geen
  enkele test gepind: de reviewer schakelde alleen de budgetdrempel uit en
  geen enkele test werd rood, omdat `finalizeBounded` dezelfde oversized
  respons toch nog post-hoc afving. Nieuwe test pint het VIA de foutmelding:
  `createByteBudget` mist het woord "geserialiseerd" dat `finalizeBounded`
  wél toevoegt, dus een assertie op die tekst onderscheidt "geweigerd tijdens
  projectie" van "geweigerd na volledige serialisatie" — zonder op tijd of
  geheugen te meten.

Tool-description bijgewerkt naar wat de code nu daadwerkelijk garandeert
(deny-by-default, summary inbegrepen, cel-/veldnamen afgekapt).

Alle vijf fixes zijn mutatiebewezen (fix uit ⇒ exact de bijbehorende
test(s) rood): allowlist naar "alles toegestaan" ⇒ 2 tests rood; summary
buiten de poort ⇒ 2 tests rood; kolomnaam-afkapping uit ⇒ 1 test rood;
kolomnaam-begroting uit (afkapping blijft aan) ⇒ 1 test rood; rijdetectie
terug naar exact-2-sleutels ⇒ 1 test rood; ByteBudget-drempel uit ⇒ zowel de
nieuwe M4-pinningtest als de N4-budgettest rood (bevestigt exact het gat dat
de review aanwees: de oude suite zag deze mutatie niet).

(cherry picked from commit 7bc7130)
…ak door poort

Her-check 3 (review2-3d.md): GO onder twee kleine voorwaarden.

- [P2 R8] De generieke objecttak van sanitizeProvenanceValue deed
  `out[childKey] = …` met `childKey` ongemoeid — N4 dichtte dit alleen voor
  projectSourceRow/projectFreeTextMap. Een project-id van 80.000 tekens
  (proj_id komt uit het bronbestand) overleefde daardoor verbatim zodra hij
  als OBJECTSLEUTEL werd gebruikt (summary.catalogCounts.taskSourceRowsByProject),
  terwijl dezelfde string als WAARDE (selector.availableProjectIds) al wél
  afgekapt werd. Nu lopen sleutels door dezelfde truncateLabel + budget.charge
  als waarden, met dezelfde botsingsveilige #N-suffixering.
- [P3 R9] De sourcePresent:false-tak van summary retournede vóór sanitize/
  finalizeBounded, met het commentaar "statische, systeemeigen tekst" — maar
  xerSourceProjectId is een documentveld uit het bestand, geen statische
  tekst. Nu loopt ook deze tak door de poort; alleen de echt statische
  systeemmelding (note) wordt na het saneren teruggezet, want die sleutel
  staat bewust niet op de allowlist.
- [P3 R7, optioneel] Een volledig verborgen string-array werd `[null, null]`
  in plaats van iets dat "hier is iets verborgen" communiceert — de array-tak
  filtert nu undefined-elementen eruit.

Allowlist-docblok aangevuld: `name`/`code` blijven zichtbaar vastgelegd als
geaccepteerd risico (een P6-resourcenaam is vaak een persoonsnaam — geen
AVG-schone lijst), `unit`/`currShortName` verwijderd (kwamen nergens in de
blootgestelde grafiek voor), `unitOfMeasure` gemotiveerd als wél live.

Mutatiebewezen: R8 teruggedraaid ⇒ de nieuwe R8-test rood (sleutel weer
80.000 tekens); R9 teruggedraaid ⇒ de nieuwe R9-test rood (currentProjectId
weer 9.009 tekens in plaats van 200).

(cherry picked from commit 63e068c)
… (7b-1/7b-2)

Etappe 7b-1 en 7b-2 landen bewust SAMEN: elk afzonderlijk maakt het corpus
meetbaar slechter (zie de meting onderaan).

OORZAAK (7b-1). De `Exceptions`-lijst van kalender 842 (`R111 - 1`) in
`crawl-xer-extra/jailaff-xer-splitter/rehab-2.xer` slaat aaneengesloten vrije
blokken op met een klem op de MA-VR-as: elke zaterdag staat er als de vrijdag
ervóór, elke zondag als de maandag erná. Blok 2008-09-29 … 2008-10-09 staat er
letterlijk als `39720 39721 39722 39723 39724 39724 39727 39727 39728 39729
39730` — 10-04 (za) en 10-05 (zo) ontbreken, 10-03 (vr) en 10-06 (ma) staan er
twee keer. Zo'n AANGRENZEND DUPLICAAT is het bewijs dat er een dag verloren
ging, en komt in het hele corpus van 93 bestanden in geen enkel ander bestand
voor. Alle 23 redundante records van die kalender staan op een vrijdag of een
maandag (nul op een andere weekdag), en de tien dagen die de set-cover van het
761-diagnosedossier onafhankelijk als "moeten vrij zijn" aanwees (2008-10-04,
10-05, 2008-12-07, 12-13, 12-14, 2009-09-19, 09-20, 2009-11-28, 11-29,
2009-12-05) zijn exact de klemdoelen van tien van die 23 records — één-op-één,
geen rest.

OORZAAK (7b-2). Voor precies diezelfde records maakte de decoder een
`p6NonWorkPenaltyDate`: een virtuele extra niet-werkdag die
`CalendarEngine.subtractP6XerProjectedWorkMinutes` bij elke achterwaartse
duur-/lagwandeling opnieuw meetelt. Reconstrueren we de dag écht, dan zou
dezelfde afwezigheid twee keer verrekend worden. Gemeten op rehab-2 met de
blokken vrij: `addWorkMinutes(2009-11-25T08:00, 56u)` = 2009-12-12T17:00 en
`subtractWorkMinutes` daarvan = 2009-11-25T08:00 (beide exact P6's LF/LS),
terwijl de projectie 2009-11-22T08:00 gaf — drie werkdagen te vroeg, exact de
drie penaltydagen 2009-11-27, 11-30 en 12-04 in dat venster.

WIJZIGING. `xerCalendarData.ts` reconstrueert de geklemde weekenddag
(`weekendClampTarget`) en laat voor die datum géén penaltydatum meer achter. De
poort is drievoudig: de kalender moet zelf een aangrenzend duplicaat dragen, het
klemdoel moet een zaterdag of zondag zijn die op déze kalender WERKT, en die dag
mag niet al een eigen uitzonderingsrecord hebben. Corpusmeting van die poort:
92 van de 93 bestanden zijn byte-identiek; alleen rehab-2 verandert. De
projectie zelf blijft staan — een generieke "sla een al vrije dag over"-wacht
zou haar de facto schrappen en dat is gemeten slechter (zie hieronder); ze is
voor twaalf andere corpusbestanden nog steeds het enige model.

METING — X12-productfidelity (`OPS_XER_FIDELITY_REPORT`, per bestand):

  totaal                       18398 → 17421  (−977)
  rehab-2.xer                  14812 → 13835  (−977)
  alle 28 overige bestanden    ongewijzigd, 0 verschil

  per as:  es 1251 → 1573 (+322)   ef 1500 → 1627 (+127)
           ls 5015 → 4546 (−469)   lf 5044 → 4583 (−461)
           tf 4765 → 4468 (−297)   ff  823 →  624 (−199)

  De twee helften afzonderlijk (mutatiebewijs op corpusniveau):
    alleen reconstructie, penalty blijft   18398 → 21673
    alleen penalty weg, geen reconstructie 18398 → 21588
    beide samen                            18398 → 17421

Corpusloos mutatiebewijs: `check-xer-calendar-data.ts` sectie 22 draait de
letterlijke recordreeks van blok 2009-11-26 … 2009-12-05 en eist tien
aaneengesloten vrije dagen plus een lege penaltylijst; zonder deze fix zeven
dagen en drie penaltydatums (exit 1). Twee controles erbij: zonder duplicaat
gebeurt er niets, en op een gewone ma-vr-kalender ook niet.

Poorten: `OPS_XER_CORPUS=… npm run test:planning` exit 1 met uitsluitend de
toegestane rode set — de vier X12-regels (totaal nu 17421 i.p.v. 18398) — plus
`milestone-duration-render`, dat op de basiscommit a669108 al rood staat en
niets met deze etappe te maken heeft. `npm run typecheck` exit 0,
`npm run lint` exit 0. Drie gepinde baselines meebewogen en opnieuw gemeten:
de 124-kalenderdigest, de SCHEDOPTIONS-blast-radius (expectedFinishVariant en
causalProductEffects) en de openbare task-replaypin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit fbcdb73)
…erkt na review)

Herwerking van `fbcdb731` na de hyperkritische review (`review-7b.md`, NIET LANDEN).
Alle zeven punten afgewerkt, elk met eigen meting.

1. POORT PER RECORD (was: per kalender). `adjacentDuplicateDates.size > 0` schakelde
   de hele kalender om; de reviewer bewees daarmee twee false positives. De poort zit
   nu op het record zelf, in twee benoemde bewijsvormen (`hasWeekendClampEvidence`):
     * SPRONG — het record staat op een vrijdag én er is een uitzondering op de maandag
       erná (of andersom), met een werkend, niet-opgeslagen weekend ertussen;
     * DUPLICAAT MÉT BLOKVERVOLG — aangrenzend duplicaat én het blok loopt aan de
       binnenzijde door.
   Fidelity ongewijzigd: rehab-2 blijft 13.835 (gemeten met een losse per-bestandprobe
   op alle zeven gate-varianten; kalenderbreed 13.835, alleen-duplicaat 15.195,
   alleen-sprong 15.866, sprong+duplicaat 13.835).
   Fixtures 22c/22d in `check-xer-calendar-data.ts` zijn exact de twee false positives;
   tegen de kalenderbrede decoder gaan ze rood (2 afwijkingen, exit 1).

2. TWEE BLOKKEN ZONDER OPENINGSDUPLICAAT verantwoord. 2006-12-29 en 2009-09-18 dragen
   geen duplicaat; ze worden door de SPRONG-vorm gedekt, niet meer impliciet door een
   kalenderbrede vlag. Fixture 22b pint dat (2009-09-19 is een van de tien set-cover-dagen).

3. REIKWIJDTE eerlijk. Niet "23 records op één kalender": rehab-2 deelt zijn `clndr_data`
   44× (groep van kalender 842) en 75× (groep van 896), dus 119 van de 124 kalenders
   krijgen elk dezelfde 23 dagen — 2.737 dagvermeldingen. Taken staan alleen op de
   842-groep (6.548 van de 6.977) en op kalender 893 (429); de 896-groep is takenloos.

4. BRONTOETS op de reconstructie (het antwoord op de ES/EF-vraag). Voor de 6.548 taken op
   de 842-groep zet P6 in álle negen blokken — inclusief de gereconstrueerde weekenddagen —
   NUL ES, EF, LS en LF, terwijl hij in de drie dagen direct vóór en ná de vier blokken die
   in de projectperiode vallen 125 (okt-2008), 141 (dec-2008), 64 (sep-2009) en 78
   (nov-2009) datums zet. De reconstructie is dus door de bron bevestigd. De 14 EF's op
   2008-12-14 die op het eerste gezicht het tegendeel leken te bewijzen horen bij kalender
   893 `R111 - 2`, een 7-daagse kalender zonder uitzonderingen die hier niet geraakt wordt.

5. BLOKABLATIE gedraaid (Plan §4.5), per as, op rehab-2:
     basis                14.812  es 618  ef 813  ls 4358 lf 4350 tf 4200 ff 473
     alle negen blokken   13.835  es 940  ef 940  ls 3889 lf 3889 tf 3903 ff 274
     zonder dec-2008      13.314  es 620  ef 684  ls 3951 lf 3948 tf 3785 ff 326
     zonder okt-2008      13.394  es 741  ef 785  ls 3890 lf 3890 tf 3753 ff 335
     zonder sep-2009      14.672   zonder nov-2009 14.171   zonder okt+dec 13.451
   Per LOSSE DAG weglaten is telkens fors slechter (14.5k–15.2k): een half gereconstrueerd
   blok geeft datums die noch die van P6 noch de fysieke zijn. De vijf blokken buiten de
   projectperiode (13 dagen) hebben exact nul effect.
   KEUZE: alle negen blokken. `zonder dec-2008` scoort 521 cellen beter en zou de
   ES-regressie wegnemen, maar punt 4 laat zien dat P6 juist rond dát blok 141 datums zet
   en er géén ín — het blok vrij maken is brongetrouw. Een kalender bewust fout zetten om
   celtellingen te kopen is geen model.

6. ES/EF-REGRESSIE als open residu benoemd, niet weggepoetst: 344 nieuw kapotte ES- en
   327 nieuw kapotte EF-cellen (es 618→940, ef 813→940), 343 daarvan op de 842-groep,
   281× één dag en 56× twee dagen te LAAT. Het is een forward-ANKER-gat, geen kalendergat:
   voor `V3109400` telt het venster van P6 (2008-12-04 → 12-24) op de gereconstrueerde
   kalender exact zeven werkdagen, en ons venster (12-06 → 12-25) óók — de duurwandeling
   klopt dus, alleen het startanker ligt één werkdag later. De vijf getraceerde taken uit de
   review (V3109400, V3109300, V3109420, V3109220, V3109480) zijn het startpunt voor die
   vervolgetappe.

7. P6-XER-PENALTYPROJECTIE VERWIJDERD. Gemeten per bestand, met en zonder de projectie, op
   alle zes penaltydragende corpusprojecten: rehab-2 13.835/13.835, Hotel Project 308/308,
   Hotel_Construction_TEC 308/308, Roads_Project_TEC 1006/1006, p6_torture_test_v1 0/0,
   Harbour Point As-Built 0/0, Harbour Point Baseline 93/93 — byte-identiek op alle zes de
   assen. Ze verklaart nergens meer één cel. `subtractP6XerProjectedWorkMinutes`,
   `p6XerProjectedWorkMinutesBetween` en hun solveraanroepen zijn weg; `CalendarEngine` kent
   daarmee geen brongebonden asymmetrie meer en `check-calendar-mirror.ts` deel C pint dat
   nu andersom. `WorkCalendar.p6NonWorkPenaltyDates` blijft als BRONDIAGNOSE bestaan (round-
   trip door het IFC); geen solverpad leest het veld nog. De eerdere claim "voor twaalf andere
   corpusbestanden nog steeds het enige model" was ongemeten en is hiermee ingetrokken; de
   18.398 → 21.588 uit `fbcdb731` mat de schrapping van rehab-2's eigen klemdatums, niet een
   generieke schrapping.

X12-PRODUCTFIDELITY, per bestand (`OPS_XER_FIDELITY_REPORT=baseline`):
  totaal                    18.398 → 17.421 (−977)
  rehab-2.xer               14.812 → 13.835 (−977)
  alle 28 overige bestanden ongewijzigd, 0 verschil
  per as: es +322, ef +127, ls −469, lf −461, tf −297, ff −199

BASELINES. `xer-schedoptions-blast-radius.json` staat weer in zijn oorspronkelijke opmaak
(diff t.o.v. 7a3dece: 79/16 regels i.p.v. 311). Er bewegen 47 bladwaarden, allemaal
`deviations`-tellers in de tegenvariant-/causale takken: 45 omlaag (grootste
`completedProgress.rows.14` 7439 → 5248, `expectedFinishVariant.fidelity.*.ls` 8356 → 6176,
`.ef` 9121 → 7002) en 2 omhoog — en die twee zijn exact de ES/EF-assen van de
`xerDefaults`-tak (1062 → 1384 en 1287 → 1414), dezelfde +322/+127 als hierboven. De
124-kalenderdigest en de openbare task-replaypin bewegen mee met de nieuwe kalender.

GEEN OPMAAKRUIS: `git diff -w` is regel-voor-regel gelijk aan `git diff` (de CRLF-flip die
`7a3dece6` in `CalendarEngine.ts` meebracht is hier niet herhaald).

POORTEN. `OPS_XER_CORPUS=… npm run test:planning` exit 1 met uitsluitend: de drie
X12-nuldoelregels (totaal nu 17.421), de X12-regel die de v1-overgangsbaseline met de
v2-meting vergelijkt (al rood vóór deze etappe, ander schema), en
`milestone-duration-render`, dat op basiscommit a669108 al rood staat. 560/560 CPM-cases
groen; `mpp-fidelity` groen (2196 checks, 216 bestanden, netto ongewijzigd); X12 ZONDER
corpus groen (74 corpusloze checks), dus CI en deploy blijven groen. `npm run typecheck`
exit 0, `npm run lint` exit 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit 1d1bc96)
…ring vastgelegd

Her-check-ronde op `1d1bc96d` (verdict GO-MET-DOSSIER-7b-4). Vijf voorwaarden afgewerkt.

1. RESTLEK GEDICHT. De reviewer vond twee nieuwe false positives op de record-lokale poort:
   D — een za-do-kalender met twee LOSSE feestdagen die toevallig drie dagen uit elkaar
   liggen (vr 2009-12-04 en ma 12-07) maakte via de sprongvorm zaterdag 12-05 half vrij;
   F — een 7-daagse kalender met een tweedaags blok (do 12-03 + vr 12-04) waarvan de vrijdag
   per ongeluk dubbel stond, maakte zaterdag 12-05 vrij.
   `hasWeekendClampEvidence` eist nu drie dingen, alle drie lokaal:
     (a) MEERDAAGS BLOK — de reeks rond het record telt minstens drie losse
         uitzonderingsdatums (een gat van hoogstens drie dagen breekt de reeks niet, dat gat
         ís het geklemde weekend). Sluit D én F. In rehab-2 tellen alle negen blokken 5–11.
     (b) BLOKVERVOLG AAN DE VAN-HET-WEEKEND-AFGEKEERDE ZIJDE — de dag vóór een vrijdag of ná
         een maandag draagt zelf ook een uitzondering; bij de sprongvorm volstaat dat één van
         de twee sprongpartners dat doet (dat dekt de twee blokken die met een vrijdag zonder
         voorganger openen: 2006-12-29 via ma 2007-01-01, 2009-09-18 via ma 2009-09-21).
     (c) een van de twee bewijsvormen: de sprong, of een aangrenzend duplicaat.
   Fixtures 22f (D) en 22g (F) zijn toegevoegd; tegen de decoder van `1d1bc96d` gaan ze beide
   rood met exact de gemelde extra zaterdag (2 afwijkingen, exit 1).
   De verscherping kost NUL cellen: rehab-2 blijft 13.835 op alle zes de assen (es 940,
   ef 940, ls 3889, lf 3889, tf 3903, ff 274), 119 kalenders, dezelfde 23 datums per
   kalender; X12-corpustotaal blijft 17.421 en alle andere bestanden byte-identiek.

2. BASELINE-OPMAAK. `xer-schedoptions-blast-radius.json` staat nu volledig in de
   oorspronkelijke compacte vorm — `completedProgress` stond nog uitgeklapt omdat zes rijen
   126 tekens tellen. Netto diff t.o.v. basiscommit `7a3dece6`: 47 toegevoegde en 47
   verwijderde regels, en dat zijn precies de 47 veranderde `deviations`-waarden — nul
   opmaakregels. (De eerdere "79/16" in `1d1bc96d` mat de diff t.o.v. `fbcdb731`, niet t.o.v.
   de basis; de reviewer mat daar terecht 192/48.)

3. RANDDEFINITIE. De 125/141/64/78 uit `1d1bc96d` telde alleen ES en EF. De definitie staat
   nu expliciet in het docblok — alle VIER de assen (ES, EF, LS, LF), over de 6.548 taken op
   de gereconstrueerde kalendergroepen, waarbij de rand de drie kalenderdagen direct vóór de
   eerste en direct ná de laatste dag van de GERECONSTRUEERDE blokspanne is — met de juiste
   getallen: 167 (okt-2008, 153 taken), 214 (dec-2008, 193), 178 (sep-2009, 157), 252
   (nov-2009, 204); 811 totaal tegen 0 datums ín de blokken. Reproduceert de meting van de
   reviewer op de eenheid. De tweede bevestiging staat er nu ook bij: P6's eigen vensters
   telden op de OUDE kalender 10,00 werkdagen voor `V3109400` (7 dagen duur) en 24,00 voor
   `V3109300` (21 dagen), en op de gereconstrueerde exact 7,00 en 21,00.

4. PLAN-DOC. `docs/superpowers/plans/2026-08-20-plan-xer-p6-lezer.md` krijgt twee stukken:
   §5 `X-O7` legt de regel "een fidelity-stap maakt geen as slechter" vast MÉT de expliciete,
   eenmalige uitzondering voor deze etappe (es +322, ef +127) en de twee bronmetingen die
   haar rechtvaardigen; §9 registreert dossier **7b-4 "forward-anker na gereconstrueerde
   kalenderblokken"** met de 344/327 cellen, de vijf getraceerde taken, de vensterlengte-
   tabel die aantoont dat de duurwandeling klopt, en de n=1-basis (één bestand van 93; MPXJ
   kent het verschijnsel niet, dus een tweede bestand moet de regel bevestigen of ontkrachten
   in plaats van er stil op mee te liften).

5. POORTEN. `npm run typecheck` exit 0, `npm run lint` exit 0,
   `check-xer-calendar-data.ts` exit 0 (44 checks), `check-calendar-mirror.ts` exit 0 (80),
   X12 summary mét corpus `17421` (exit 0 in meetmodus), `check-xer-schedule-options-corpus`
   exit 0 (17), `check-xer-calendar-corpus` exit 0 (8), `check-xer-task-replay` exit 0 (32).
   `git show -w` is regel-voor-regel gelijk aan `git show`.

NIET GEDAAN (bewust, staat als open punt): de `public/docs`/changelog-regel over het verschil
tussen een verse XER-import en een eerder opgeslagen IFC, en de
`readIFCWithXerReconstruction`-fixture uit H-3.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit a66ae13)
…innig

Alleen commentaar, geen gedragswijziging.

1. `hasWeekendClampEvidence` zei impliciet dat de drie eisen de false-positive-klasse dichten.
   Ze dichten de twee GEMETEN gevallen (fixtures 22f en 22g), niet de klasse. Het docblok geeft
   nu het tegenvoorbeeld dat de klasse reproduceert: voeg aan 22f één dag toe — vrijdag
   2009-12-04, maandag 12-07 én dinsdag 12-08 als losse feestdagen op een za-do-kalender — en de
   reeks haalt de drempel van drie, de maandag heeft blokvervolg aan zijn afgekeerde zijde, en
   zaterdag 12-05 wordt alsnog vrijgemaakt (nagemeten: `2009-12-04 2009-12-05 2009-12-07
   2009-12-08`; zondag 12-06 niet, want een maandagrecord is op deze kalender geen redundant
   record). Met de vermelding dat het risico empirisch klein is — nul treffers in 93
   corpusbestanden buiten `rehab-2.xer` — maar niet nul: de poort filtert op bewijskracht en is
   geen sluitend bewijs.

2. "Nul in alle negen blokken tegen 811 eromheen" suggereerde dat alle negen blokken bewijs
   dragen. In dezelfde zin staat nu dat die 811 volledig op de vier blokken binnen de
   projectperiode zit (167/214/178/252) en dat de overige vijf 0 ín én 0 in de rand hebben en dus
   niets bewijzen. Zelfde correctie in de X-O7-tekst in
   `docs/superpowers/plans/2026-08-20-plan-xer-p6-lezer.md`.

Poort: `npm run typecheck` exit 0.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
(cherry picked from commit ae5931e)
…en gedrag)

Ontwerp 2026-09-04 §4.1/§4.3/§4.4, bouwstap 1:
- `Task.workRule` (WorkRule, neutraal tussen MSP en P6) en
  `Project.defaultWorkRule`; `ResourceAssignment.plannedWorkMinutes/
  actualWorkMinutes/remainingWorkMinutes` (drie optionele werkvelden,
  besluit 9). Afwezig ⇒ afgeleid zoals vandaag; geen enkele solverstap
  of bewerking leest ze nog (dat is stap 4).
- IFC: pset `OPS_WorkRule` (descriptor 15 in PER_TASK_PSETS),
  `DefaultWorkRule` in OPS_ProjectSettings, de drie werkvelden in het
  bestaande `OPS_Timephased`-JSON-blob (golden rule: niets ⇒ niets).
  Fixture + canon in check-ifc-roundtrip (185 groen).
- Extensiecontract: ExtTask.workRule, ExtProject.defaultWorkRule,
  ExtAssignment-werkvelden, mappers beide kanten, contractpoort groen.
- Taakraster: alleen-lezen technische kolommen `task.workRule` en
  `assignment.plannedWork/actualWork/remainingWork` (uren in beeld),
  labels in 14 locales; veld-dekking en registerpoort groen.
- MCP: planner_get_task toont workRule en de werkvelden; REJECT_HINT
  voor workRule tot stap 7. moveProject-verdicten n/a.
- Nieuw: `src/engine/work/workRuleMapping.ts` (stap 2, nog onbedraad):
  MSPDI-codes 0/1/2, P6 DurationType-namen, XER-tokens, en de
  "afwezig ⇒ afgeleid"-importregel voor de werkvelden.

Planning-, MCP- en bibliotheeksuite groen; typecheck, cycles,
store-boundaries, i18n groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…in de taaktypes-branch

Gestapelde branch: de taaktypes-etappe bouwt verder op de XER-lezer
(`Task.p6DurationType`, `OPS_P6Progress`, het XER-bronarchief). Conflicten
waren uitsluitend "beide kanten voegden toe" in dezelfde 21 bestanden
(taak-/projecttypes, ifcPsets, ext-contract, rasterdekking, MCP-leestool,
moveProject-verdicten, negen compacte locale-bestanden, de twee poorten);
beide kanten zijn behouden. `OPS_WorkRule` is nu descriptor 17, ná
`OPS_P6Progress` en `OPS_Summary`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…werkvelden

Ontwerp 2026-09-04 §4.2/§4.3/§4.4, bouwstap 2 (ná de merge met de XER-branch):
- `importNormalize.deriveImportedWorkRules`: één afleiding van `workRule`
  uit de bewaarde importvelden (MSP: mspTaskType+effortDriven; P6/XER:
  p6DurationType), aangeroepen in de .mpp-, MSPDI-, P6 XML- en XER-lezer.
  Importvelden blijven onaangeraakt; een bestaande regel wint.
- MSPDI: lezer leest task-level <Type> (0/1/2) en <EffortDriven>, writer
  schrijft ze uit de werkregel (bewaard MSP-vinkje wint, beslispunt 8-B);
  toewijzingen: Work/ActualWork/RemainingWork ↔ de drie werkvelden, alleen
  bewaard bij afwijking van duur × inzet (`importedWorkFields`).
- P6 XML: <DurationType> beide kanten; ActualUnits/PlannedUnits/
  RemainingUnits (uren) ↔ werkvelden op hun alfabetische PMXML-plek.
  FIX: de XER-branch had de labels "Fixed Duration and Units" en "Fixed
  Duration and Units/Time" verwisseld (DT_FixedDUR2 resp. DT_FixedDrtn);
  gecorrigeerd volgens Oracle's XER Import/Export Data Map Guide, ook in
  check-xer-p6xml-parity (die nu de DurationType-uitzondering op de
  asymmetrie vastlegt).
- XER: TASKRSRC target_qty / act_reg_qty + act_ot_qty / remain_qty →
  werkvelden, alleen bij afwijking (afspraak met de XER-etappe); materiaal
  buiten de driehoek; `XerResourceReadContext.taskWorkMinutes` optioneel.
- Nieuw: `check-work-rule-mapping.ts` (37 checks: tabellen, afleiding,
  MSPDI-/P6 XML-round-trips, XER-fixture). Fidelity-poorten ongewijzigd
  groen (mpp 678, xer 47); spec §4.2 bijgewerkt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…n MCP-tweeling

Brug src/engine/work/workRuleApply.ts tussen de domeinobjecten en de pure
kern workTriangle.ts: captureTriangle vóór de mutatie, settle… erna, één
terugschrijf (applyTriangleResult) van inzet, restwerk en — waar de regel dat
wil — de taakduur (verricht deel + nieuwe rest; hele dagen in dagmodus).

Bedraad in taskSlice.updateTask (+ nieuw setTaskWorkRule),
resourceSlice.assignResource/updateAssignment/unassignResource (+ nieuw
setAssignmentWork), gridTransaction.ts (celduur én de assignment-set-cel via
AssignmentSettleOp) en createMcpTransactions.ts (alle tweelingen plus
setAssignmentWork/setTaskWorkRule op de draft). Een duur die uit de driehoek
komt zet scheduleStale, herschaalt contour + importsplits en wist het
Z8-venster — precies als een duurbewerking. rescaleTaskContours krijgt een
keepWork-parameter uit de effectieve werkregel (contourKeepsWork; zonder eigen
workRule blijft de MSP-afleiding gelden). reconcileContourWork = besluit 3:
verandert het restwerk van een toewijzing mét contour, dan zakt de hoogte mee,
de as blijft. assignmentDayUnits krijgt opgeslagen werk als derde bron (data,
curvevorm over de duur); zonder werkveld byte-identiek.

Regressie: tests/planning/check-work-rule-store.ts (65 checks: standaardregel
byte-identiek, FIXED_WORK/FIXED_DURATION_WORK in de store, contour meetlat
22/23, raster, MCP, vierde bron, undo in één stap = meetlat 28). Planning- en
MCP-suites groen. Spec §10 (stap 4 gebouwd, stap 6 store-kant meegenomen),
TODO (remainingTime-vraag, B1c-koppelpunt clearLevelingGaps) en CLAUDE.md
bijgewerkt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
@Nozzit Nozzit changed the title Taaktypes: ontwerp (spec) + bouwstap 3, de pure werkdriehoek met meetlat Taaktypes: ontwerp + bouwstappen 1–4 (datamodel, importvertaling, werkdriehoek, bedrading) — gestapeld op de XER-branch Sep 5, 2026
Geen nieuwe tool; drie bestaande uitgebreid. planner_update_tasks en
planner_add_tasks kennen `fields.workRule` (vier waarden of null = project-
standaard): de allowlist in taskFields.ts levert hem apart van de kale
veld-merge, zodat updateTasksCore ná de duurpatch draft.setTaskWorkRule
aanroept — een gelijktijdige `duration` wordt onder de OUDE regel verwerkt en
de nieuwe regel legt daarna het restwerk vast (spec §5 rij 6). Bij aanmaak is
het een kaal veld; beide addTask-tweelingen nemen `workRule` mee.
planner_manage_assignments `update` kent `remainingWorkMinutes` (> 0, via
draft.setAssignmentWork; alleen waar workRuleApplies — mijlpaal/samenvatting/
hangmat/ELAPSEDTIME zacht geweigerd). planner_update_project kent
`defaultWorkRule` (null ⇒ sleutel echt weg); planner_get_project_info toont
hem. REJECT_HINTS voor de drie werkvelden als taakveld.

tests/mcp/cases-work-rule.ts: 9 cases via de echte dispatch (zetten/wissen/
ongeldig, duur+regel in één call, FIXED_WORK vs standaardregel, resource
erbij/eraf, projectstandaard, planner_batch = één undo-stap). Planning- en
MCP-suites groen; spec §10, TODO en CLAUDE.md bijgewerkt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
@Nozzit Nozzit changed the title Taaktypes: ontwerp + bouwstappen 1–4 (datamodel, importvertaling, werkdriehoek, bedrading) — gestapeld op de XER-branch Taaktypes: ontwerp + bouwstappen 1–4 en 7 (datamodel, importvertaling, werkdriehoek, bedrading, MCP) — gestapeld op de XER-branch Sep 5, 2026
…ing, geen drift, verplaatsen/verwijderen door de driehoek

Blokkerend (B1): `settleDurationEdit` vergeleek de RESTduur, waardoor een
voortgangsbewerking (completion/remainingTime via raster of een gespreide
time-tak) de inzet herrekende. De poort is nu de TOTALE werkduur uit de
momentopname (`CapturedTriangle.totalMinutes`).
Blokkerend (B2): een duur uit de driehoek op een gestarte taak liet de rest
opnieuw afleiden als nieuwe duur × (1 − completion) ⇒ drift bij heen-en-weer
(case 31). De rest wordt nu expliciet geschreven (`remainingTime`/
`remainingMinutes`) zodra de taak gestart is; `completion` blijft.
Belangrijk (B3): de opgeslagen-werk-bron in `assignmentDayUnits` telt nu het
verrichte deel mee (`actualWorkMinutes`, anders verrichte duur × inzet) —
een typewissel op een half gedane taak halveerde anders het histogram.
Belangrijk (B4): `moveAssignment` (eraf + erbij) en `removeResource` (eraf
per taak) lopen door de driehoek, in store én MCP-tweeling.
Klein: K1 `workRule` via `updateTask`/`updateTaskFields`/`patchTaskFields`
legt via `settleRuleChange` óók het werk vast; K3 alleen een herleide inzet
wordt afgerond; K4 één uurmodus-test (`isHourTask`) voor lezen en schrijven;
K5 `settleDurationAftermath` als ene nazorg (contour, importsplits, Z8-venster,
bevroren walks) voor store, raster en MCP; K6 volgorde in een gemengde
Resources-cel gedocumenteerd en getest. K2 (kalenderwissel na vastgelegd
werk) staat als open beslispunt in de TODO.

Tests: check-work-rule-store 65 → 95 checks (voortgang > 0, uurmodus,
FIXED_RATE + 8-B, materiaal in het raster, datums-zoals-opgeslagen,
verplaatsen/resource verwijderen, K1). Planning- en MCP-suites groen.
Spec §6.1/§6.5/§8/§10 en CLAUDE.md bijgewerkt.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…iting, paneel/dialoog, raster, i18n en gids

Zichtbaarheid (spec §7): instelling `ui.showTaskTypes` (`ops-showTaskTypes`,
settingsRegistry + saveShowTaskTypes + SettingsPanelContent, default uit) óf
documentontsluiting: `taskTypesVisible` in DOCUMENT_FIELDS (rol none, bij laden
afgeleid via `hasTaskTypeData` — taak met workRule/mspTaskType/p6DurationType,
projectstandaard, of toewijzing met werkveld — en gezet door setTaskWorkRule,
setAssignmentWork en de rasterbewerkingen), met één melding per document
(`taskTypesNotice.ts`, notifications.taskTypesUnlocked, helpArticleId →
gids-taaktypes). Selector `taskTypesUnlocked` (engine/work/taskTypesVisibility).

UI: `TaskWorkRuleField` in eigenschappenpaneel en taakdialoog (keuzelijst met
projectstandaard, "Beschermd: …" in gewone woorden, MSP-bijschrift 8-B); in
`TaskAssignmentsSection` de kolom Werk (rest) in uren met slotjes op de
beschermde hoek en '—' voor materiaal; raster: `task.workRule` als bewerkbare
enum (typewissel legt via gridTransaction het werk vast) en
`assignment.remainingWork` als tokens-kolom "naam: uren" via de assignment-set-
transactie en `settleWorkEdit`; beide alleen `available` wanneer ontsloten
(`TaskColumnContext.taskTypesUnlocked`, ook in de gridtransactie-context).
`task.workRule` leest in `readOnly` alleen gecertificeerde controllers en staat
in de Aanbeveling-4-pin van check-grid-transaction; de plakbenchmark filtert
op `available` zoals de echte adapter.

i18n in 14 locales (workRule-namen, beschermingsteksten, instelling + hint,
melding, kolomvalidatie); gids `gids-taaktypes` nl+en + manifest (README-telling
34); spec §10, TODO en CLAUDE.md bijgewerkt.

Tests: check-work-rule-store sectie (n) (110 checks), browserspec
tests/browser/work-rule.spec.ts (keuzelijst, werk typen, inzet, undo,
ontsluiting; 3/3 groen), planning-/bibliotheek-/MCP-suites groen, verify:i18n,
verify:docs, verify:examples groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
@Nozzit Nozzit changed the title Taaktypes: ontwerp + bouwstappen 1–4 en 7 (datamodel, importvertaling, werkdriehoek, bedrading, MCP) — gestapeld op de XER-branch Taaktypes (werkregels en opgeslagen werk): ontwerp + bouwstappen 1–7 — gestapeld op de XER-branch Sep 5, 2026
…erkt

docs/superpowers/README.md: de spec staat nu als "gebouwd (stappen 1–7)" met
de code-aanwijzers; docs/wiki/Features.md krijgt de feature "Task types and
work" met link naar de gids gids-taaktypes. verify:docs groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…bevriest niets, typewissel zonder stale, dialoog commit direct

B1: `validateAssignmentTokens` laat `remainingWorkMinutes` meereizen bij
plakken naar een andere taak (de genormaliseerde tokens lieten het werk
vallen ⇒ stille no-op). B2: de werkcel in het raster vergelijkt met de
GETOONDE waarde (opgeslagen, anders restduur × inzet) zodat een niet-bewerkte
toewijzing haar afgeleide getal niet als expliciet werk krijgt. B3: de
werkregel in het eigenschappenpaneel loopt via `setTaskWorkRule` — geen
`scheduleStale`, "datums zoals opgeslagen" blijft staan. B4: in de taakdialoog
commit een typewissel op een bestaande taak direct (zoals de toewijzings-
sectie), en Opslaan stuurt de duur alleen mee wanneer de gebruiker die in de
dialoog wijzigde (anders draaide Opslaan een duur uit de driehoek terug).
K1: `clearTaskTypesNoticeForDoc` bij newProject/createNewProject. K3: elk
schrijfpad (updateTask, addTask, MCP-tweelingen) ontsluit het document.
K4: werkkolom en rasterkolom alleen waar `workRuleApplies`. K5: werkinvoer
commit op Enter/blur (`WorkHoursInput`), niet per toetsaanslag. K6: contour-
restsom als afgeleide waarde, aria-labels met resourcenaam, sectiekop in de
instellingen (`settings.taskTypesSection`, 14 locales).
MCP: `planner_manage_assignments` `add` kent `remainingWorkMinutes` (in een
batch is het nieuwe assignmentId niet tempId-resolveerbaar).

Tests: check-work-rule-store sectie (o) (120 checks), cases-work-rule 10,
browserspec typt toets-voor-toets en dekt het dialoogpad (3/3). Planning-
en MCP-suites groen. TODO (K2 crashherstel-melding, K6a) en spec §10.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…r de werkregel, expliciete rest schuift mee

K2 (spec §6.4 herzien): een andere kalender (taakkalender, projectkalender of
andere uren per dag in een kalender) verandert de slotgrootte; de restduur in
dagen blijft, het werk van vóór de wissel is het anker en daarna beslist de
regel — Vast werk/Vaste inzet ⇒ R = max(W/I) in de nieuwe slot (langer bij
minder uren per dag), Vaste duur en werk ⇒ inzet W/R', standaardregel ⇒ werk
volgt (byte-identiek zonder werkveld). Kern `applySlotChange`, brug
`settleCalendarChange`; bedraad in taskSlice.setTaskCalendar en updateTask
(calendarId, vóór een eventuele duur in dezelfde patch), de rastercel
Kalender, de MCP-tweelingen (updateTaskFields/patchTaskFields/updateCalendar),
projectSlice.setProjectCalendar en resourceSlice.updateCalendar (alle taken op
die kalender, `tasksOnCalendar`). Een project-/kalenderwijziging die duren
verandert meldt hoeveel (notifications.workRuleDurationsChanged, 14 locales
met CLDR-meervouden). Uurtaken blijven ongemoeid.

Δ-rest (spec §6.5): een duurbewerking op een taak met EXPLICIETE restduur
schuift die rest mee (rest = max(0, rest + Δ), dag- en uurmodus; completion
blijft) — Microsoft: Remaining Duration = Duration − Actual Duration [M5].
`carryRemainingThroughDurationEdit` in updateTask, het raster
(finishDurationEdit) en de MCP-tweeling, alleen wanneer de patch de rest niet
zelf zette.

Meetlat 32–34 (kern + spec §9, evidence reasoned met de Microsoft-/Oracle-
bronnen); check-work-rule-store sectie (p) (142 checks); gids nl+en, TODO,
CLAUDE.md. Planning-, MCP- en bibliotheeksuite groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…g, contour-as op de oude slot, FIXED_RATE zonder werkveld

Reviewbevindingen F1–F11 op fae46f3:
- F1 raster: de kalendercel is een EIGEN stap vóór de rest van de paste (zelfde volgorde als
  `updateTask`); een `else` tussen kalender- en duurstap gooide de geplakte duur weg.
- F2/F8 MCP `patchTaskFields`: Δ-rest- en contourreferentie ná het kalenderblok; `restUntouched`-poort.
- F3: `captureCalendarChange` legt de werkminuten in de OUDE slot vast; `settleCalendarChange` doet
  nu zelf de nazorg (één definitie voor zes aanroepers) en herschaalt de contour-as óók wanneer de
  dagen niet veranderen (Vaste duur: dezelfde dagen zijn in de nieuwe slot ander werk).
- F5 kern: `applySlotChange` legt onder FIXED_RATE geen werkveld vast (zoals `applyUnitsEdit`).
- F6/F7: 8-B geldt niet bij een slotwissel; Δ-rest geldt wél op ELAPSEDTIME maar niet op
  verzameltaak/hangmat/mijlpaal — beide gedocumenteerd (spec §6.4/§6.5, docbloks).
- F9: `tasksOnCalendar` via `resolveCalendar` (bungelende kalender volgt de projectkalender);
  `tasksFollowingProjectCalendar` voor `setProjectCalendar`.
- F10: melding zonder dedupeKey — één melding per bewerking met het echte aantal.
- F4: bewust niet gewijzigd (`completion` blijft); de inconsistentie met de Gantt-voortgangsbalk
  staat als open eigenaarsvraag in spec §6.5 en docs/TODO.md.
- Extra: `setTaskCalendar`/`updateTask` meldden een door de nazorg gewist Z8-venster niet.

Tests: sectie (r) in check-work-rule-store (22 checks), kern-cases 35/36 (meetlat 1…36),
twee MCP-toolcases (update_tasks calendarId+duration, update_calendar). Alle suites groen.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…te uit de toewijzing, effectieve slot, verliesmelding op alle paden

- G1/G2: `settleCalendarChange` schaalt de contour-as éérst naar de nieuwe slot (hoogte volgt
  R' × I) en verzoent daarna de hoogte van elke contour met opgeslagen werkveld tegen dat werk
  (`reconcileContourWork` over alle toewijzingen van de taak) in plaats van tegen een regelvlag —
  onder de standaardregel schaalde de hoogte dubbel (histogram 25 % te laag), onder FIXED_RATE
  liepen contour en toewijzing uiteen.
- G3: `applyCellEdits` gebruikt weer `assignmentsForTask` (gemeten: 746 → 129 ms bij 1000 taken
  × 1000 toewijzingen).
- G4: `updateCalendar` en `setProjectCalendar` melden een door de nazorg gewist Z8-venster.
- G5: `workRuleContextOf` en `taskCalendarHoursPerDay` lezen de EFFECTIEVE uren per dag
  (`effHoursPerDay`), dezelfde slot als het raster.
- G6/G7/G9/G10: docblok `rescaleTaskContours` klopt weer met de code; ELAPSEDTIME blijft bij een
  kalenderwissel byte-identiek (spec §6.4); drie niet-bedrade randpaden en het Z8-venster-verschil
  in docs/TODO.md en CLAUDE.md.
- G11: dode `docId`-parameter van `notifyWorkRuleDurationsChanged` weg.
- G8: contour mét werkveld over alle vier de regels (hoogte = werk toewijzing, histogramsom) en de
  verliesmelding via updateCalendar/setProjectCalendar gepind (174 checks).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
…pliciet geschreven rest

Zodra de brug de restduur expliciet schrijft (Δ-regel bij een duurbewerking, kalenderwissel op een
gestarte taak) wordt `completion` herrekend als 1 − rest ÷ duur (`syncCompletionToRemaining`,
dezelfde formule en randafspraken als een restbewerking in het taakraster), zodat Gantt-balk,
solver en rapportage één waarheid delen. Voorbeeld: 10 d op 50 % → 6 u/dag onder Vast werk geeft
12 d, rest 7, 41,7 %; terug naar 8 u/dag weer 10 d, rest 5, 50 %. Een duur die onder het verrichte
deel wordt gekort geeft rest 0 en dus 100 %.

Let op (spec §6.5): elke voortgangsinvoer en elke import schrijft `remainingTime` expliciet
(`applyProgressInvariants`, `importNormalize`), dus de Δ-regel en dit besluit gelden in de praktijk
voor vrijwel elke gestarte taak — een duurbewerking op een gestarte taak verandert het percentage.

Tests: check-work-rule-store q1/q2/q6/q7/r4/r11/r12/r14, cases-work-rule en cases-taskfields
bijgewerkt; spec §6.4/§6.5, docs/TODO.md, CLAUDE.md en de gids (nl+en).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0159unb6VzxUx7WG4w3a2ARA
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants