From 42b876e82ee6b3e22f44116152badd11faa70361 Mon Sep 17 00:00:00 2001 From: jarvis-at-skic Date: Thu, 3 Sep 2026 21:32:16 +0000 Subject: [PATCH] [verified] docs: reconcile AeroSim Wave 2 progress --- docs/projects/aerosim.md | 10 +- scripts/verify-portal.mjs | 358 ++++++++++++++++++++++++++++++++++++-- 2 files changed, 348 insertions(+), 20 deletions(-) diff --git a/docs/projects/aerosim.md b/docs/projects/aerosim.md index 10c5c62..fa6fb96 100644 --- a/docs/projects/aerosim.md +++ b/docs/projects/aerosim.md @@ -48,8 +48,8 @@ A playable MVP covering a light trainer, commercial airliner, and fighter-style - Project organization: [corp-v1-aerosim](https://gitea.lego-cloud.eu/corp-v1-aerosim) - Skills organization: [corp-v1-aerosim-skills-code-agent](https://gitea.lego-cloud.eu/corp-v1-aerosim-skills-code-agent) -- [Application repository](https://gitea.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim) — `test` at `58d33ffbd3ca21ffdc5882a75de8fe71c25188b6` -- [Documentation repository](https://gitea.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim-documentation) — `test` at `29d5e7810e63f3407f62730c1b2c71bc0644310b` +- [Application repository](https://gitea.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim) — `test` at `efbf3967548f1cbf5e06f97fa9d659535e35a7b4` +- [Documentation repository](https://gitea.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim-documentation) — `test` at `f686610a8b1b21825c66dc424f484fb2672933d7` - [Published documentation](https://pages.apps.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim-documentation/) — protected by the existing Keycloak access layer ## Delivery progress checkpoint — 2026-09-03 @@ -58,11 +58,11 @@ Release `1.0.0` contains 114 Tasks. Progress is reported by lifecycle checkpoint | Measure | Progress | Meaning | |---|---:|---| -| Canonical `ToBeReleased` | 4 / 114 (3.5%) | `TASK-0001`, `TASK-0008`, `TASK-0011`, and `TASK-0014` have reviewed implementation, merged application changes, successful integrated CI, and reconciled canonical delivery evidence. | -| Repository-verified implementation | 5 / 114 (4.4%) | The four canonical Tasks plus `TASK-0015`, merged through application PR #13 at application `test` SHA `58d33ffbd3ca21ffdc5882a75de8fe71c25188b6`; its canonical task-history reconciliation is pending completion of the active Wave 2 delivery. | +| Canonical `ToBeReleased` | 8 / 114 (7.0%) | Wave 1 Tasks `TASK-0001`, `TASK-0008`, `TASK-0011`, and `TASK-0014` plus Wave 2 Tasks `TASK-0012`, `TASK-0015`, `TASK-0073`, and `TASK-0114` have reconciled canonical delivery evidence. The remaining 106 ReadyForDelivery Tasks have not reached this checkpoint. | +| Repository-verified implementation | 8 / 114 (7.0%) | The same eight Tasks are implemented at application `test` SHA `efbf3967548f1cbf5e06f97fa9d659535e35a7b4`; integrated publish task `1760` and validate task `1761` succeeded. This implementation measure is reported separately from canonical lifecycle and release state. | | Released | 0 / 114 (0%) | No Release `1.0.0` artifact has been approved and released. `ToBeReleased` is not release authorization. | -Wave 1 is implemented and reconciled. Wave 2 is active: `TASK-0015` is integrated, while `TASK-0012`, `TASK-0073`, and `TASK-0114` remain under automated review and repair. The authoritative task-level lifecycle remains in the [AeroSim documentation](https://pages.apps.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim-documentation/); this Board record is a dated portfolio checkpoint, not a second task database. +Wave 2 is complete: its final application head is `efbf3967548f1cbf5e06f97fa9d659535e35a7b4`, and its canonical documentation was merged through PR #143 at documentation `test` SHA `f686610a8b1b21825c66dc424f484fb2672933d7`. Documentation exact-head build task `1762` and integrated build task `1763` succeeded, and the deployed release-readiness readback passed. Wave 3 is active: `TASK-0013`, `TASK-0016`, `TASK-0074`, and `TASK-0115` are claimed and in progress, but are not yet implemented or merged. The authoritative task-level lifecycle remains in the [AeroSim documentation](https://pages.apps.lego-cloud.eu/corp-v1-aerosim/corp-v1-aerosim-documentation/); this Board record is a dated portfolio checkpoint, not a second task database. ## Discord diff --git a/scripts/verify-portal.mjs b/scripts/verify-portal.mjs index ffd1641..13533cc 100644 --- a/scripts/verify-portal.mjs +++ b/scripts/verify-portal.mjs @@ -58,25 +58,353 @@ assert.match(board, /Only these Board members may approve a project kickoff or p assert.match(board, /Only these Board members may approve changes to `corp-v1--main`/i); const aerosimProject = read('docs/projects/aerosim.md'); -for (const marker of [ +const APP_SHA = 'efbf3967548f1cbf5e06f97fa9d659535e35a7b4'; +const DOC_SHA = 'f686610a8b1b21825c66dc424f484fb2672933d7'; + +const markdownSection = (document, heading) => { + const marker = `## ${heading}`; + const start = document.indexOf(marker); + assert.notEqual(start, -1, `AeroSim section is missing: ${heading}`); + const end = document.indexOf('\n## ', start + marker.length); + return document.slice(start, end === -1 ? document.length : end); +}; + +const uniqueLine = (text, predicate, description) => { + const matches = text.split('\n').filter(predicate); + assert.equal(matches.length, 1, `AeroSim must contain exactly one ${description}`); + return matches[0]; +}; + +function verifyAeroSimProject(document) { + const requiredSectionHeadings = [ + 'Resources', + 'Delivery progress checkpoint — 2026-09-03', + 'Discord', + ]; + const documentLines = document.split('\n'); + const requiredSectionIndices = requiredSectionHeadings.map((heading) => { + const indices = documentLines.flatMap((line, index) => line === `## ${heading}` ? [index] : []); + assert.equal(indices.length, 1, `AeroSim required section heading must appear exactly once: ${heading}`); + return indices[0]; + }); + assert.ok( + requiredSectionIndices.every((index, position) => position === 0 || requiredSectionIndices[position - 1] < index), + 'AeroSim required sections must follow Resources, checkpoint, Discord order', + ); + + for (const marker of [ 'Delivery progress checkpoint — 2026-09-03', - '4 / 114', - '3.5%', - '5 / 114', - '4.4%', + '8 / 114', + '7.0%', '0 / 114', + 'TASK-0012', 'TASK-0015', - 'PR #13', - '58d33ffbd3ca21ffdc5882a75de8fe71c25188b6', - '29d5e7810e63f3407f62730c1b2c71bc0644310b', -]) { - assert.ok(aerosimProject.includes(marker), `AeroSim progress checkpoint is missing: ${marker}`); + 'TASK-0073', + 'TASK-0114', + 'TASK-0013', + 'TASK-0016', + 'TASK-0074', + 'TASK-0115', + 'PR #143', + '1760', + '1761', + '1762', + '1763', + 'efbf3967548f1cbf5e06f97fa9d659535e35a7b4', + 'f686610a8b1b21825c66dc424f484fb2672933d7', + '106 ReadyForDelivery', + ]) { + assert.ok(document.includes(marker), `AeroSim progress checkpoint is missing: ${marker}`); + } + + const resources = markdownSection(document, 'Resources'); + const applicationResource = uniqueLine(resources, (line) => line.startsWith('- [Application repository]'), 'application repository resource'); + const documentationResource = uniqueLine(resources, (line) => line.startsWith('- [Documentation repository]'), 'documentation repository resource'); + assert.match(applicationResource, new RegExp(`application.*\\b${APP_SHA}\\b`, 'i'), 'application repository must reference the application SHA'); + assert.match(documentationResource, new RegExp(`documentation.*\\b${DOC_SHA}\\b`, 'i'), 'documentation repository must reference the documentation SHA'); + + const checkpoint = markdownSection(document, 'Delivery progress checkpoint — 2026-09-03'); + const checkpointLines = checkpoint.split('\n'); + const checkpointTableHeader = '| Measure | Progress | Meaning |'; + const checkpointTableHeaderIndices = checkpointLines.flatMap( + (line, index) => line === checkpointTableHeader ? [index] : [], + ); + assert.equal(checkpointTableHeaderIndices.length, 1, 'AeroSim checkpoint table header must appear exactly once'); + const checkpointTableHeaderIndex = checkpointTableHeaderIndices[0]; + assert.equal( + checkpointLines[checkpointTableHeaderIndex + 1], + '|---|---:|---|', + 'AeroSim checkpoint table separator must exactly preserve its three columns and progress alignment', + ); + assert.deepEqual( + checkpointLines.slice(checkpointTableHeaderIndex + 2, checkpointTableHeaderIndex + 5).map( + (line) => line.match(/^\| (Canonical `ToBeReleased`|Repository-verified implementation|Released) \|/)?.[1], + ), + ['Canonical `ToBeReleased`', 'Repository-verified implementation', 'Released'], + 'AeroSim checkpoint rows must follow canonical, implementation, released order', + ); + const canonicalRow = uniqueLine(checkpoint, (line) => line.startsWith('| Canonical `ToBeReleased` |'), 'canonical ToBeReleased row'); + const implementationRow = uniqueLine(checkpoint, (line) => line.startsWith('| Repository-verified implementation |'), 'repository-verified implementation row'); + const releasedRow = uniqueLine(checkpoint, (line) => line.startsWith('| Released |'), 'released row'); + const waveStatus = uniqueLine(checkpoint, (line) => line.startsWith('Wave 2 is complete:'), 'Wave 2/Wave 3 status paragraph'); + + assert.match(releasedRow, /\|\s*0\s*\/\s*114\s*\(0%\)/i, 'AeroSim must distinguish released progress'); + assert.match(canonicalRow, /\|\s*8\s*\/\s*114\s*\(7\.0%\)/i, 'AeroSim must expose canonical delivery completion'); + assert.match(implementationRow, /\|\s*8\s*\/\s*114\s*\(7\.0%\)/i, 'AeroSim must expose repository-verified implementation separately'); + assert.match( + canonicalRow, + /Wave 2 Tasks `TASK-0012`, `TASK-0015`, `TASK-0073`, and `TASK-0114`/, + 'AeroSim Wave 2 task group must remain on the canonical completion row', + ); + assert.match( + implementationRow, + new RegExp('application `test` SHA `' + APP_SHA + '`; integrated publish task `1760` and validate task `1761` succeeded', 'i'), + 'AeroSim implementation row must bind the application SHA, publish task 1760, and validate task 1761', + ); + assert.match(waveStatus, new RegExp('final application head is `' + APP_SHA + '`'), 'Wave 2 status must bind the final application head to the application SHA'); + assert.match(waveStatus, new RegExp('documentation `test` SHA `' + DOC_SHA + '`'), 'Wave 2 status must bind the documentation merge to the documentation SHA'); + assert.match( + waveStatus, + /Documentation exact-head build task `1762` and integrated build task `1763` succeeded/, + 'Wave 2 status must bind exact-head build task 1762 and integrated build task 1763', + ); + assert.match( + waveStatus, + /Wave 3 is active: `TASK-0013`, `TASK-0016`, `TASK-0074`, and `TASK-0115` are claimed/, + 'AeroSim Wave 3 task group must remain in the active Wave 3 clause', + ); + assert.match(waveStatus, /Wave 3[^\n]*claimed[^\n]*not (?:yet )?implemented or merged/i, 'AeroSim must distinguish active Wave 3 claims from implementation'); + assert.match(waveStatus, /dated portfolio checkpoint, not a second task database/i, 'AeroSim must remain a non-authoritative portfolio checkpoint'); + + for (const [staleMarker, stalePattern] of [ + ['4 / 114', /(? { + assert.equal(document.split(oldText).length - 1, 1, `mutation fixture must find exactly one occurrence: ${oldText}`); + return document.replace(oldText, newText); + }; + const swap = (document, left, right) => { + const placeholder = '__VERIFY_PORTAL_MUTATION_PLACEHOLDER__'; + return replaceExactly(replaceExactly(replaceExactly(document, left, placeholder), right, left), placeholder, right); + }; + const swapAll = (document, left, right) => { + assert.ok(document.includes(left), `mutation fixture must find: ${left}`); + assert.ok(document.includes(right), `mutation fixture must find: ${right}`); + const placeholder = '__VERIFY_PORTAL_MUTATION_PLACEHOLDER__'; + return document.replaceAll(left, placeholder).replaceAll(right, left).replaceAll(placeholder, right); + }; + const checkpointRow = (document, prefix) => uniqueLine( + markdownSection(document, 'Delivery progress checkpoint — 2026-09-03'), + (line) => line.startsWith(prefix), + `${prefix} mutation source row`, + ); + const sectionBlock = (document, heading) => { + const section = markdownSection(document, heading); + return section.endsWith('\n') ? section : `${section}\n`; + }; + const futureCountFixtures = [14, 15, 24, 25, 114].flatMap((count) => [ + { + name: `accepts legitimate future count ${count} / 114`, + document: replaceExactly( + aerosimProject, + 'Release `1.0.0` contains 114 Tasks.', + `Release \`1.0.0\` contains 114 Tasks. Forecast example: ${count} / 114.`, + ), + accepted: true, + }, + { + name: `accepts legitimate compact future count ${count}/114`, + document: replaceExactly( + aerosimProject, + 'Release `1.0.0` contains 114 Tasks.', + `Release \`1.0.0\` contains 114 Tasks. Forecast example: ${count}/114.`, + ), + accepted: true, + }, + ]); + const staleCountFixtures = [4, 5].flatMap((count) => [ + { + name: `rejects stale count ${count} / 114`, + document: replaceExactly( + aerosimProject, + 'Release `1.0.0` contains 114 Tasks.', + `Release \`1.0.0\` contains 114 Tasks. Superseded snapshot: ${count} / 114.`, + ), + accepted: false, + message: new RegExp(`stale AeroSim checkpoint marker must not remain: ${count} / 114`), + }, + { + name: `rejects compact stale count ${count}/114`, + document: replaceExactly( + aerosimProject, + 'Release `1.0.0` contains 114 Tasks.', + `Release \`1.0.0\` contains 114 Tasks. Superseded snapshot: ${count}/114.`, + ), + accepted: false, + message: new RegExp(`stale AeroSim checkpoint marker must not remain: ${count} / 114`), + }, + ]); + const canonicalRowFixture = checkpointRow(aerosimProject, '| Canonical `ToBeReleased` |'); + const implementationRowFixture = checkpointRow(aerosimProject, '| Repository-verified implementation |'); + const releasedRowFixture = checkpointRow(aerosimProject, '| Released |'); + const resourcesSectionFixture = sectionBlock(aerosimProject, 'Resources'); + const checkpointSectionFixture = sectionBlock(aerosimProject, 'Delivery progress checkpoint — 2026-09-03'); + const discordSectionFixture = sectionBlock(aerosimProject, 'Discord'); + const fixtures = [ + ...futureCountFixtures, + ...staleCountFixtures, + { + name: 'rejects removed checkpoint table header', + document: replaceExactly(aerosimProject, '| Measure | Progress | Meaning |\n', ''), + accepted: false, + message: /checkpoint table header/, + }, + { + name: 'rejects malformed checkpoint table header', + document: replaceExactly(aerosimProject, '| Measure | Progress | Meaning |', '| Measure | Meaning | Progress |'), + accepted: false, + message: /checkpoint table header/, + }, + { + name: 'rejects removed checkpoint table separator', + document: replaceExactly(aerosimProject, '|---|---:|---|\n', ''), + accepted: false, + message: /checkpoint table separator/, + }, + { + name: 'rejects malformed checkpoint table separator', + document: replaceExactly(aerosimProject, '|---|---:|---|', '|---|---|---|'), + accepted: false, + message: /checkpoint table separator/, + }, + { + name: 'rejects swapped canonical and implementation rows', + document: swap(aerosimProject, canonicalRowFixture, implementationRowFixture), + accepted: false, + message: /checkpoint rows must follow canonical, implementation, released order/, + }, + { + name: 'rejects swapped implementation and released rows', + document: swap(aerosimProject, implementationRowFixture, releasedRowFixture), + accepted: false, + message: /checkpoint rows must follow canonical, implementation, released order/, + }, + { + name: 'rejects removed Resources heading', + document: replaceExactly(aerosimProject, '## Resources\n', ''), + accepted: false, + message: /required section heading.*Resources/i, + }, + { + name: 'rejects renamed Resources heading', + document: replaceExactly(aerosimProject, '## Resources', '## Project resources'), + accepted: false, + message: /required section heading.*Resources/i, + }, + { + name: 'rejects removed checkpoint heading', + document: replaceExactly(aerosimProject, '## Delivery progress checkpoint — 2026-09-03\n', ''), + accepted: false, + message: /required section heading.*Delivery progress checkpoint/i, + }, + { + name: 'rejects renamed checkpoint heading', + document: replaceExactly(aerosimProject, '## Delivery progress checkpoint — 2026-09-03', '## Delivery status — 2026-09-03'), + accepted: false, + message: /required section heading.*Delivery progress checkpoint/i, + }, + { + name: 'rejects removed Discord heading', + document: replaceExactly(aerosimProject, '## Discord\n', ''), + accepted: false, + message: /required section heading.*Discord/i, + }, + { + name: 'rejects renamed Discord heading', + document: replaceExactly(aerosimProject, '## Discord', '## Project channels'), + accepted: false, + message: /required section heading.*Discord/i, + }, + { + name: 'rejects Resources and checkpoint sections in reverse order', + document: swap(aerosimProject, resourcesSectionFixture, checkpointSectionFixture), + accepted: false, + message: /required sections must follow Resources, checkpoint, Discord order/, + }, + { + name: 'rejects checkpoint and Discord sections in reverse order', + document: swap(aerosimProject, checkpointSectionFixture, discordSectionFixture), + accepted: false, + message: /required sections must follow Resources, checkpoint, Discord order/, + }, + { + name: 'rejects swapped application and documentation SHA associations', + document: swapAll(aerosimProject, APP_SHA, DOC_SHA), + accepted: false, + message: /application repository.*SHA|documentation repository.*SHA/i, + }, + { + name: 'rejects swapped publish and validate CI task associations', + document: replaceExactly( + aerosimProject, + 'publish task `1760` and validate task `1761`', + 'publish task `1761` and validate task `1760`', + ), + accepted: false, + message: /publish task.*1760|validate task.*1761/i, + }, + { + name: 'rejects swapped documentation CI task associations', + document: replaceExactly( + aerosimProject, + 'exact-head build task `1762` and integrated build task `1763`', + 'exact-head build task `1763` and integrated build task `1762`', + ), + accepted: false, + message: /exact-head build task.*1762|integrated build task.*1763/i, + }, + { + name: 'rejects Wave 2 and Wave 3 task-group permutations', + document: swap( + aerosimProject, + '`TASK-0012`, `TASK-0015`, `TASK-0073`, and `TASK-0114`', + '`TASK-0013`, `TASK-0016`, `TASK-0074`, and `TASK-0115`', + ), + accepted: false, + message: /Wave 2 task group|Wave 3 task group/i, + }, + ]; + const failures = []; + for (const fixture of fixtures) { + try { + verifyAeroSimProject(fixture.document); + if (!fixture.accepted) throw new assert.AssertionError({message: 'mutated document was incorrectly accepted'}); + console.log(`ok - ${fixture.name}`); + } catch (error) { + if (!fixture.accepted && fixture.message?.test(error.message)) { + console.log(`ok - ${fixture.name}`); + } else { + failures.push(`${fixture.name}: ${error.message}`); + console.error(`not ok - ${fixture.name}: ${error.message}`); + } + } + } + assert.equal(failures.length, 0, `AeroSim verifier self-tests failed:\n${failures.join('\n')}`); + console.log('AeroSim verifier self-tests passed.'); } -assert.match(aerosimProject, /Released[^\n]*0\s*\/\s*114[^\n]*0%/i, 'AeroSim must distinguish released progress'); -assert.match(aerosimProject, /ToBeReleased[^\n]*4\s*\/\s*114[^\n]*3\.5%/i, 'AeroSim must expose canonical delivery completion'); -assert.match(aerosimProject, /repository-verified[^\n]*5\s*\/\s*114[^\n]*4\.4%/i, 'AeroSim must expose pending-reconciliation implementation evidence'); -assert.doesNotMatch(aerosimProject, /Application repository[^\n]*14b5c0e8f25215d8bb4f41edcc0cdff4a87c342c/, 'stale AeroSim application SHA must not remain'); -assert.doesNotMatch(aerosimProject, /Documentation repository[^\n]*5f674525f6c8b5ce29fe2653350c6188fb97ea01/, 'stale AeroSim documentation SHA must not remain'); const lifecycle = read('docs/governance/project-lifecycle.md'); assert.match(lifecycle, /Board approval/i, 'project lifecycle must require Board approval');