From c9ad6b6c1c7dc1eecd1d7ee1c18b71fa85b6b1ca Mon Sep 17 00:00:00 2001 From: Dmitrii Vasilev Date: Sat, 17 Oct 2026 22:52:04 +0700 Subject: [PATCH] spec(t27f): reel status goes through the topic engine, not a rule of its own (Closes #9999) topic_engine.t27 gains earned_status(step, domain, grounded, test_pass, reviewed, live): it walks the measurements through verify() and after_action(PUBLISH) and decides nothing on its own. reel_t27_basics.t27 drops its own STATUS_* codes and status rule, takes its domain from route() and every cell's status from earned_status. The engine routes the course brief to engineering (spec, compiler, fpga), where a DELIVER step needs a review. No trinity PR that wrote the course has one, so lessons 7-9, 16-18 and 25-27 stay draft although their pages are live: 18 published, 9 draft, not the 27 published the first draft showed. t27c and t27b agree: topic_engine 13/13, reel_t27_basics 7/7, reel 20/20, deck 15/15; three planted faults fail the same test in both. Co-Authored-By: Claude Opus 5.5 --- specs/t27f/reel_t27_basics.t27 | 81 +++++++++++++++++++++++++--------- specs/t27f/topic_engine.t27 | 45 +++++++++++++++++++ 2 files changed, 105 insertions(+), 21 deletions(-) diff --git a/specs/t27f/reel_t27_basics.t27 b/specs/t27f/reel_t27_basics.t27 index afdde8ac51..cc9225a33c 100644 --- a/specs/t27f/reel_t27_basics.t27 +++ b/specs/t27f/reel_t27_basics.t27 @@ -13,14 +13,23 @@ ; 10-18 (tests and functions, inside a function, your own types), 19-27 (modules and rules, the ; compiler and tri, errors and what next). The test lenses_are_whole_modules holds that. -; STATUS is measured, not chosen. On 2026-10-17: -; - page: GET https://t27.ai/learn// returned 200 with the title ": t27 basics -; course, lesson N of 27" for all 27; a made-up id returned 404, so 200 is not a catch-all. +; STATUS is earned through the topic engine (specs/t27f/topic_engine.t27, earned_status), not +; chosen here. The engine routes the course brief (its TITLE and DESCRIPTION at SOURCE_COMMIT) by +; KEYWORD_MATCH: spec, compiler, fpga are 3 engineering hits and no other list is hit, so the domain +; is DOMAIN_ENGINEERING. What was measured, per lesson, on 2026-10-17: +; - grounded: every lesson has its text in the source spec, so every one has a source link. ; - spec: t27c test-report on the Railway lab passed every test of all 27 lesson specs ; (apps/website/public/t27/files/specs/basics/*.t27 at SOURCE_COMMIT). 23 of them report their ; passes as vacuous because every assert is on constants and is checked when compiled; planting ; a wrong one (lesson 11, HOURS == 25) turned that test red, so the checks are real. -; A cell is PUBLISHED only when both hold, VERIFIED when only the spec passes, DRAFT otherwise. +; - review: neither trinity PR that wrote the course (#1463, #1472) has a review, so no lesson does. +; - page: GET https://t27.ai/learn// returned 200 with the title ": t27 basics +; course, lesson N of 27" for all 27; a made-up id returned 404, so 200 is not a catch-all. +; The engine asks a LEARN step for a source link and an engineering PRACTICE step for a passing test, +; so lessons in those stages are VERIFIED, and their live pages make them PUBLISHED: 18 of 27. An +; engineering DELIVER step asks for a review, which no lesson has, and a live page does not stand in +; for one: lessons 7-9, 16-18 and 25-27 stay DRAFT although their pages are live. Until 2026-10-17 +; this file used its own rule (live page and passing spec -> published) and drew all 27 as published. ; SCENES: the t27f_view host draws the reel.t27 demo camera (overview, lens 0, focus on cell 10, ; evidence, call), so this reel uses that camera; the test the_camera_is_the_demo_camera holds it. @@ -29,7 +38,7 @@ ; TWO COMPILERS: this file, reel.t27 and deck.t27 run under t27c (the Zig backend) and under t27b ; (t27's own AArch64 backend, `t27b test --check`, qemu-aarch64 on the lab). On 2026-10-17 the two -; agreed on every test (6 + 20 + 15 pass), and each planted fault above failed the same test in both. +; agreed on every test, and each planted fault failed the same test in both (see the PRs). ; t27b also refused the first draft of this file: it compared a str's u64 `.len` with an i32, which ; t27c accepted; every length is now cast to i32 before it is compared. @@ -61,6 +70,13 @@ use t27f::deck::text_fits; use t27f::deck::chars_per_line; use t27f::deck::card_inner_w; use t27f::deck::region_w; +use t27f::topic_engine::STATUS_DRAFT; +use t27f::topic_engine::STATUS_PUBLISHED; +use t27f::topic_engine::STAGE_DELIVER; +use t27f::topic_engine::DOMAIN_ENGINEERING; +use t27f::topic_engine::route; +use t27f::topic_engine::step_stage; +use t27f::topic_engine::earned_status; pub const TOPIC_ID : str = "t27-basics"; pub const SOURCE_REPO : str = "gHashTag/trinity"; @@ -72,17 +88,18 @@ pub const MEASURED_ON : str = "2026-10-17"; pub const LESSONS_PER_MODULE : i32 = 3; pub const MODULES_PER_LENS : i32 = 3; -/// Lesson statuses, the T27fTopicEngine codes reel.t27 reads. -pub const STATUS_DRAFT : u8 = 3; -pub const STATUS_VERIFIED : u8 = 4; -pub const STATUS_PUBLISHED : u8 = 5; +/// The domain the engine routes the course brief to, and the hits it counted (see the header). +pub const DOMAIN : u8 = 2; +pub const ENGINEERING_HITS : u32 = 3; -/// The two measurements per lesson, in lesson order (see the header for how each was taken). +/// The measurements per lesson, in lesson order (see the header for how each was taken). +pub const GROUNDED : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; pub const PAGE_LIVE : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; pub const SPEC_PASSES : [27]bool = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true]; +pub const REVIEWED : [27]bool = [false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false]; /// What a host passes the viewer, per cell: the status code and the lesson number as a label. -pub const STATUS : [27]u8 = [5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5, 5]; +pub const STATUS : [27]u8 = [5, 5, 5, 5, 5, 5, 3, 3, 3, 5, 5, 5, 5, 5, 5, 3, 3, 3, 5, 5, 5, 5, 5, 5, 3, 3, 3]; pub const LABELS : [27]str = ["1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16", "17", "18", "19", "20", "21", "22", "23", "24", "25", "26", "27"]; pub const LENS_TITLES : [3]str = ["values", "logic", "tools"]; @@ -107,7 +124,7 @@ pub const ROWS : [20]str = [ "Try: break one, the other two still pass", "27 of 27 lesson pages live on t27.ai", "27 of 27 lesson specs pass on the lab", - "A planted wrong assert turns it red", + "18 published; 9 wait for a review", "Measured 2026-10-17", "t27.ai/learn/what-a-spec-is/", "Progress stays in this browser", @@ -123,17 +140,22 @@ pub fn module_of(cell: i32) -> i32 { return @divTrunc(cell, LESSONS_PER_MODULE); } -/// The status a cell has earned from its two measurements. +/// The status the engine says a cell has earned from its measurements. pub fn measured_status(cell: i32) -> u8 { - const live : bool = PAGE_LIVE[@intCast(cell)]; - const pass : bool = SPEC_PASSES[@intCast(cell)]; - if (live and pass) { - return STATUS_PUBLISHED; - } - if (pass) { - return STATUS_VERIFIED; + const i : usize = @intCast(cell); + return earned_status(@intCast(cell), DOMAIN, GROUNDED[i], SPEC_PASSES[i], REVIEWED[i], PAGE_LIVE[i]); +} + +pub fn count_status(status: u8) -> i32 { + var n : i32 = 0; + var c : i32 = 0; + while (c < CELL_COUNT) { + if (STATUS[@intCast(c)] == status) { + n = n + 1; + } + c = c + 1; } - return STATUS_DRAFT; + return n; } pub fn count_true(xs: [27]bool) -> i32 { @@ -208,12 +230,29 @@ test a_status_is_earned_not_chosen { assert STATUS[@intCast(c)] == measured_status(c); c = c + 1; } + assert count_true(GROUNDED) == 27; assert count_true(PAGE_LIVE) == 27; assert count_true(SPEC_PASSES) == 27; + assert count_true(REVIEWED) == 0; + // the evidence slide's "18 published; 9 wait for a review" + assert count_status(STATUS_PUBLISHED) == 18; + assert count_status(STATUS_DRAFT) == 9; + // the drafts are exactly the DELIVER lessons, whose pages are live all the same + var d : i32 = 0; + while (d < CELL_COUNT) { + const deliver : bool = step_stage(@intCast(d)) == STAGE_DELIVER; + assert (STATUS[@intCast(d)] == STATUS_DRAFT) == deliver; + d = d + 1; + } // a published cell wears the published style, never a gap or a draft assert cell_style(STATUS[0]) == STYLE_PUBLISHED; } +test the_domain_is_routed_not_chosen { + assert DOMAIN == DOMAIN_ENGINEERING; + assert route(0, ENGINEERING_HITS, 0, 0) == DOMAIN; +} + test the_camera_is_the_demo_camera { assert DEMO_COUNT == 5; // scene 1 shows lens 0, the row HEAD[1] calls "Lessons 1-9" diff --git a/specs/t27f/topic_engine.t27 b/specs/t27f/topic_engine.t27 index 0dac0e6a25..c24cc60f1c 100644 --- a/specs/t27f/topic_engine.t27 +++ b/specs/t27f/topic_engine.t27 @@ -336,6 +336,29 @@ pub fn after_action(status: u8, action: u8, approved: bool) -> u8 { return status; } +/// The status a step has earned from what was measured about it, walked through the gates above +/// and nothing else: a step with no source is a GAP; a grounded one starts as a DRAFT; each piece of +/// evidence goes through verify(); a step that is live counts as published only if it was VERIFIED +/// first, so a live page with too little evidence stays a DRAFT. `live` means the item already +/// reached its audience, so whoever put it there approved that. +pub fn earned_status(step: u8, domain: u8, grounded: bool, test_pass: bool, reviewed: bool, live: bool) -> u8 { + if (grounded == false) { + return STATUS_GAP; + } + const stage : u8 = step_stage(step); + var status : u8 = verify(STATUS_DRAFT, stage, domain, EVIDENCE_SOURCE_LINK); + if (test_pass) { + status = verify(status, stage, domain, EVIDENCE_TEST_PASS); + } + if (reviewed) { + status = verify(status, stage, domain, EVIDENCE_REVIEW); + } + if (live) { + status = after_action(status, ACTION_PUBLISH, true); + } + return status; +} + test shape_is_three_lenses_of_nine { assert LENS_COUNT * STEPS_PER_LENS == LESSON_COUNT; assert STAGE_COUNT * STEPS_PER_STAGE == STEPS_PER_LENS; @@ -452,6 +475,28 @@ test engineering_practice_needs_a_test { assert evidence_accepted(STAGE_PRACTICE, DOMAIN_ENGINEERING, EVIDENCE_TEST_PASS); } +test a_live_page_does_not_verify_a_step { + const e : u8 = DOMAIN_ENGINEERING; + // steps 0, 3 and 6 are the first LEARN, PRACTICE and DELIVER steps of lens 0 + assert step_stage(0) == STAGE_LEARN; + assert step_stage(3) == STAGE_PRACTICE; + assert step_stage(6) == STAGE_DELIVER; + // no source: a gap, whatever else was measured + assert earned_status(0, e, false, true, true, true) == STATUS_GAP; + // LEARN: the source link is its evidence + assert earned_status(0, e, true, false, false, false) == STATUS_VERIFIED; + assert earned_status(0, e, true, false, false, true) == STATUS_PUBLISHED; + // PRACTICE in engineering: only a passing test verifies it + assert earned_status(3, e, true, false, true, true) == STATUS_DRAFT; + assert earned_status(3, e, true, true, false, true) == STATUS_PUBLISHED; + // DELIVER in engineering: a passing test and a live page are not a review + assert earned_status(6, e, true, true, false, true) == STATUS_DRAFT; + assert earned_status(6, e, true, true, true, false) == STATUS_VERIFIED; + assert earned_status(6, e, true, false, true, true) == STATUS_PUBLISHED; + // the stage repeats in every lens: step 26 is the last DELIVER step + assert earned_status(26, e, true, true, false, true) == STATUS_DRAFT; +} + test progress_only_after_verification { assert opens_next(STATUS_OPEN) == false; assert opens_next(STATUS_DRAFT);