diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 54477f1..f1562c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -120,6 +120,7 @@ jobs: --npm-pack \ --emit-events \ --events-out artifacts/events.ndjson \ + --events-truncate \ --strict-events echo "::endgroup::" diff --git a/artifacts/events.ndjson b/artifacts/events.ndjson new file mode 100644 index 0000000..4915b42 --- /dev/null +++ b/artifacts/events.ndjson @@ -0,0 +1,23 @@ +{"schema":"tm-events@1","event":"GATES_START","ts":"2025-10-19T02:35:59.889Z","seq":1,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"compose_path":"/workspace/true-modules/runs/canon-loop-demo/meta/compose.json","modules_total":2}} +{"schema":"tm-events@1","event":"LINT_START","ts":"2025-10-19T02:35:59.960Z","seq":2,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"lint_tool":"eslint"}} +{"schema":"tm-events@1","event":"LINT_PASS","ts":"2025-10-19T02:36:00.798Z","seq":3,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"lint_tool":"eslint","dur_ms":838}} +{"schema":"tm-events@1","event":"TEST_START","ts":"2025-10-19T02:36:00.800Z","seq":4,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/T-001.json"}} +{"schema":"tm-events@1","event":"TEST_PASS","ts":"2025-10-19T02:36:00.934Z","seq":5,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/T-001.json","dur_ms":134,"side_effects":{"declared":["FS:write"],"observed_operations":["FS:write"],"undeclared_operations":[],"fs_write":{"count":5,"outside_module_root":false,"sample_paths":[{"path":".tmp","inside_module_root":true},{"path":".tmp/case-t0cphL","inside_module_root":true},{"path":".tmp/case-t0cphL/reporter.log","inside_module_root":true},{"path":".tmp/case-t0cphL","inside_module_root":true},{"path":".tmp/case-t0cphL/reporter.log","inside_module_root":true}],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"TEST_START","ts":"2025-10-19T02:36:00.935Z","seq":6,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/T-002.json"}} +{"schema":"tm-events@1","event":"TEST_PASS","ts":"2025-10-19T02:36:01.088Z","seq":7,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/T-002.json","dur_ms":153,"side_effects":{"declared":["FS:write"],"observed_operations":["FS:write"],"undeclared_operations":[],"fs_write":{"count":7,"outside_module_root":false,"sample_paths":[{"path":".tmp","inside_module_root":true},{"path":".tmp/case-XI63ZD","inside_module_root":true},{"path":".tmp/case-XI63ZD/reporter.log","inside_module_root":true},{"path":".tmp/case-XI63ZD","inside_module_root":true},{"path":".tmp/case-XI63ZD/reporter.log","inside_module_root":true}],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"TEST_START","ts":"2025-10-19T02:36:01.089Z","seq":8,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/invariants/idempotent.json"}} +{"schema":"tm-events@1","event":"TEST_PASS","ts":"2025-10-19T02:36:01.226Z","seq":9,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","test":"tests/invariants/idempotent.json","dur_ms":137,"side_effects":{"declared":["FS:write"],"observed_operations":["FS:write"],"undeclared_operations":[],"fs_write":{"count":8,"outside_module_root":false,"sample_paths":[{"path":".tmp","inside_module_root":true},{"path":".tmp/case-dW6Efo","inside_module_root":true},{"path":".tmp/case-dW6Efo/reporter.log","inside_module_root":true},{"path":".tmp/case-dW6Efo","inside_module_root":true},{"path":".tmp/case-dW6Efo/reporter.log","inside_module_root":true}],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"TEST_START","ts":"2025-10-19T02:36:01.227Z","seq":10,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","test":"tests/T-010.json"}} +{"schema":"tm-events@1","event":"TEST_PASS","ts":"2025-10-19T02:36:01.358Z","seq":11,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","test":"tests/T-010.json","dur_ms":131,"side_effects":{"declared":[],"observed_operations":[],"undeclared_operations":[],"fs_write":{"count":0,"outside_module_root":false,"sample_paths":[],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"TEST_START","ts":"2025-10-19T02:36:01.359Z","seq":12,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","test":"tests/invariants/deterministic.json"}} +{"schema":"tm-events@1","event":"TEST_PASS","ts":"2025-10-19T02:36:01.488Z","seq":13,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","test":"tests/invariants/deterministic.json","dur_ms":129,"side_effects":{"declared":[],"observed_operations":[],"undeclared_operations":[],"fs_write":{"count":0,"outside_module_root":false,"sample_paths":[],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"PORT_CHECK_START","ts":"2025-10-19T02:36:01.492Z","seq":14,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","port":"ReporterPort@1"}} +{"schema":"tm-events@1","event":"PORT_CHECK_START","ts":"2025-10-19T02:36:01.495Z","seq":15,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","port":"CLIPort@1"}} +{"schema":"tm-events@1","event":"TSC_START","ts":"2025-10-19T02:36:01.500Z","seq":16,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"artifact":".tm/tsc.log"}} +{"schema":"tm-events@1","event":"TSC_PASS","ts":"2025-10-19T02:36:02.866Z","seq":17,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"dur_ms":1364,"artifact":".tm/tsc.log"}} +{"schema":"tm-events@1","event":"PORT_CHECK_PASS","ts":"2025-10-19T02:36:02.867Z","seq":18,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","port":"ReporterPort@1"}} +{"schema":"tm-events@1","event":"PORT_CHECK_PASS","ts":"2025-10-19T02:36:02.867Z","seq":19,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","port":"CLIPort@1"}} +{"schema":"tm-events@1","event":"SIDEEFFECTS_SUMMARY","ts":"2025-10-19T02:36:02.880Z","seq":20,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"cli","side_effects":{"declared":[],"observed_operations":[],"undeclared_operations":[],"fs_write":{"count":0,"outside_module_root":false,"sample_paths":[],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"SIDEEFFECTS_SUMMARY","ts":"2025-10-19T02:36:02.880Z","seq":21,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"module":"reporter","side_effects":{"declared":["FS:write"],"observed_operations":["FS:write"],"undeclared_operations":[],"fs_write":{"count":20,"outside_module_root":false,"sample_paths":[{"path":".tmp","inside_module_root":true},{"path":".tmp/case-t0cphL","inside_module_root":true},{"path":".tmp/case-t0cphL/reporter.log","inside_module_root":true},{"path":".tmp/case-XI63ZD","inside_module_root":true},{"path":".tmp/case-XI63ZD/reporter.log","inside_module_root":true}],"outside_samples":[]},"processes":{"total":0,"categories":{}}}}} +{"schema":"tm-events@1","event":"GATES_SUMMARY","ts":"2025-10-19T02:36:02.881Z","seq":22,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"phases":[{"name":"manifest_validation","dur_ms":62,"status":"pass"},{"name":"lint","dur_ms":840,"status":"pass"},{"name":"tests","dur_ms":689,"status":"pass"},{"name":"typecheck","dur_ms":1370,"status":"pass"},{"name":"oracles","dur_ms":0,"status":"skipped"},{"name":"npm_pack","dur_ms":0,"status":"skipped"}],"dur_ms":2992,"passed":5,"failed":0,"skipped":0}} +{"schema":"tm-events@1","event":"GATES_PASS","ts":"2025-10-19T02:36:02.881Z","seq":23,"source":{"cli":"tm","version":"0.1.0"},"context":{"run_id":"canon-demo","mode":"shipping","compose_sha256":"6809b66c0ea846ca9467f763b91e73b3cd9ccf87e462bf5e1beae3ccd2de1af0"},"detail":{"passed":5,"failed":0,"skipped":0,"dur_ms":2992}} diff --git a/implementation_report.json b/implementation_report.json new file mode 100644 index 0000000..9b244b4 --- /dev/null +++ b/implementation_report.json @@ -0,0 +1,26 @@ +{ + "schema": "tm-implementation-report@1", + "run_id": "2025-10-19T02:36:02.881Z", + "canon_lock_sha256": "97b94ada2f0f6bb47477259c7d54a75108a71130ba717d108f93af148db56400", + "compose_sha256": "1bc197c0266354b6bef03c58b86156bde9674285f969659eeb95f26526d23013", + "results": { + "reporter": { + "interfaces": { "Reporter.write": "pass" }, + "invariants": { "idempotent(Reporter.write)": "pass" }, + "acceptance": { "T-001": "pass", "T-002": "pass" } + }, + "cli": { + "interfaces": { "CLI.parse": "pass" }, + "invariants": { "deterministic(CLI.parse)": "pass" }, + "acceptance": { "T-010": "pass" } + } + }, + "evidence": { + "events_file": "artifacts/events.ndjson", + "port_exports": { + "reporter": { "Reporter.write": "modules/reporter/src/index.js#reporterWrite" }, + "cli": { "CLI.parse": "modules/cli/src/cli.js#cliParse" } + } + }, + "notes": [] +} diff --git a/modules/reporter/src/index.js b/modules/reporter/src/index.js index f984322..3653fe8 100644 --- a/modules/reporter/src/index.js +++ b/modules/reporter/src/index.js @@ -36,15 +36,16 @@ export async function reporterWrite(message, options = {}) { if (err && err.code !== 'ENOENT') throw err; } - const alreadyLast = prior.length > 0 && prior[prior.length - 1] === trimmed; - if (!alreadyLast) { + const seen = new Set(prior); + const alreadyLogged = seen.has(trimmed); + if (!alreadyLogged) { await fs.appendFile(logFile, `${trimmed}\n`, 'utf8'); prior.push(trimmed); } return { file: logFile, - appended: !alreadyLast, + appended: !alreadyLogged, lines: prior.slice() }; } diff --git a/modules/reporter/tests/invariants/idempotent.json b/modules/reporter/tests/invariants/idempotent.json index 807fcda..99922b3 100644 --- a/modules/reporter/tests/invariants/idempotent.json +++ b/modules/reporter/tests/invariants/idempotent.json @@ -1,5 +1,6 @@ { "name": "idempotent(Reporter.write)", "case": "idempotent", - "message": "stable" + "message": "stable", + "interleave": ["something else"] } diff --git a/modules/reporter/tests/runner.mjs b/modules/reporter/tests/runner.mjs index d33ad4c..e004be4 100644 --- a/modules/reporter/tests/runner.mjs +++ b/modules/reporter/tests/runner.mjs @@ -56,13 +56,25 @@ async function runCase(spec, moduleRoot) { } case 'idempotent': { const first = await reporterWrite(spec.message, { logDir }); - const second = await reporterWrite(spec.message, { logDir }); assert.equal(first.appended, true); + + if (Array.isArray(spec.interleave)) { + for (const entry of spec.interleave) { + const res = await reporterWrite(entry, { logDir }); + assert.equal(res.appended, true); + } + } + + const second = await reporterWrite(spec.message, { logDir }); assert.equal(second.appended, false); + const contents = await fs.readFile(second.file, 'utf8'); - const lines = contents.trim().split(/\r?\n/); - assert.equal(lines.length, 1); - assert.equal(lines[0], spec.message); + const lines = contents + .split(/\r?\n/) + .map((line) => line.trimEnd()) + .filter((line) => line.length > 0); + const occurrences = lines.filter((line) => line === spec.message); + assert.equal(occurrences.length, 1); break; } default: