Merge pull request 'docs: reconcile AeroSim Wave 2 progress' (#23) from docs/aerosim-wave2-progress into main
Build and publish Corp v1 Board portal / build (push) Successful in 38s

This commit was merged in pull request #23.
This commit is contained in:
2026-09-03 14:33:35 -07:00
2 changed files with 348 additions and 20 deletions
+5 -5
View File
@@ -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
+341 -13
View File
@@ -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');
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',
'TASK-0073',
'TASK-0114',
'TASK-0013',
'TASK-0016',
'TASK-0074',
'TASK-0115',
'PR #143',
'1760',
'1761',
'1762',
'1763',
'efbf3967548f1cbf5e06f97fa9d659535e35a7b4',
'f686610a8b1b21825c66dc424f484fb2672933d7',
'106 ReadyForDelivery',
]) {
assert.ok(aerosimProject.includes(marker), `AeroSim progress checkpoint is missing: ${marker}`);
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', /(?<!\d)4\s*\/\s*114(?!\d)/],
['3.5%', /(?<![\d.])3\.5%(?![\d.])/],
['5 / 114', /(?<!\d)5\s*\/\s*114(?!\d)/],
['4.4%', /(?<![\d.])4\.4%(?![\d.])/],
['58d33ffbd3ca21ffdc5882a75de8fe71c25188b6', /58d33ffbd3ca21ffdc5882a75de8fe71c25188b6/],
['29d5e7810e63f3407f62730c1b2c71bc0644310b', /29d5e7810e63f3407f62730c1b2c71bc0644310b/],
]) {
assert.doesNotMatch(document, stalePattern, `stale AeroSim checkpoint marker must not remain: ${staleMarker}`);
}
assert.doesNotMatch(waveStatus, /Wave 2 is active/i, 'Wave 2 must not remain active');
}
verifyAeroSimProject(aerosimProject);
if (process.argv.includes('--self-test')) {
const replaceExactly = (document, oldText, newText) => {
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');