feat(L08): add Docker fundamentals card

This commit is contained in:
user
2026-07-21 18:20:10 +02:00
commit 2ed1b7954a
39 changed files with 7419 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
import assert from 'node:assert/strict';
import { access, readFile } from 'node:fs/promises';
import test from 'node:test';
const source = JSON.parse(await readFile(new URL('../json/card_source.json', import.meta.url), 'utf8'));
test('L08 identity and task order are stable', () => {
assert.equal(source.card.number, '08');
assert.equal(source.card.uuid, 'b6d3b00d-6912-502d-95d7-3ee47c60f1db');
assert.deepEqual(source.tasks_order, ['task01', 'task02', 'task03']);
});
test('engine boundary and lifecycle are explicit', () => {
const text = JSON.stringify(source);
for (const token of ['offline-model', 'socket_exposed=0', 'build', 'running', 'exited', 'volume', 'bind', 'trace.log']) {
assert.match(text, new RegExp(token, 'i'));
}
assert.match(text, /nie oznacza uruchomienia prawdziwego silnika/i);
});
test('all task sources exist', async () => {
for (const name of ['task01_build_image_spec', 'task02_container_lifecycle', 'task03_volume_and_bind']) {
await access(new URL(`../src/tasks/${name}.sh`, import.meta.url));
}
});
+67
View File
@@ -0,0 +1,67 @@
import assert from 'node:assert/strict';
import { mkdtemp, rm } from 'node:fs/promises';
import os from 'node:os';
import path from 'node:path';
import test from 'node:test';
import { CardStateDatabase } from '../scripts/lib/card_state_db.mjs';
test('card state survives closing and reopening SQLite', async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-state-'));
const file = path.join(directory, 'state.sqlite3');
const state = {
schema: 'stem-card-navigation.v2',
revision: 7,
selected_at: '2026-07-17T12:00:00.000Z',
snapshot_ref: 'task04.alloc5.cursor'
};
try {
const first = new CardStateDatabase(file);
first.write('navigation', state);
first.close();
const second = new CardStateDatabase(file);
assert.deepEqual(second.read('navigation'), state);
assert.equal(second.read('missing'), null);
second.close();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
test('audit log is append-only, ordered and keeps navigation timestamps', async () => {
const directory = await mkdtemp(path.join(os.tmpdir(), 'stem-card-audit-'));
const file = path.join(directory, 'state.sqlite3');
try {
const database = new CardStateDatabase(file);
const navigation = {
task: { id: 'task04' },
block: { id: 'allocator-flow' },
phase: { id: 'alloc5' },
step: { id: 'alloc5-commit' },
snapshot_ref: 'task04.alloc5.commit'
};
const first = database.appendEvent({
eventType: 'navigation.activate',
actor: 'nvim',
navigation,
occurredAt: '2026-07-17T12:00:00.000Z',
payload: { source: 'F2' }
});
const second = database.appendEvent({
eventType: 'checkpoint.ready',
actor: 'nvim',
navigation,
occurredAt: '2026-07-17T12:00:01.000Z'
});
assert.equal(second.id, first.id + 1);
assert.deepEqual(
database.listEvents({ afterId: first.id, limit: 10 }).map(event => event.event_type),
['checkpoint.ready']
);
assert.equal(database.listEvents()[0].snapshot_ref, navigation.snapshot_ref);
database.close();
} finally {
await rm(directory, { recursive: true, force: true });
}
});
+5
View File
@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
root="${CARD_ROOT:-$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)}"; task="${1:?task required}"
case "$task" in task01_build_image_spec|task02_container_lifecycle|task03_volume_and_bind) ;; *) exit 2 ;; esac
exec env CARD_ROOT="$root" "$root/src/tasks/$task.sh"