Skip to content

Releases: ShkodnikAI/Metalogos-

0.32.0

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 15 Oct 10:24
08482d1

Security

  • №689 (gh#1256, В44 — the R-1 audit finding; fix date 2026-10-15):
    the mail ПДн (personal data) learned to SPEAK at the flow layer. Before
    the fix the private mark lived on exactly ONE registry row
    (canary_check, №661), imap_read carried all 9 fields untrusted,
    and the label engine was NAME-based only — the registry field-meta
    never reached the №323/№325 flow machinery: a program could send the
    mail correspondence (from/to/subject/body_text/body_html/attachments)
    straight into smtp_send/http_post/respond with ZERO findings —
    the silent ПДн egress the audit's R-1 described («the machine does not
    check the private → sink flow because there are almost no marks»).
    The repair, RED-first (tests/naryad_689_private_field_flow.rs): (1)
    the REGISTRY marks — the ImapEmail ПДн fields are private now (the
    shape read off the impl, №650; uid/date/seen stay untrusted — the
    server metadata); (2) the BRIDGE —
    semantic::registry_private_flow_label: a row whose field-meta
    carries private seeds the flow layer with the type-layer secret
    projection (conf=private, integrity=trusted, the ADR-0154 §2 form), so
    the №325 sink gate fires the PII_EGRESS_NETWORK / PII_EGRESS_OUTPUT
    classes on the let-bound AND the direct-call paths; redact stays the
    only legal downward cure (№326, tested). The honest boundaries: the
    bridge is deliberately NARROW (rows without a private mark — the
    untrusted/integrity vocabulary — keep the old path; whole-value
    fail-closed posture, the per-field precision is the stage-2 contour);
    card_contacts/cal_events keep the honest JSON-String spelling with
    the reason in the row comments (the stage-0 scalar vocabulary carries
    no label — a String<private> row would erase to Unknown and regress
    the typed floor; the structural typing is the №688-cycle tranche).
    Metrics (the actual recount): the field-label share 46/48 = 95.83% —
    unchanged; the sensitive surface 73/145 = 5034 bp — unchanged (the
    marks changed values, not coverage). Affected: programs egressing the
    imap_read fields into the egress sinks — they now refuse at compile
    with the PII class named (advisory under profile legacy, the №325
    posture).

  • №685 (gh#1252, В44 — the S-1 audit's High finding; fix date 2026-10-15):
    the RAW template insert {{{ var }}} in render() is FAIL-CLOSED now.
    Before the fix the N9 raw form (gh#1219) landed its argument VERBATIM in
    the page while every static layer answered «sanitized» at the render
    boundary — render("Comment", "author", body.author, "text", body.text)
    with {{{ text }}} in a text: String template passed mlog check,
    run and serve with zero findings: the XSS the audit demonstrated,
    invisible to the HTML_INJECTION category. The repair is the audit's
    prescription, three gates deep: (1) the DECLARATION gate — a template
    parameter used in {{{ }}} must be declared Html, anything else is
    the RAW_INSERT_NOT_HTML stable-code refusal BEFORE execution on BOTH
    backends (check_program + the compile_program gate block, the №673
    contour shape; the code joins the №385/ADR-0169 classification, nothing
    renamed); (2) the CALL-SITE coercion — a String-shaped argument into an
    Html-typed parameter of a statically-known template refuses (the №662
    rule-1 twin, the implicit String→Html the runtime accepted silently);
    (3) the TAINT/LABEL layers — render() is now a sanitizer for the
    ESCAPED positions and a CONDUCTOR for the RAW ones (the template body
    decides from the program's declarations; a dynamic/unknown name is a
    conservative conductor; the UserInput/LLM egress gates
    OPEN_REDIRECT/HTML_INJECTION/TELEGRAM_HTML_INJECTION read the conducted
    taint instead of a fake lift). The honest path is unchanged: an
    Html-typed parameter built by an explicit Html producer
    (render("Outer", render("Inner", s))) renders verbatim on both
    backends. Affected: programs that fed String values into raw
    {{{ }}} positions — they now refuse at start with the parameter and
    the fix named in the message (the class-4 dictionary entry, the guard
    scenario per the №653 form; the audit's «do not cut 0.31.0 before S-1»
    is satisfied). template_render's raw=true trusted-author contract is
    unchanged (the limitations row now tells the two contracts apart).

  • №673 (gh#1200, В41 — BREAK_OUTSIDE_LOOP, the R-2 audit's two
    «молча неверные» break/continue edges; fix date 2026-10-14):
    a break/continue (а)
    OUTSIDE any loop (№658-b) or (б) crossing the VALUE channel — an arm
    body of a match/if-as-value (№658-a) — now refuses as a SEMANTIC error
    BEFORE execution on BOTH backends, with the stable
    BREAK_OUTSIDE_LOOP code stamped first (the №523 shape; the code
    joins the №385/ADR-0169 classification, no existing code renamed).
    Before the fix both edges were the class-4 dictionary entries (№653)
    the honest-limits records pinned: the TW refused at RUNTIME
    («break/continue used outside of a loop»), while the VM either
    compiled the bare statement to NOTHING and continued (the audit's
    import_batch shape — break on the error path instead of return,
    and the code after it RAN: the recorded pre-fix VM answer was
    Ok("published")) or silently ABSORBED the signal inside a
    value-channel arm and ran the loop to its condition (the recorded
    pre-fix VM answer was Ok("matched;matched;matched;") where the TW
    refused). The repair is the audit's recommendation — a semantic gate,
    not a runtime mechanism: ONE shared walker
    (semantic::break_outside_loop_violations) walks every executable
    body (pattern, tool method, server route, hook, on_deny handler,
    test) with the loop/value-channel context model — loop bodies open
    the loop context, statement-position branches preserve it (the
    №658 contract the norms S-BLK-011/012 pin stays UNCHANGED — a break
    nested through if/match inside a loop keeps working), value-channel
    arm bodies forbid the signal outright, a loop inside an arm re-opens
    the legal context for its own body. Wired into BOTH production gates:
    check_program (the TW run/serve path) and — for the first time for
    a semantic finding class — compile_program (the VM compile→run .mbc
    path, which had no semantic gate; only THIS class is wired there, not
    the whole check_program — the widening is a separate conversation).
    The runtime mirrors stay untouched as backstops. Two conformance
    pairs landed (the sblk_011/012 form): sblk_015_break_value_arm
    (№658-a) and sblk_016_break_outside_loop (№658-b), both
    STATUS: error, CODE: BREAK_OUTSIDE_LOOP — RED before the fix on
    the VM (the silent answers above), green after. Honest limits:
    the blocks.md №658-a/№658-b records moved to the REPAIRED form
    (the №652-a sample; the edge history kept). Affected: programs that
    RELIED on the silent absorption (the №653 class-4 guard scenario) —
    they now refuse loudly at startup and must use return.
    Size: M; risks: low-medium (any pre-existing program carrying the
    illegal shapes now blocks at startup — that is the repair's purpose;
    the legal break/continue surface is contract-pinned unchanged).

Added

  • №688 (gh#1255, В44 — the audit f92c61c S-3; the draft, the owner's
    verdict pending):
    the 0.32 typed-source goal moves to the
    FIXED-LIST closure form (ADR-0193 Proposed) — «of the 67 untyped
    sensitive sources recorded at the anchor f92c61c, at least 20
    closed, the ПДн-class first; the denominator is OUT of the goal».
    The machine half: scripts/ci/gate_032_untyped_sources.txt (the 67
    ids with the registry lines at the anchor, generated by the anchor
    worktree's own №644 inventory) + the --goal032 report-only counter
    (GREEN at the honest 0/20 start; the --gate032 N floor mode exists
    mechanically and wires ONLY with the owner's §5 fixation — the
    gate_031 one-PR form). The X-3 fraud shape (the 0.31 goal met by the
    denominator growth 66/138 → 73/145 while untyped 67 did not move)
    is closed mechanically: only closing REAL rows of the FROZEN list
    moves the goal.

  • №691 (gh#1258, В44 — the spec topics): the specification grew two
    topics — routes.md (Topic 4: S-RT-001 the route declaration
    vocabulary, S-RT-002 the direct respond* early answer, S-RT-003 the
    depth-2 RESPOND_SWALLOWED refusal, S-RT-004 the raw-insert Html-only
    rule, S-RT-005 the protective-header ownership — the middleware's,
    not set_header's, S-RT-006 the respond_html dual contract) and
    state.md (Topic 5: S-ST-001 the world_state private-by-default
    materialization refusals, S-ST-002 the private label gates
    materialization — not the handle flow — plus the typed device chain,
    S-ST-003 the literal-key memory round-trip). NINE new conformance
    pairs (both backends, the №645 runner; the №685 raw-insert pairs
    referenced, not duplicated); the probes found NO cross-backend
    divergence — the route/state refusals are shared-code builtins and
    semantic checks. The normative core: 33 → 42 norms. The №674
    mirror-scan IN SYNC (the №668 typed-coarse honesty got its №588-form
    limitations row in the same PR); REFERENCE cross-references the
    topics from the server and the world_state rows.

  • №674 (gh#1201, В41, process/ci — the honest-boundary machine sees
    the spec):
    honest_boundary_check.py now scans the «Honest limits»
    sections of docs/spec/*.md — every ACTIVE record (the №-id bullets)
    MUST have its mirror row in docs/limitations.md; REPAIRED records
    are exempt (the repair closed the edge — the №652-a sample). The scan
    is repo-state based and runs in the SAME blocking №604 job on PRs AND
    on push/main (the job's push/main branch runs the full retrospective
    now instead of exiting early — no new CI cell, №535); the --diff
    marker/dictionary modes stay unchanged. The gap it closes: the №659-x
    /№659-y records lived in docs/spec/blocks.md ONLY — limitations.md
    had no mirror (...

Read more

0.30.1

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 12 Oct 11:08
27877b8

Security

  • №658 (gh#1159) — the VM break/continue parity (the behavior change,
    fail-closed; AFFECTS 0.30.0 AND EARLIER on the VM backend; the audit
    guard scenario: a break/continue inside a loop body — top-level,
    inside an if guard, inside a match arm, in a nested loop — was
    silently IGNORED by the VM: the body ran to the condition cap as if
    the statement were absent, and a top-level form kept an unresolved
    Jump(0) placeholder that tripped the infinite-loop guard, while the
    tree-walking backend honored both statements; the X-1/Y-1/Q-1
    silently-wrong control-flow class, the №652-a finding):
    the VM loop
    path mirrors the TW ControlFlow contract 1:1 — every
    break/continue reaches its NEAREST enclosing loop on BOTH backends
    (break exits the loop; continue starts the next iteration — the
    condition re-test for while, the next item for
    each/each_with_index). The compiler now resolves every loop jump
    from the loop-stack entry: the shared statement compiler registers a
    Jump(0) placeholder in the NEAREST entry at any nesting depth and
    the loop compiler patches it from the POPPED entry (the stale-local
    patch loops — the pre-№658 root cause — are gone). The norms
    S-BLK-011/012 + five conformance pairs pin the record; the TW is
    unchanged. Found by №652 (the spec topic 2 probe); the repair follows
    the №651/№629 precedent. The fix lands 2026-10-09 (this PR; after the
    v0.30.0 tag b4b9abb lineage — the published tag is affected on the VM
    backend; no re-tag — the №620 precedent).

  • №651 (gh#1145) — the TW/VM condition parity (the behavior change,
    fail-closed; AFFECTS 0.30.0 AND EARLIER on the VM backend; the audit
    guard scenario: a composite or an opaque value in a condition — e.g.
    if [items] { … } or if hash_password(pw) { … } — silently chose a
    branch on the VM (a non-empty List took THEN, an opaque handle took
    ELSE) while the tree-walking backend refused; the X-1/Y-1/Q-1
    silently-wrong-guard class):
    the VM condition path (the new
    Instruction::JumpIfNotCond, emitted for if / else-if / while / the
    match guards) mirrors the TW Value::as_bool 1:1 — the truthy set of
    a condition is exactly {true Bool, Float ≠ 0.0, non-empty String},
    Unit is falsy, and every composite / opaque / Fluid value refuses
    with the stable [TYPE_MISMATCH] code on BOTH backends (the norm
    S-VAL-013; the №629 loud-refusal class). The soft truthiness of
    &&/|| (the №532 twin, compiled to the legacy JumpIfNot) is
    unchanged and stays pinned. Found by №645 (the spec-carcase
    conformance build, the finding №645-a); the repair follows the №629
    precedent. The fix lands 2026-10-08 (this PR; after the v0.30.0 tag
    6eafee1 — the published tag is affected on the VM backend; no re-tag
    — the №620 precedent).

Fixed

  • #1169/#1170 (the office VM-pilot staging, FO-050; PR — this fix) —
    the VM serve-boot db bootstrap (the interpreter-parity boot path; the
    behavior change, fail-closed at boot; Fixed-class → rides the
    0.30.1 train):
    db { url: env("NAME") } on the VM serve lane
    resolved the URL inside EVERY route handler (the №758 lazy
    first-db-access semantics), so the same read went through the №259
    serve-route gate — without METALOGOS_ENV_ALLOWLIST every request
    500'd with ENV_NOT_PERMITTED (the interpreter resolves the url
    expression at BOOT in the Process context and serves the same file +
    env with 200), and the schema-DDL bootstrap only ran when a request
    first touched the db — on the office's real corpus (30 routes) a
    fresh DB stayed at 0 tables and the first request died with an
    unrelated arity 500 instead of an honest bootstrap diagnostic
    ([vm/db] Connected was already on the boot log — the TW
    registration lane opened the file — while the VM request lane kept
    re-resolving and re-refusing per request). The fix: at VM serve boot
    (run_server + the test harness, on the compiled Program BEFORE it
    is shared) boot_resolve_db_url resolves the env name ONCE in the
    Process (boot) context — the resolved literal replaces db_url_env,
    so the request path performs NO env() reads at all — and replays the
    schema DDL eagerly on a boot connection (same open + WAL +
    DDL-tolerance steps as the lazy open), so a fresh DB carries the
    schema BEFORE the first request (the #1169 interpreter parity); an
    unset variable is a LOUD BOOT error naming the variable and the
    remedy (was: a silent OK boot — the TW registration error line was
    swallowed by the merge, then per-request 500s); a non-sqlite URL is
    the same LOUD boot error it used to be at the first access (№758
    text moved to boot time). The №259 serve-route env gate is
    UNCHANGED for program-level env() calls — the boot read is the
    Process-context class (the №259 contract, the №457 registration
    zone). The lazy per-request connection open stays for requests that
    never touch the db (№409 cost contract); reset_for_reuse semantics
    unchanged (№403 4/4). Contract tests
    metalogos-server/tests/issue_1169_1170_vm_boot_db.rs (T1: the
    db-touching route serves WITHOUT the allowlist — was 500
    ENV_NOT_PERMITTED per request; T2: the fresh DB carries the schema
    table immediately after boot; T3: the unset variable fails the BOOT
    loudly naming the variable — was a silent OK boot; T4: the
    literal-URL twin — no regression on the №758 surface); №758 5/5, №259
    7/7, №160 23/23 (1 ignored), №403 4/4, №457 5/5, №399 4/4 GREEN.
    The TeamSkillWired expects 2 args first-request symptom of #1169
    (the office corpus) is NOT reproduced minimally and is NOT claimed
    fixed — it is expected to disappear with the empty-schema state gone;
    the minimal-repro ask stands in the issue thread.

0.30.0

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 08 Oct 06:55
6eafee1

The release verdict (the 0.30 v2 gate)

GREEN at the release cut (tag v0.30.0 @ 6eafee1c — the fresh run of
unfreeze_gate.py --office-tests pass --gate-target 0.30 — exit 0, the
summary is committed in the lockstep PR #1141) and the FULL В34 form read
exit 0 on main @ 6d1aa26
(the branch-protection fact key landed after
the cut per the §5 recorded sequencing — the criterion machine-reads the
audit conclusion):

Goal Verdict Evidence
parameterized share ≥ 5000 bp (OWNER-FIXED 2026-10-07, the В34 addendum form, ADR-0186 §5) MET 6043 bp — 55/91 = 60.43% (the three honest packages: №623 7 rows → №631 27 rows → №637 21 rows)
open High findings (server path) = 0 MET the release-block counter = 0 (machine-read, never hand-zeroed)
the domain quorum MET 0/8 groups carry duplicates vs the goal 1/3
the serve-e2e inventory MET every state-accumulating declaration done
the branch-protection criterion (the В34 addendum Z-3): the LAST branch-protection-audit run GREEN MET the switches applied by the owner BEFORE the publication (the №646 request held): 25 required checks = the maintainers.md checklist verbatim, strict, enforce_admins, the PR requirement (approvals 0); the first GREEN audit run 37736590049 on fc856fb; gh#1000 closed manually 2026-10-08; the fact key fact_branch_protection_audit: GREEN landed WITH its checker (№525) in #1143

§4 (ADR-0177) GREEN across the board: types 309/516 (5988 bp floor), dedup 0
(mirrors 6/6), debt 11/0/15, the memory office-E2E evidence record GREEN.

The publication is the owner's act (docs/release-checklist.md, step 5 —
"the machine never publishes"): executed by the owner's PAT on the verbatim
chat verdict «публикуй» (2026-10-08), the v0.29.0 precedent.


Security

  • №629 (gh#1096) — the VM comparison parity (the behavior change,
    fail-closed; AFFECTS 0.29.0 AND EARLIER on the VM backend — all six
    comparison operators, including !=; the audit guard scenario
    if owner != current_user { … } with owner = Unit PASSED on the
    VM — the X-1/Y-1/Q-1 silently-wrong-guard class):
    the VM's
    ==/!=/>/</>=/<= now mirror the TW matrix 1:1 — the
    heterogeneous equality ("abc" == 5.0, "5" == 5.0), the ordering
    on non-Float pairs ("b" > "a", true > false) and the
    incomparable types (Struct/List/Html) REFUSE with the stable
    [TYPE_MISMATCH] code on BOTH backends; the legacy VM answered a
    silent false (and true for numeric strings) — the
    silently-wrong class; Unit == Unit is true on both now. Found by
    the №621 fuzzer sweep the day it stopped being vacuous; the №479
    + precedent. The fix landed on main 2026-10-07 (22d62fa — after
    the v0.29.0 tag, 4be0f72: the published tag is affected; no re-tag
    — the №620 precedent); the affected-release audit 2026-10-08 (the
    unified audit of 4830170, the dispatch gh#1136, Q-1).

Changed

  • №630 (gh#1098) — the 0.30 release-gate §5 mechanical transition
    (process, the owner-fixed merge):
    owner_fixed: true in
    gate_030_goals.txt (ADR-0186), ADR-0186 Proposed → Accepted, the
    DEFAULT gate target of unfreeze_gate.py 0.29 → 0.30; the 0.28
    and 0.29 records stay verifiable explicit targets (--gate-target).
    The typed parameter of the 0.30 gate = the parameterized share
    (≥ 5000 bp, the owner's 2026-10-07 verdict); the machine read is
    RED-loud until the movement completes — by design.

  • №631 (gh#1099) — the parameterized movement 0.30 №2 (internal):
    the second honest package of 27 verified rows — the registry-order
    contour carries List<T> / Struct<Name> spellings now (the
    memory/todo/goal/profile/mood families; the polymorphic list ops stay
    honestly coarse — the element type is the caller's). The parameter
    share 7/91 = 769 bp → 34/91 = 3736 bp; the floors raised in the
    same PR (the parameterized baseline 3736, the in-tree PARAM_FLOOR 31).

  • №627 (gh#1110) — the field-label metadata (internal, the stage-2
    preparation per ADR-0178):
    the registry specs may carry the
    Struct<Name>{field:label,...} form; the labels live in the registry
    side-table (BuiltinSpec.field_meta), the Type enum stays erased;
    a malformed section is a loud Unknown (fail-closed). The FOURTH
    metric — the field-label share among the parameterized Struct rows
    (3/20 = 1500 bp, the first honest package of 28 verified rows:
    GeoLocation 9, Weather 13, LlmUsage 6) with the checked-in floor +
    the in-tree lock. The parser mirrors (gen_metrics, gen_reference, the
    two Rust pins) synced — the generated blocks regenerated.

  • №633 (gh#1101) — the dead_code burn (internal): 33 → 15 (18
    places removed or justified), the floor re-locked in the same PR.

  • №634 (gh#1102) — the self-host lexer lane lifted green
    (internal):
    the §5 test fixture had drifted behind the language
    (let-mut, the no-entry flow, the newline in the quote class, the
    stale keywords) — repaired; the №617 artifact documented-accepted
    with the mutation-verified pin; the ignore floor 14 → 13.

  • №635 (gh#1103) — the VM template_render lane lifted green
    (internal):
    both vm_golden corpus sweeps 189/189 (the template gap
    was closed by №250; the harness facts fixed — the env sidecars №385,
    the candle-gated exclusion mirror, the browser-gated p88); the ignore
    floor 13 → 11.

  • №622 (gh#1085) — return inside a match/if EXPRESSION arm is
    captured as the arm's value on BOTH backends:
    the TW documented
    contract (the expression channel carries values, not control signals)
    now holds on the VM — let v = match x { "a" then { return 5.0 } }
    binds 5 and runs the tail instead of terminating the pattern with
    "5". The consumer scan (match arms / if-without-else / if-else /
    MatchExpr — the implicit block value never becomes an early return;
    sandbox/flow have no statement bodies in the grammar).

  • №623 (gh#1086) — the parameterized signature vocabulary stage-0
    (internal):
    the registry specs carry List<T> / Struct<Name>
    spellings (the enum erases the parameter); the THIRD metric — the
    parameterized share among the List/Struct rows (7/91 = 769 bp, the
    first honest package of 7 verified rows; the general typed share 5988
    bp, the precise share 4224 bp untouched per the Z-2 verdict).

  • **№621 (gh#1084) — the differential fuzzer executes again (internal):
    ** the sweep was vacuously green (0/150 programs ran — the stale
    builtin call, the undefined idents, the heterogeneous arithmetic); the
    generator produces the №612 loop class (the bare-call bodies, the
    mixtures, the tails, the nested shapes) and the permanent LIVENESS
    ratchet pins the executability (≥ 60% of the sweep on both backends).

  • №617 (gh#1075) — the semantic checker grows three compile-time
    refusals (the gh#967 §3 lane):
    the variable-scope walk (the TW env
    model verbatim — flat, never popped, the entities as globals, the
    render/reflex_* name-reference slots exempt), the undefined-variable
    read refusal (the runtime message verbatim + the stable
    UNDEFINED_VARIABLE code), the assignment-to-undefined refusal, and
    the static opaque-concat check (the declared-fact twin of the runtime
    guard — cannot concatenate opaque type …). The three #[ignore]d
    gh#967 §3 tests lifted green; the debt floor 17 → 14; the №465 fuzzer
    VM lane now carries the №523 semantic gate (the production order).

  • №616 (gh#1077) — the PRECISE movement 0.30 №1: 61 registry rows
    typed against the verified single-shape handler facts — the precise
    share 3042 → 4224 bp (218/516), the general share 4670 →
    5852 bp (302/516), TYPED_FLOOR 237 → 298; the floors raised in the
    same PR (the №757 procedure); the 0.29 gate fact synced (the
    machine-read rule).

  • №615 (gh#1078) — the 0.30 gate DRAFT (Proposed, ADR-0186):
    gate_030_goals.txt with owner_fixed: false — the draft parameters
    strictly above the live facts (typed 6100 > 5852, precise 4500 >
    4224, the X-3 rule); the gate reads it honestly RED on the fixation
    fact; the DEFAULT gate target stays 0.29 (the blocking CI never reads
    a draft) — the fixation is the OWNER's gate.

  • №618 (gh#1076) — the Камертон cycle closure report: the external
    review report artifact (docs/audits/kamerton-report-v0.29.0.md) —
    the SHA256 of the v0.29.0 assets re-run on the downloaded artifacts
    (3/3 OK against the attached SHA256SUMS), the R1–R6 verdicts from real
    runs of the repro corpus (scripts/repro_kamerton/, the t92
    reconstruction) on the fixed main — every probe now behaves as its
    contract demands (the mutation probe goes red: assert_eq failed: 3 != 999), the D1/D2 → PR/commits map, the affected releases
    (v0.28.1, v0.28.2 — update to v0.29.0), and the honest protocol
    corrections verbatim from t92. The forwarding to the external
    reviewer is the owner's gate (the external channel).

0.29.0

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 06 Oct 14:17
4be0f72

The release verdict (the 0.29 v2 gate)

GREEN at the release cut (main @ 4be0f72, the fresh run of
unfreeze_gate.py --office-tests pass --gate-target 0.29 — exit 0, the
summary is committed in the lockstep PR):

Goal Verdict Evidence
typed share ≥ 3500 bp (OWNER-FIXED 2026-10-04) MET 4670 bp — 241/516 = 46.70%
precise share ≥ 3000 bp (№605, the audit X-3, OWNER-FIXED 2026-10-06, ADR-0181 §3.1) MET 3042 bp — 157/516 = 30.42%; the honest RED record (2131 bp, PR #1069) landed BEFORE the movement
open High findings (server path) = 0 MET the release-block counter = 0 (machine-read, never hand-zeroed)
the domain quorum MET 0/8 groups carry duplicates
the serve-e2e inventory MET every state-accumulating declaration done

§4 (ADR-0177) GREEN across the board: types 4670 bp, dedup 0 (mirrors 6/6),
debt 17/0 + 33/33, the memory office-E2E evidence record GREEN.

The publication is the owner's act (docs/release-checklist.md, step 5 —
"the machine never publishes"): executed by the owner's PAT on the verbatim
chat verdict «Делай релизный наряд 0.29.0» (2026-10-06), the v0.28.2
precedent.

What is in the release

  • №612 (gh#1061, PR #1068) — the loop semantics fixed: a bare call in
    the body NEVER terminates the loop, the tails execute; the vacuous
    greens eliminated as a class (the test harness reads ControlFlow now —
    an early return in a test body is a LOUD failure, the corpus scan:
    zero test bodies use explicit return). Pre/post on the honest-count
    program: 1 → 3. The TW↔VM parity e2e pinned (the honest count 3 on
    BOTH backends).
  • №611 (gh#1060, PR #1067) — the schema-as-code references fixed: the
    qualified name parsed in EVERY spacing (the dot is the separator —
    references(parent.id) no longer drops the modifier silently, the
    broken REFERENCES parent(.) clause is impossible); mlog check
    validates the generated DDL on an in-memory SQLite dry-run over the
    SSOT renderer (ast::schema_table_ddl) — a DDL SQLite rejects fails
    the check LOUDLY, never the apply.
  • The 0.29 gate movement: №605 (gh#1046, PR #1062) — the X-3
    precise-share goal goal_precise_share_bp: 3000 OWNER-FIXED
    (ADR-0181 §3.1), the coarse goal stays the floor; №613 (gh#1070,
    PR #1071) — 47 verified PRECISE rows, 2131 → 3042 bp, the floors
    raised in the same PR per the №757 procedure (precise 3042, general
    4670, TYPED_FLOOR 190 → 237); the between-releases enforcement is the
    precise-baseline ratchet (the share may ONLY GROW).

The owner's decisions archived (the records)

  • ADR-0184 (№603, gh#1044, PR #1063) — the depth-≥2 respond boundary:
    REFUSED FOREVER, variant Б. RESPOND_SWALLOWED stays forever — the
    bare respond*/respond_html* form at depth ≥ 2 is refused at startup
    on BOTH backends; the migration remains one word (return respond(...)).
  • ADR-0185 (№609, gh#1050, PR #1066) — the memory_forget container
    form: DEFERRED to the memory-phase planning (postponed, NOT
    rejected; the return trigger is the memory-phase planning).

Process

  • №608 (gh#1049, PR #1064): the Y-4 quota line in the wave report — the
    counter learns the real formats, the --line mode; the quota is
    machine-counted on every wave report.
  • №610 (gh#1051, PR #1065): the newcomer path — the broken first step
    fixed (the clone URLs), the live pool list rendered from
    gh#951–gh#959, 9/9 acceptance criteria.
  • The 0.28.2 post-publication sync (PR #1059): fact_open_high_server
    closed at 0 on the live tag, REALITY §6.17 → PUBLISHED.

The full verbatim wave records: CHANGELOG [0.29.0]; the release prep:
REALITY §6.18.


The assets (attached by the release pipeline, release: published →
the tagged commit is built):

  • mlog-linux-x86_64 — the release binary (cargo build --release --locked -p metalogos-server --bin mlog; the exact toolchain is
    recorded in BUILD-INFO.txt).
  • mlog-linux-x86_64.cdx.json — CycloneDX SBOM describing exactly that
    binary.
  • SHA256SUMS — digests of the binary, the SBOM and BUILD-INFO.
  • Two Sigstore attestations via GitHub artifact attestations: the SLSA
    build provenance and the SBOM attestation bound to the same binary
    subject.

Built from the tag v0.29.0 (4be0f72), the version lockstep 0.29.0 in
the workspace manifests (№614, gh#1072, PR #1073 — the №583 pattern:
Cargo.toml + Cargo.lock ×5 members + the README badge + the generated
SSOT blocks).


The post-publication fact sync: REALITY §6.18 release state →
PUBLISHED — the syncing PR #1074 (merged
2026-10-06; ADR-0181 §4.1 step 1 — the syncing PR linked from the
release notes). The gate facts unchanged by the release:
fact_open_high_server 0 (no release-block carriers opened), the
precise share 3042 bp (the ratchet holds — only up), the quorum 0/8.


Security (ADVISORY — restart your tests)

0.28.x and earlier are affected on the TW interpreter — the fix ships IN
0.29.0; users of earlier versions should re-run their existing tests on
≥ 0.29.0.
The Камертон matrix finding (the unified audit of e40ce8e /
v0.29.0, the dispatch gh#1089) traced the №612 loop defect to its
user-facing consequences: until 0.29.0, a call with a non-Unit result at
the END of a loop body was read as an early Return of the ENCLOSING
function — the loop ran exactly ONE iteration, the tail after the loop
was silently cut, and in mlog test bodies the harness flattened the
fabricated Return into Ok — a GREEN run that never executed the
checks. The return here was FABRICATED by the implicit block-value
mechanics, not written by the author — the dictionary class «a
respond*/deny*/return being IGNORED» (№604), mirrored: a return being
INVENTED.

each row in rows { db_execute("INSERT …", row) } — with the INSERT
call last in the body, the first iteration returned from the function:
one row inserted, the rest skipped, exit code 0.

  • The guard patterns with an audit loop: a loop that re-checks a
    condition (audit/retry/scan) stopped after the first iteration — the
    code after the loop, the second half of the guard, never ran.

  • The batch inserts/updates: a loop over rows executed exactly ONE
    element — the remaining rows were silently skipped (no error, no
    warning, exit code 0).

  • The false confidence of vacuous green runs: in mlog test, a test
    whose body tripped the shape reported GREEN while its asserts never
    executed. The harness reads ControlFlow NOW (№612): an early
    termination is a LOUD failure — a green run on ≥ 0.29.0 means the body
    ran to completion.

  • Affected: mlog run, mlog test, and mlog serve with
    METALOGOS_SERVE_BACKEND=interpreter (the fallback backend; the serve
    DEFAULT is the VM) — on every published release BEFORE 0.29.0. The VM
    backend is NOT affected. The fix landed in 0.29.0 (№612, PR #1068).

  • The user action: restart your existing tests on ≥ 0.29.0; re-check
    the batch insert/update loops and the guard patterns with audit loops
    written before 0.29.0. The fix/audit date: 2026-10-06/07.

0.28.2

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 06 Oct 04:14
c694b6d

Security (UPDATE REQUIRED)

0.28.1 and earlier are affected on BOTH backends — update required. The В31 corrective wave (the unified audit of 25b375e / v0.28.1, the dispatch gh#1052) found the audit §3 Y-1 regression (High): №584 retired the RESPOND_NOT_TERMINAL gate for ALL bare-respond forms at once — including the ONE form that never worked. The audit's guard scenario:

a guard if is_admin(...) == false { respond("403") } nested inside another block-form if — db_execute("DELETE …") executed for a NON-administrator, both backends answered 200 purged; the compiler gave only a style warning, and the honest limitations.md row did not reach the user.

  • Y-1 (High) — the swallowed respond regression (№600, gh#1041, PR #1054):* a bare respond*/respond_html* call carried by a statement NESTED under a top-level block-form if/else branch (depth ≥ 2) does NOT stop the route on EITHER backend — the serve lane's per-statement branch walk discards nested-statement responses and the branch continues; №584 had lowered the whole early-answer surface at once and this form lost the fail-closed refusal it had under №581. Fixed: the blocking RESPOND_SWALLOWED semantic error refuses every such program at startup on BOTH backends again (fail-closed, the №581 posture; the SSOT RespondPosition predicate is shared by the semantic walk and the №584 lowering — the compiler stays the reference enumerator). The migration is one word — write return respond(...) — the same shape the advisory already recommends. The fix/audit date: 2026-10-05/06.
  • The repair rider (the honest-boundary finding of the №601-mandated test, PR #1054): the TW serve lane's top-level branch walk discarded an explicit return respond(...) carried by a NESTED statement (the ControlFlow::Return signal was flattened into a plain value and dropped) while the VM answered it — the migration path above was silently broken on the TW since №584. Fixed in the same PR: the branch walk now propagates ONLY the explicit Return signal; plain tail values stay discarded (the etalon; the depth-≥2 semantics question itself is the owner gate №603).
  • The test contract rewire (№601, gh#1042, PR #1054): the depth-2 swallow shape now expects the startup refusal with the stable [RESPOND_SWALLOWED] code; the audit §3 guard scenario is pinned as a negative test plus its one-word migration end-to-end on both backends (403 to the non-admin BEFORE the protected code, 200 + the marker to the admin); the №584 early-answer surfaces stay green; the predicate is mutation-verified at both transition points.

The release-block label discipline (ADR-0179 §4) held until this live tag: fact_open_high_server 0 → 1 (the №600 carrier) → 0 (the release naryad №602's closure on the tag).

The wave records riding this release (the В29 tail + В30, verbatim) are in the CHANGELOG [0.28.2] section — the branch-protection audit job (№586), the wave-30 Камертон enrichment (№590–№596), the post-wave docs syncs.


The assets (attached by the release pipeline, release: published → the tagged commit is built):

  • mlog-linux-x86_64 — the release binary (cargo build --release --locked -p metalogos-server --bin mlog; the exact toolchain is recorded in BUILD-INFO.txt).
  • mlog-linux-x86_64.cdx.json — CycloneDX SBOM describing exactly that binary.
  • SHA256SUMS — digests of the binary, the SBOM and BUILD-INFO.
  • Two Sigstore attestations via GitHub artifact attestations: the SLSA build provenance and the SBOM attestation bound to the same binary subject.

Built from the tag v0.28.2 (c694b6d), the version lockstep 0.28.2 in the workspace manifests (№602, gh#1043, PR #1055 + the lockstep PR #1058).


The post-publication fact sync: the release-block carrier (#1041) closed on this live tag per its own condition; fact_open_high_server 1 → 0 in both goals files and the REALITY §6.17 release state → PUBLISHED — the syncing PR #1059 (merged 2026-10-06; ADR-0181 §4.1 step 1 — the syncing PR linked from the release notes).

0.28.1

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 05 Oct 04:26
d5af542

Security (UPDATE REQUIRED)

0.28.0 and earlier are affected on the VM backend (mlog serve runs the bytecode VM by default since 2026-09-21) — update required. The consolidated audit d63cc1d (2026-10-04) found two server-path divergences; both were honestly named in the b678def commit body (№574.1) before the fix existed, and both are closed by the wave-29 security wave (the dispatch gh#1004; the wave-29 rule: the gates read the truth — fact_open_high_server 0 → 2 → 1 → 0).

  • X-1 (High) — the guard bypass (№581, gh#995, PR #1007): a bare respond*/respond_html* call in a NON-tail position of a route body did NOT stop route execution on the VM backend — the TW answered immediately from any block, the VM compiled the call with Instruction::Pop, so the guard pattern if is_admin(...) == false { respond("403", "forbidden") } silently FELL THROUGH to the protected code (an unauthorized caller reached the protected handler). Fixed: the RESPOND_NOT_TERMINAL semantic gate refuses every such program at startup on BOTH backends (fail-closed); the migration is one word — return respond(...). The fix/audit date: 2026-10-04.
  • X-2 (Medium) — the leftover-local data leak (№582, gh#996, PR #1008): a route/branch body whose final statement is a let/assignment made the VM's route epilogue pop the LAST LOCAL SLOT and serialize it as the response body (route { let row = query_row(...) } returned the row to the caller; the interpreter answered the plain 200 OK). Fixed: the PushUnit route-epilogue invariant — the body exit never reads a local slot; the fall-through answers the same shared default as the interpreter lane. The fix/audit date: 2026-10-04.

The honest-boundary trail: b678def (№574.1) named both divergences in the commit body; the wave-29 dispatch (gh#1004) made the gates read that truth; the release-block label discipline (ADR-0179 §4) held until this live tag. The stage-2 parity step (the VM serves respond in ANY position — №584) and the route-body differential-fuzzer lane (№585) follow in the 0.29 cycle.

Assets and provenance

  • mlog-linux-x86_64 — the release binary (cargo build --release --locked -p metalogos-server --bin mlog; the exact toolchain is recorded in BUILD-INFO.txt).
  • mlog-linux-x86_64.cdx.json — CycloneDX SBOM describing exactly that binary.
  • SHA256SUMS — digests of the binary, the SBOM and BUILD-INFO.
  • Two Sigstore attestations via GitHub artifact attestations: the SLSA build provenance and the SBOM attestation bound to the same binary subject.

Built from the tag v0.28.1 (d5af542), the version lockstep 0.28.1 in the workspace manifests (№583, gh#997, PR #1010).


The post-publication fact sync: the release-block carrier (#997) closed on this live tag per its own condition; fact_open_high_server 1 → 0 in both goals files, the [0.28.1] CHANGELOG date, the REALITY §6.14 release state — the syncing PR #1024 (merged 2026-10-05; ADR-0181 §4.1 step 1 — the syncing PR linked from the release notes).

0.28.0

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 03 Oct 15:46
3576887

Security

The audit-driven security release: the v0.27.1 tag (2026-09-29)
predates every fix below. v0.27.1 and earlier are affected — update is
mandatory.

  • №523 (gh#832; the consolidated audit A+D 30.09, N-1, High):
    semantic findings now BLOCK mlog run and mlog serve — the
    liar-string security-check bypass is closed. v0.27.1 blocked only two
    message-substring classes (contains("distill_to"),
    contains("[DENY_")), mlog serve ran NO semantic pass at all, and
    an unknown function returned the STRING
    "[ERROR: unknown function 'x']" as the call's VALUE — a non-empty
    string is truthy in is_truthy, so if is_admn(user) with
    is_admn undefined took the then-branch: the security check became
    its own bypass (a typo in a function name was enough). Now every
    semantic::check_program error blocks the run path and the serve
    startup (exemptions classify ONLY by the structured error kind in
    ONE explicit place — never by message substring), the unknown
    function is the coded [UNDEFINED_FUNCTION] compile error, and the
    refusal carries the stable code at position 0. The tree-walking
    runtime was the affected path; the VM backend's compile stage
    already refused the class — the exploit shape is now refused on BOTH
    backends (pinned in tests/naryad_523_semantic_gate.rs).
  • №526 (gh#835; the consolidated audit A+D 30.09, N-2): the GDPR
    Art. 17 erasure path for voiceprints LANDS — voice_delete(handle)
    (idempotent; the persisted twin runs the SECURE-DELETE path: the
    ciphertext blob is zero-overwritten in place BEFORE the row removal)
    and voice_list() (the informed-deletion basis; the listing never
    decrypts, the embedding bytes never enter any result). Neither is
    feature-gated — the erasure right cannot depend on a build flag.
    v0.27.1 and earlier had ONLY insert/replace: a persisted print
    outlived every consent revocation ("Delete path: ABSENT",
    privacy.md §2.1).
  • №527 (gh#836; the consolidated audit 30.09, N-3): the voiceprint
    ciphertext is BOUND TO ITS SUBJECT — the AES-256-GCM AAD carries the
    (subject_id, registry, schema version) triplet, so a blob
    transplanted onto another subject's row FAILS authentication (the
    swap attack the bare tag accepted is closed), and the decoded key
    buffer lives under Zeroizing. The transitional empty-AAD read
    announced by №527 existed ONLY on main — its deadline is honored IN
    THIS RELEASE: the fallback is removed, a legacy row refuses with
    [VOICEPRINT_DECRYPT], the only path back is re-enroll/re-save. No
    released version ever wrote a legacy row (the GCM store itself ships
    in 0.28.0).
  • №517 (gh#801) + №512 (gh#796; the consolidated audit 28.09
    C-06):
    the honest at-rest crypto for voiceprints — real
    AES-256-GCM with a fresh random 96-bit nonce per write, the key
    ONLY through the secret()-gate semantics (env-sourced hex-256,
    never derived from the name), a keyless save fails closed
    ([VOICE_INSECURE_STORE]); №512 removed the FAKE crypto claims
    first (the pre-№517 store's schema said AES-256-GCM while the bytes
    were name-XOR plaintext). v0.27.1 and earlier persisted voiceprints
    without honest at-rest encryption.

Changed and fixed

  • Release supply-chain assets (CI infrastructure; no language or runtime
    change): the new .github/workflows/release.yml runs after the owner
    publishes a GitHub Release and attaches the mlog binary, a CycloneDX 1.5
    SBOM that describes exactly that binary (cargo-cyclonedx 0.5.9, pinned),
    BUILD-INFO.txt (exact toolchain) and SHA256SUMS, plus two Sigstore-signed
    attestations (SLSA build provenance
    and the SBOM bound to the binary). Until now releases were a tag and notes
    with no assets. Honest limits: reproducibility not claimed or verified, glibc of the
    pinned ubuntu-24.04 runner, no human-held key; releases up to v0.27.1 have
    no assets.
    Verification recipe: SECURITY.md → "Verifying release artifacts";
    operator step: docs/release-checklist.md step 6.

  • The №527 deadline honored in the release prep (issue #836 — the
    limitations.md TRANSITION row, closed in this PR per the №524 rule):
    the transitional empty-AAD read (the second decrypt attempt) is
    REMOVED — a legacy №517-era voiceprint row refuses with
    [VOICEPRINT_DECRYPT] (the message names the legacy possibility and
    the path back: re-enroll/re-save). VoiceprintCryptoStatus loses
    the LegacyNoAad variant (no producer remains); the
    status-returning load keeps its signature (AadBound for every
    readable row). Tests: tests/naryad_527_voice_aad.rs — T2 (the
    legacy row refuses after the deadline), T3 (re-enroll/re-save is
    the ONLY path back; the rebound row refuses the swap again), T4
    (the single-attempt wrong-key refusal); T1/T5 unchanged. No released
    version ever wrote a legacy row — the transitional population was
    main-only.

  • Naryad №550 (issue #911; Wave 25 P0, the release prep): the
    unfreeze-gate DEFAULT target is 0.28 — a bare unfreeze_gate.py run
    now checks the 0.28 ABSOLUTE goals (ADR-0179 §5, the v2 gate)
    instead of the legacy §4-only reading; the legacy reading stays
    available explicitly (--gate-target legacy). The workspace version
    is 0.28.0; the README version badge is re-rendered from Cargo.toml
    (the №497 badge-sync gate).

  • Naryad №551 (issue #912; Wave 25 P0; the audit 02.10 M-1 + §6.2): the
    "merge without CI" class closes on both halves. The POLICY half:
    docs/maintainers.md "When CI is down" — the verbatim audit rule ("No
    merges while GitHub Actions is degraded. A merge without a green
    required-check run is a process violation, not a judgement call"),
    the unverified-≠-green posture ("no red X" is not "green" — the dead
    event delivery of 30.09 looked exactly like that), and the
    branch-protection CHECKLIST for the owner (require PR + the required
    set fact-checked against the ci.yml display names + up-to-date +
    no-bypass; applying it is an admin action the executor cannot
    perform). The MACHINE half: scripts/ci/merge_ci_audit.py + the
    scheduled merge-ci-audit workflow (Mondays 05:00 UTC) — every main
    merge of the last 7 days is re-checked against a GREEN run of the
    required set on its PR's head SHA; a skipped check is not green, an
    unfetchable PR head is a divergence (fail-closed: unknown is never
    green), a divergence = a red run + an automatically filed issue with
    the list; the script only reports (no merge/revert/history edits).
    The first scheduled run is EXPECTED to flag the documented 30.09
    outage merges — the issue asks for verdicts, not for panic. Pinned by
    tests/naryad_551_merge_ci_audit.rs (the fixture self-test driver +
    the required-set sync between the script, the test and the
    checklist).

  • Naryad №566 (issue #928; Wave 25 P2 tail; the audit 02.10 §7.2 W-4):
    the grantors' digest stops lagging the machine metrics —
    docs/PLAN-SUMMARY.md's CURRENT numbers are GENERATED by
    scripts/gen_metrics.py (the same sources as the README metrics block:
    one SSOT, one process computes both, they cannot disagree). The
    audit's exact finding is fixed first: the digest's wave-24 section
    claimed "now 2110 bp" while the live floor is 3195 bp (162/507 =
    31.95%, precise 83/507 = 16.37% — the №560 second share side by
    side); the hand-written current number is GONE, the generated block
    (between explicit markers, the №460 posture) carries the floor and
    the registry size instead — the narrative stays hand-written (the
    №566 boundary: the digest's prose is the publisher's voice; the
    historical wave-23 snapshot "floor 998→2110 bp" is a frozen fact and
    stays). gen_metrics.py --check now verifies BOTH targets and the
    blocking metrics job runs it — a future drift is CI-red by
    construction. Pinned by tests/naryad_566_plan_summary_numbers.rs
    (3 tests): the digest block == an INDEPENDENT recomputation from
    registry.rs (the exact type_signature_share.py regex contract),
    the generator --check green right now, the stale "now 2110 bp"
    claim must not return.

  • Naryad №565 (issue #927; Wave 25 P2 tail; the audit 02.10 §7.2 W-3):
    the TWO-ARGUMENT respond_html(a, b) form is deprecated — its sense
    depends on the CONTENT of the first argument (a first token 100..=599
    reads as an HTTP status, anything else as a document title: the
    audit's respond_html("200 причин выбрать нас", body) served HTTP 200
    and silently dropped the title; a DB- or LLM-sourced string would
    decide the response's meaning). THE EXPLICIT FORMS LAND:
    respond_html_status(status, body) — the body verbatim at the given
    status; the status must open with an HTTP code in 100..=599 or be a
    whole number 100..=599, otherwise a LOUD argument error (no legacy
    silent-200 fallback — the №514 rule); respond_html_doc(title, body) — the title is never read as a status and never dropped, the
    same full-document shape as the office form (№892). Both serve
    Content-Type: text/html; charset=utf-8, both are typed in the
    registry ("Struct" — the HttpResponse shape), classified as
    Sink/Public/Irreversible, and ride EVERY HTML egress gate exactly
    like respond_html (LLM-output, open-redirect, secret-leak,
    recall-taint — the explicit name is not a bypass). The deprecation
    carries a WARN-ONLY semantic pass (the №474 posture): every 2-arg
    call whose first argument is NOT a string literal is announced with
    both migration paths; a literal first argument keeps working
    silently this release; removal no earlier than one release with the
    warning. The 1-arg form is the UNCHANGED SSOT contract (gh#899).
    The floors moved WITH the rows that earned them (the №757
    procedure): the general typed floor 3195 → 3241 bp (165/509), the
    precise floor 1637 → 1650 bp (84/509 — query_param ships typed
    "String", both paths verified), the in-tree compiled lock
    158 → 161. Pinned by tests/naryad_565_respond_html_forms.rs (13):
    the explicit forms on BOTH backends (T...

Read more

0.27.1

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 29 Sep 10:54
5b79996

Security

The audit-driven security release: the v0.27.0 tag predates every fix
below. 0.27.0 is affected — update is mandatory.

  • №510 (gh#794; the consolidated audit 28.09 C-01, High): != in
    rule conditions, flow branch conditions and mutate … rollback_if
    worked as == on the VM (the compiler wildcard arms) — and the rule
    path could not even parse (the grammar deadlock: every comparison
    operator, not just !=, failed to parse in rule conditions); the VM
    rollback kept-table disagreed with the TW for Gt/Ge/Eq/Ne. 0.27.0
    is affected — update is mandatory.
  • №474 (gh#722; the audit 26.09 §3.2, High): the db parity —
    query_row_vm parameters route through the same parameterization as
    the TW lane (convert_params), closing the silent parameter shift.
  • №475 (gh#723; the audit 26.09 §3.1, High): the fs_gate facade —
    one gated FS entry point + the clippy.toml disallowed-methods
    ratchet; the pdf/smtp/config bypass paths are gone.
  • №477 (gh#725; the audit v0.26.1 §3.5): the mcp-serve context
    inversion — the strict-by-default exec context at the entrypoint.
  • №478 (gh#726; the audit v0.26.1 §3.6 + the 27.09 action): the mock
    unification onto the 454 SSOT + the loud env/read_file with the
    explicit-silence *_or naming.
  • Also landed since v0.27.0: №481 (the loud env refusal), №482 (the
    unfreeze-gate CI job), №483 (the media/vision dedup ratchet at zero).

Changed and fixed

  • Naryad №510 (issue #794; the consolidated audit 28.09 C-01, Wave 21
    P0): the != operator is honest end-to-end. THREE defects, one
    class — comparison operators silently degrading, each invisible to
    the gates for its own reason. (1) The compiler wildcard: AstCompareOp
    match arms in mutate rollback_if, the flow branch conditions and
    compile_rule ended with _ => ConditionOp::Eq // Ne and others fall back to Eq — CompareOp has exactly six variants, so the wildcard
    hid exactly Ne: rollback_if: accuracy != 0.9 and flow branches
    cold(x.status != "b") compiled as Eq on the VM backend. All three
    arms are explicit now, and #![deny(clippy::wildcard_enum_match_arm)]
    stands at the top of src/compiler.rs — the lint surfaced 16
    wildcard sites in the file (rules, branches, rollback, BinOp
    fall-through, Statement loops, Expr simplifications, the JIT purity
    disallow-mirror); every one is an explicit variant list, so a new
    enum variant is a compile error until it is consciously classified.
    (2) The grammar deadlock the compiler fix exposed: the
    compare_condition rule (expression ~ compare_op ~ expression)
    could NEVER match — the greedy left expression layers comparisons
    as infix (compare_expr), consumed the whole a != b and left no
    compare_op for the condition. rule If(...) with ANY comparison
    operator (not just !=: >, >=, <, <=, == too) failed to
    PARSE — only contains worked; the consolidated audit's premise
    ("rules accept !=") was lexically true and structurally dead, so
    the compiler wildcard was unreachable dead code on the rule path.
    The parser now accepts an expression-shaped condition and decomposes
    its top-level comparison into Condition::Compare; anything else at
    the top level is a loud parse error. The documented syntax
    (rule If(target.field op value)) now matches reality. (3) The VM's
    mutate kept-table disagreed with the TW interpreter for every
    operator except Lt/Le: Gt/Ge ALWAYS rolled back, Eq was inverted,
    Ne was missing (the always-keep default). The table now mirrors
    interpreter/hooks.rs exactly (kept = NOT(accuracy OP threshold)).
    The diff-fuzzer №465 generated zero rules — the seeded rule block
    (all six operators, deterministic per seed) now rides the program
    output through the flow head, so the class stays visible to CI
    forever. Verified: the №510 contracts C1–C10 green on BOTH backends
    (rule fire/silence, branch taken/not-taken loud consistency,
    rollback kept/rolled-back across !=/>/==); the fuzzer suite
    green with the rule generation; clippy -D warnings clean.

  • Naryad №505 (issue #788; the audit 28.09 §3.5): the naryad number
    uniqueness gate — scripts/ci/naryad_number_check.py + the
    Naryad numbering (blocking) CI job next to the adr-check. Under
    one number different works have already lived (№474, №475, №476,
    №493 — the external audit stumbled on it twice): the gate parses
    the PR title's naryad claim and refuses a number that already
    landed as a DIFFERENT work, listing the occupied works. The
    exception grammar is explicit and pinned by the script's self-test:
    the work-group suffix (№466 group M), the dotted re-issue
    (№475.1), the re-land suffixes (reland/retry/revert/rerun/redo/
    take-N) and the same-issue follow-up (the occupied record pointing
    at the same issue #M). The occupied set comes from the merged
    git history (the EN naryad N and RU Наряд №N subject forms) and
    the closed issue titles via GH_TOKEN (best effort — the history
    alone suffices). The four known collisions are documented in the
    naryad's report so external links stop confusing them. The script
    does not rewrite history and does not rename old naryads.

  • Naryad №495 (issue #780; the audit 28.09 §3.1 High, the P0 core of
    Wave 19): distillation in mlog serve now WORKS on both backends —
    the new server-level DistillHub (src/distill_hub.rs). Before
    this, the per-request fresh program context destroyed every
    recorded example with its request on the TW lane and the VM pool
    reset distill_states on every check-in, so the training threshold
    max(distill_after, 10) was unreachable in serve by construction,
    silently, with no error and no audit event. The hub holds the
    examples, the TEACHING/DISTILLED modes, the trained-model registry,
    the in-flight flags and ONE background training thread per process
    (the №489 posture brought to serve — the VM lane stops training
    synchronously in-request); both backends call it through the
    DistillAccess trait (the DbAccess sample, №474/№484), which also
    collapses the TW/VM distill mirrors — simple_embedding now has
    ONE canonical copy (byte-identical by construction, the №502 mirror
    count moves down). Examples persist in the SQLite table
    distill_samples (the №166 plan shape, with the source
    provenance column) in the server's memory-persist file; weights keep
    persisting through reflex_save. Migration: existing saved weights
    read as before; the example ledger starts accumulating from zero
    (stated honestly here — no backfill exists). Fix-in-shared-point
    corollaries the work surfaced: the TW route body executor now builds
    its context through the SAME fresh_program_context the ticks use
    (the hand-rolled copy had already drifted), and load_program
    registers the learnable table from the program's declarations —
    before it, a VM route body calling a learnable pattern failed loud
    with "learnable index N not found" (main_code never runs on serve);
    the №496 e2e (the audit's headline recommendation) drove both
    findings out. Verified: the e2e green on BOTH serve backends
    (TEACHING accumulation → the audit events distill.training-* →
    DISTILLED answers with the LLM counter frozen → the accumulated
    examples survive a server RESTART); both rollback mutations (hub
    attach off, VM injection off) turn the e2e red; lib 872/0; clippy
    -D warnings clean.

  • Naryad №500 (issue #783; the audit 28.09 §3.2, the hardening core
    of Wave 19): the third-party path-APIs no longer bypass the №475
    filesystem facade. Every pdf load/save site (13 across
    pdf_fill_form, pdf_rotate_page, pdf_delete_pages,
    pdf_extract_images, pdf_merge, pdf_split, pdf_metadata,
    pdf_set_metadata) routes through crate::fs_gate: reads go
    read_bytes(path, purpose) → LopdfDocument::load_mem, writes go
    save_to(&mut Vec) → write_bytes(output_path, purpose) — the
    library's own file open (invisible to any lint) is gone, and the
    audit's three vectors are dead: overwriting app.mlog (the hard
    write deny), writing /home/app/.ssh/authorized_keys (the sandbox
    absolute-path refusal), reading /srv/other-tenant/contract.pdf
    (the read gate). The №500 ratchet extends clippy.toml
    disallowed-methods to the library path-APIs themselves
    (lopdf::Document::load/save, rusqlite::Connection::open,
    image::open, std::fs::copy/rename/remove_dir_all/ create_dir_all) — the explicitly-justified service sites (the
    weights store, the memory/KG/checkpoint journals, the reminder db,
    the doctest harness, vector.rs's post-sandbox open) carry named
    #[allow]s; the pdf output dirs create through the new
    fs_gate::create_dir_all (the full write-gate vocabulary on a
    DIRECTORY path). The write-side canonical re-check mirrors the read
    side's documented one: a symlink named innocently but pointing at
    the application image is refused BY THE POLICY (naming the swap)
    before the OS's O_NOFOLLOW does — pinned by the blocking
    reproductions (7 tests: the three vectors, the .. traversal, the
    symlink swap, the deny-list read, and the legit
    pdf_set_metadata round-trip proving honest pdf scenarios
    unchanged). Verified: grep of direct lopdf path-APIs in pdf.rs = 0;
    mutation checks (raw-write injection → the vectors turn red);
    lib 872/0; clippy -D warnings with the extended list clean.

  • Naryad №506 (issue #789; the audit 28.09 immediate item):
    docs/limitations.md gains the three honest rows the audit asked
    for, before the 0.27.1 tag. (1) Distillation in mlog serve does
    NOT accumulate examples between requests — the per-request fresh
    program context resets the TEACHING state, distill_after is never
    reached across requests (minimal ClassifyIntent example; repair:
    №495). (2) The two pinned ok↔err outcome divergences are
    user-visible as different mlog run vs mlog serve behavior —
    `m...

Read more

v0.27.0 — the unfreeze release (the owner lift, gh#680)

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 27 Sep 02:15
0d1cce9

The unfreeze release — the ADR-0177 domain freeze is lifted by the owner's explicit decision.

The freeze window (ADR-0177 §6) closes with this release. The lift is recorded by the owner in the gate thread gh#680 (the lift record comment 5851623870, 2026-09-27; the office-channel trace_id 1a0e06d976daf0c9); the ADR Status is updated by a naryad, not silently (№493, PR #752). All four §4 criteria carried their evidence at the lift (REALITY §6.12): the enum Type stages 0 and 1 (№467, №474), the TW/VM dedup 20/20 — one-way down from 60 (№462, №466), the debt gate 85/49/37, exit 0 (№468), and the FO-056 memory office E2E on 0.26.1 — 16/16 + 3/3 (№475).

What opens with the unfreeze:

  • the media/vision dedup residue (the 20 names) and the physical crate split move forward per the №472 roadmap (docs/refactoring-split-plan.md);
  • the domain work resumes under the standing strategic discipline — the gates and the ratchets stay, the freeze does not.

The release naryad: №494 (issue #753, PR #754, squash 0d1cce9). The previous release: v0.26.2 — Wave 18, the unfreeze path (the §4 criteria finish).

Full Changelog: https://github.com/ShkodnikAI/Metalogos-/blob/main/CHANGELOG.md

v0.26.2 — Wave 18: the unfreeze path (the ADR-0177 §4 criteria finish)

Choose a tag to compare

@ShkodnikAI ShkodnikAI released this 26 Sep 23:19
08833bf

The Wave 18 release — the unfreeze path. The closing docs release
(naryad #476, issue #744, strictly last; PR #750).

  • The №466 dedup transfer completes (the groups 5–7 of the dispatch
    #745): the audit-ledger five, the recipe pair and the server/runtime
    eight leave both backends into the shared live modules
    (src/audit_ops.rs, src/recipe_ops.rs, src/runtime_ops.rs); the
    №462 counter moves 35 → 20 (60 at the start) — the remaining 20
    names are exactly the media/vision cluster riding the 0.27 crate
    split. Every transfer PR carried the №465 diff-fuzzer before/after
    proof (the divergence classes unchanged).
  • The enum Type stage 1 lands (naryad #474, PR #749): the let-type
    inference, WARN-ONLY (the №467 canon) — the [stage1 types]
    diagnostics in mlog check; no program is rejected; the runtime is
    unchanged.
  • The FO-056 evidence refresh (naryad #475): the memory-office E2E
    dogfood re-run on 0.26.1 — green (16/16 + pytest 3/3), recorded in the
    gate thread gh#680.

The ADR-0177 §4 unfreeze criteria all read GREEN with the evidence
pointers (REALITY §6.12). The LIFT belongs to the owner alone (recorded
in gh#680). 0.27.0 = the release of freeze-lifting — the tag is NOT
published until that explicit lift.

Docs: REALITY §6.12, PLAN-SUMMARY (the Wave 18 row), CHANGELOG 0.26.2.