Repository navigation
0.28.0
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 BLOCKmlog runandmlog serve— the
liar-string security-check bypass is closed. v0.27.1 blocked only two
message-substring classes (contains("distill_to"),
contains("[DENY_")),mlog serveran 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 inis_truthy, soif is_admn(user)with
is_admnundefined took the then-branch: the security check became
its own bypass (a typo in a function name was enough). Now every
semantic::check_programerror 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 intests/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)
andvoice_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 underZeroizing. 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.ymlruns after the owner
publishes a GitHub Release and attaches themlogbinary, a CycloneDX 1.5
SBOM that describes exactly that binary (cargo-cyclonedx 0.5.9, pinned),
BUILD-INFO.txt(exact toolchain) andSHA256SUMS, 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
pinnedubuntu-24.04runner, 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.mdstep 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).VoiceprintCryptoStatusloses
theLegacyNoAadvariant (no producer remains); the
status-returning load keeps its signature (AadBoundfor 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 bareunfreeze_gate.pyrun
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
scheduledmerge-ci-auditworkflow (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 --checknow 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-ARGUMENTrespond_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'srespond_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
likerespond_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 (TW + VM flows), the loud
strictness, the warning fires on non-literals in patterns AND route
bodies, no warning for literals, HTML_INJECTION/SECRET_LEAK through
the new names, the registry facts. The corpus: 241 programs, 0
warnings; registry-vs-corpus 711 pairs OK; REFERENCE regenerated
(509 builtins, 0 TODO(doc)). The office migration snapshot: the
public corpus carries exactly one 2-arg call site
(examples/p_p01_respond_html_query_param.mlog — two literal-status
calls) → the migration targets respond_html_status; the private
office corpus rides the №559 call-shapes snapshot per the dispatch. -
Naryad №564 (issue #926; Wave 25 P2 tail; the audit 02.10 §7.2 W-2):
the mirror metric sees the WHOLE src/ tree — the №502 counter
(vm.rs-only) widens tosrc/**/*.rsbecause the mirror CLASS is not
vm.rs-specific: html_label.rs (№544.3) carries two taint-walk mirrors
("the parity demands the EXACT mirror, quirks included") that the
narrow scan never saw — the same class as M-8 (№558). The fixed
regex gains the §7.2 audit-sketch markers (mirror of the …,
exact mirror) beside the №502 pair (continuity:ported verbatim,
TW-identical,same as the TWstay in the set). The widened fact
is fixated ONCE — 7 mentions (5 in html_label.rs, 1 in audit.rs doc,
1 vm.rs №456 banner; the vm.rs fact under the narrow patterns stays
0) — in the newscripts/ci/src_mirror_baseline.txt(the
vm.rs-only baseline retires; the samevm-mirrors (blocking)CI
job, the same unfreeze-gate dedup criterion, movement ONLY down).
The REGISTRATION (the naryad item 3, for the stage-3 typing gate):
both html_label.rs walks are tracked mirrors — the BINDING walk
(binding_label → binding_taint + get_expr_taint shape) and the SINK
walk (sink_arg_is_untrusted → expr_is_llm_tainted +
expr_is_learnable_tainted, depth-bounded 3 per №295) — with the
unification requirement recorded (ONE lane-parameterized walk,
SECRET/SQL/HTML); html_label.rs itself is NOT touched (the naryad
boundary). docs/maintainers.md gate list and
scripts/ci/unfreeze_gate.py moved to the new baseline in the same PR. -
Naryad №563 (issue #924; Wave 25 P2; the audit 02.10 "the rest"
line): the read_file soft-missing debt is 5 → 0 — the №531
transition ENDED with v0.28.0 andread_fileof a MISSING file now
refuses LOUDLY with[IO_ERROR]on BOTH backends (the refusal names
the explicit-fallback surfaceread_file_or(path, default)). The
№254 empty-string contract is gone with the transition release; the
№514 rule (gh#798) is now total: softness lives in the_orname,
everything else is loud.read_file_oris UNCHANGED (the only
remaining missing-file soft surface; no new soft functions — the
naryad boundary); existing-file reads are byte-identical. The debt
counter reads 0 with the only-down lock
(scripts/ci/debt_baseline.txt); the transition marker string is
GONE from the tree (the counter counts it — the flip is proven by
the code, not the marker). The limitations.md TRANSITION row closed
in the same PR (the №524 rule); REFERENCE.md §4.13 + the
generated index updated. Tests: tests/naryad_563_read_file_loud.rs
(6 — the loud flip on TW AND VM, the_ortwin on both, the
existing-file byte-identity, the debt-gate pin); the n254/n455/n507
pins flipped as their own comments promised. -
Naryad №562 (issue #923; Wave 25 P2; the audit 02.10 §6.1): the
tag↔main gap gate — the M-3 class ("a High fix lives outside a
release") becomes a MEASURED state. The new blockingrelease-gap (blocking)CI job runs scripts/ci/release_gap_gate.py: a pending
Security entry in [Unreleased] older than the floor FAILS the gate
with the oldest stamp named; an UNDATED pending Security entry fails
too (fail-closed — an undated pending fix cannot be measured); no
Security part in [Unreleased] passes. The floor N=14 days is the
OWNER's parameter (the naryad proposes, it does not choose; the
publication itself stays the owner's gate — the criterion only
measures the limbo). Embedding decision (the naryad's "факт решает"):
a separate blocking job, NOT a fifth criterion inside unfreeze_gate —
the unfreeze gate is the ADR-0177 §4 four-criteria machine verdict
(a frozen list); the release-gap criterion is release hygiene, not an
unfreeze criterion. The job joined the №551 required set (13 items —
the script, the sync test and the branch-protection checklist moved
together). Today's CHANGELOG passes ([Unreleased] carries no Security
part). Tests: tests/naryad_562_release_gap.rs (6 — the self-test
driver, the committed-CHANGELOG pass, fresh/over-age/unstamped
fixtures through the real script, the structural required-set pin);
n551 3/3 stays green. -
Naryad №561 (issue #922; Wave 25 P2; the audit 02.10 §7.2 W-1): the
good-first-issue pool policy — the pool drains from the INSIDE (five
of the №541 positions closed by owner/agent commits within a day; a
newcomer on 03.10 saw an EMPTY pool), so the refill rule is now
written: internal closing is allowed but the refill to 8–10 open
positions happens in the same wave, the pool-size counter is part of
the wave report, and thereserved-for-newcomerlabel marks the
pool (AGENTS.md §3: agents do NOT take labeled tasks — a recurring
upkeep task an agent did internally is re-opened as a
good-first-issue for the next external contributor). The policy
lands in docs/maintainers.md (the onboarding-lane section) + AGENTS.md;
the label created on the repository. THE POOL REFILLED TO 9 in the
same wave, each issue by the №491 filter (self-contained volume, no
context dependency) with the expected volume and the entry point:
gh#951 (the 40 examples without a golden.expectedsidecar),
gh#952 (the book syntax page — 41 fenced snippets), gh#953 (the
tutorial + stdlib pages — 25 snippets), gh#954 (doc-tests.md
freshness), gh#955 (testing-evidence.md citations), gh#956
(risk-review-checklist.md links), gh#957 (CONTRIBUTING first-PR
path), gh#958 (AGENTS.md §1 applied to itself), gh#959
(PLAN-SUMMARY/REALITY numbers vs the generated metrics SSOT).
Deprecated
-
Naryad №560 (issue #921; Wave 25 P2; the audit 02.10 M-4): the PRECISE
typed-signature share lands as the second metric beside the general
one — the audit's finding: 79 of the 162 typed rows are bare
Struct/List without a parameter (≈49% coarse), so the general 31.95%
share is reachable by coarse types. The precise set is
{String, Float, Bool, Unit}; the registry's stage-0 spec strings are
bare (no List/Struct(Name) forms exist in the source — the
honest fact the metric records), and the measured fact EXACTLY
matches the audit: precise 83/507 = 16.37% (49 String + 20 Float +
9 Unit + 5 Bool) on the audit revision 6e66d3d.
type_signature_share.py --precisereports and gates the second
floor; scripts/ci/type_signature_precise_baseline.txt locks it
(threshold_bp 1637, movement only up — never by loosening the
PRECISE_TYPES definition; Unknown stays Unknown, the №560 boundary);
the type-signatures (blocking) CI job gates BOTH floors; the README
generated-metrics block carries both shares side by side
(Typed Signatures | 162/507 (31.95%) — precise 83/507 (16.37%)),
regenerated by gen_metrics.py and validated by --check +
readme_consistency. The 0.29 precise-share GOAL is the OWNER's
parameter (§3 ADR-0179) — the proposal is delivered in the issue. -
Naryad №559 (issue #920; Wave 25 P1; the audit 02.10 M-2 systematic +
§6.3): the corpus contract is machine-checked — changes that turn a
warning into an error run against the KNOWN corpus, not wait for a
user (the respond_html lesson). Three pieces, one blocking surface:
(1) scripts/ci/extract_call_shapes.py — the call-shape extractor
(string-aware, comment-stripping, struct-literal-bracket-aware; the
declaration heads excluded; thename|argc|counttriples carry no
code and no query strings — the №559 privacy boundary);
(2) scripts/ci/office_call_shapes.txt — the committed snapshot of
examples/ + self-host/ (712 pairs; the office snapshot is the owner's
hand-off with the same one-liner; hand-annotated
SKIP|name|argc|reasonlines mark try-wrapped negative fixtures and
survive as a documented convention); (3)
scripts/ci/registry_vs_corpus.py — the blocking validator (every
registry-bound pair must satisfy the registry's arity spec; the
non-builtin names and the dynamic-arity list — forget, render, the
semantic.rs posture — are skipped; a divergence FAILs with the name
and the arity) wired as a step into the existing
registry-arity-check (blocking) CI job (the required set is
unchanged — the job IS the blocking surface). The specs reach the
validator through scripts/ci/registry_arity.txt — the machine-read
export pinned to BUILTIN_REGISTRY by tests/naryad_559_registry_vs_corpus.rs
(the file moves with the registry, never alone; the METALOGOS_N559_BLESS=1
dev path regenerates). The review rule lands in CONTRIBUTING.md.
The check PAID FOR ITSELF IN THE SAME PR: the snapshot caught a
LATENT 1-arg session_login(user) call in examples/compat/p6_full_app.mlog
— the runtime refuses 1-arg (the spec is 2..2) and the branch was
never executed by any test; the example is fixed to the honest 2-arg
form (the password is NOT verified — ADR-0172 §6) with the finding
documented in place. Tests: naryad_559_registry_vs_corpus.rs 4/4
(the export sync, the dynamic-arity posture pin, the self-test
driver, the committed-corpus acceptance); the validator self-test
covers green/divergence/skip/dynamic-arity/range cases + the SKIP
annotation round-trip. -
Naryad №558 (issue #919; Wave 25 P1; the audit 02.10 M-8): the ONE
module-search rule —src/module_path.rs::resolve_module_fileis the
single implementation of the "module_path → file" rule, and all THREE
call sites call it: the interpreter's runtime loader
(interpreter/modules.rs), the compiler'sresolve_import
(compiler.rs) and the semantic static resolution
(resolve_imports_statically, semantic.rs — the third copy that
lived outside the mirror metrics). The previous forms composed the
same path two ways (format!("{}.mlog")append vs
with_extension("mlog")replace) — EQUIVALENT on every reachable
input, because the import-path grammar admits no dots
(import_path_segments = { IDENT ~ (SLASH ~ IDENT)* }), so the
extension-replace could never fire; the append form is the SSOT and
the equivalence is recorded in the file. The base directory stays a
site's own choice (base_dir for the loader and the semantic pass,
std_root for the compiler) — the RULE is shared, the roots differ by
design. The file-existence rule stays at each site's read: the loud
per-site refusals keep their exact pinned wording. Behavior is
byte-identical — pinned by tests/naryad_558_module_path.rs (6): the
three sites × found/not-found/nested shapes (the flat
import my_utils, the nestedimport lib/extra), the semantic
merged-set assertion (both modules' patterns land), and the
source-level SSOT pin (every site delegates; no inline composition
remains). Honest metrics: dup-names 0, mirrors 0 — no new pair
entered the contour (the collapse REMOVED the unmeasured third copy;
the leaf module adds no C4 edge). n523 11/11, n535 17/17, n557 7/7
stay green. -
Naryad №557 (issue #918; Wave 25 P1; the audit 02.10 M-7): the
SEMANTIC startup gate lands on every mcp-serve entrypoint — the same
level of protectionmlog servegot in №523. mcp-serve ran NO
semantic pass at all (grep 0 check_program sites on the audit
revision): a tool file nevermlog check-ed could carry blocking
semantic findings and serve. Nowresolve_imports_statically(the
same "." base_dir rule as serve) feedscheck_program, the exemption
filter reads ONLY the structured kind (is_exempt_from_blocking — the
ONE explicit place), and a non-empty blocking list refuses the
STARTUP with the serve refusal's format: the FIRST blocking finding's
stable code at position 0 (№479), the onряд stamp "Naryad #557",
the format_blocking_line list. The gate is wired at ALL THREE
entrypoints (stdio run_mcp_server, run_mcp_server_network, the test
harness run_test_mcp_server) right after the №536 Category A gate —
a structural test pins the both-gates parity (the transports cannot
drift). The blocking-checks matrix: the mcp-serve "absent
(зафиксировано)" row is REPLACED by five real cells
(read/parse/audit/semantic×2, all blocks) — the matrix has no
mcp-serve absent row anymore; blocking_checks_sync.py asserts the
five-command coverage (the JIT absent row stays — the honest
boundary); fact_blocking_check_cells 27 → 31. HONOR BOUNDARY (the
naryad forbids semantic changes): the check_program walk surface is
UNCHANGED — pattern and route bodies are walked (the №523 walks),
tool-method bodies are NOT (the pre-existing blindness both serve and
mcp-serve share; the honest extension is a check_program semantic
change = a separate gated decision, recorded as a candidate). The
fixtures pin the walked surfaces: a pattern-bodied undefined call and
a missing import refuse the startup loudly and early; a clean tool
file passes. Tests: tests/naryad_557_mcp_semantic_gate.rs (7); the
n535 structural pins updated (mcp-serve must NOT regress to absent);
n535 17/17, n536 10/10, n523 11/11 stay green. -
Naryad №556 (issue #917; Wave 25 P1; the audit 02.10 M-6; the GDPR
line №526→№527→№556): the strict-erasure posture for the voice store
closes the two residuals the audit found. (1) The DELETED page no
longer survives in the journal: the store setup turns
PRAGMA secure_delete = ON(verified loudly — a connection refusing
the posture fails init) and everydelete_voiceprintends with
PRAGMA wal_checkpoint(TRUNCATE)— a WAL-mode database's log is reset
to zero length by the call, a busy WAL is a loud error, never silence
(the n526 zero-overwrite left the page image in the WAL — exactly the
residual the audit flagged). (2) The audio artifacts stop lying about
at-rest secrecy (the №512 lesson): the legacy plaintextaudio_bytes
column becomesaudio_encrypted— the SAME AES-256-GCM scheme as the
voiceprints (№517) with the №527 subject binding (the AAD registry is
voice_artifacts; a blob transplanted onto another name refuses with
the new[VOICE_ARTIFACT_DECRYPT]code), the mock runtime keeps the
visible INSECURE-XOR-MOCK mark, and a keyless real-runtime write fails
closed ([VOICE_INSECURE_STORE]). The migration secures the legacy
plaintext bytes (zero-overwrite first; the DROP purges the freed pages
under secure_delete) and rebuilds the table — no released version ever
wrote an audio row (the write path never shipped), so the dev-era rows
are not carried. The store-levelsave/load_audio_artifactpair is
the AT-REST contour only — wiring a program-facing audio write surface
is deliberately NOT in №556 (the boundary). The crypto plumbing is one
shared core (aes_gcm_encrypt/aes_gcm_decrypt+ the
DecryptFailuretaxonomy) with the registry-specific AAD composable —
the voiceprint refusals keep their exact pinned wording; dup-names 0,
mirrors 0. Tests: tests/naryad_556_voice_secure_delete.rs (9 — the
PRAGMA posture, the observable WAL truncate, the encrypted roundtrip,
the wrong-key/swap/keyless refusals, the mock mark, the legacy
migration); the voiceprint suites (n512/n517/n527) and the 23
store-unit tests stay green. docs/privacy.md §2.1 records both
postures with the honest boundary (the store's OWN connection; the
file-level guarantee stays the DB file owner's). -
Naryad №555 (issue #916; Wave 25 P1; the audit 02.10 M-5 + Е-3): the
MSRV contract is CHECKED, not just claimed — a blockingmsrv (blocking)CI job pins the toolchain to exactly 1.93.1 (both the
dtolnay/rust-toolchain@1.93.1action ref AND the
RUSTUP_TOOLCHAIN: "1.93.1"env, the documented override over the
rust-toolchain.tomlchannel = "stable"— the gh#865 silent-switch
failure mode cannot recur) and runscargo check --workspace --all-targetson the floor; clippy stays on stable by design (the
№500 lint-set precedent). The job joined the №551 required set (the
script, the sync test and the branch-protection checklist moved
together in this PR). Dockerfile: the builder patch is PINNED to the
MSRV (rust:1.93.1-slim-bookworm, the tag verified to exist) — a
floatingrust:1.93could drift below the declared floor; the
policy in the file says rust-version and the FROM tag bump in the
same PR. CONTRIBUTING.md carries the external-auditor note (Е-3):
rustc ≥ 1.93.1 is mandatory and the pre-1.93.1 manifest refusal is
the contract working, not a breakage. -
Naryad №554 (issue #915; Wave 25 P1; the audit 02.10 §3.3, position Е
on the memory pairs — the dispatch resolution "memory — document"):
the memory pairs are PERMANENT BY DESIGN. The honest record lands in
the TW/VM Divergences table of docs/limitations.md ("the memory
builtins run on TWO ENGINES: memorize/recall/recall_top_k/forget
(plus the dispatch marshaling twins) — the VM's honest simple-memory
store and the TW's full store | №442 | PERMANENT BY DESIGN"), with
live links to the shared module, the baseline and the pinning test.
src/memory_ops.rs cross-links the record (the module docstring keeps
the shared-HOME-not-unification statement); the ops-pair baseline
marks the five pairs [permanent — №554] while the query pair stays
UNMARKED (a parity-fix candidate, position А — never a design
decision). Pinned by tests/n554_memory_pairs_permanent.rs (3/3): the
three artifacts cannot drift apart — a lost record fails the file.
NO behavior change (the §5.3 rule: the memory pairs are never merged
at the cost of semantics). -
Naryad №553 (issue #914; Wave 25 P1; the audit 02.10 §3.3, position А
on the SQL pairs — the dispatch resolution "SQL — fix"): the
SQL-params divergence is FIXED, and the two db pairs collapse. THE
FIX: a non-List params argument is a LOUD refusal on BOTH backends —
db_execute() second argument must be List, got {}/
db_execute_with_grant() third argument must be List, got {}. The
VM lane used to end the params match with_ => Vec::new()— the
audit's exampledb_execute("UPDATE accounts SET frozen = 1 WHERE id = ?1", user_id)executed with the placeholder UNBOUND (or, on a
placeholder-free statement, the argument was silently IGNORED) on
the serve-default backend, while the TW lane refused loudly. THE
COLLAPSE (the №540 mechanics):db_executeand
db_execute_with_grantare ONE suffix-free function over the
DbAccesstrait each — the TW ridesTwDbAccess::lock, the VM its
own trait impl; the ops-pair threshold 8 → 6
(ops_pair_baseline.txtmoved in the same PR). The unified bodies
keep the RICHER error on each axis per the №484 discipline:
db_executegains the №758 not-open remedy on the TW lane, the
grant fn keeps thename()missing-argument texts on both lanes
(the former TW upfront-arity text is subsumed). Behavior-neutral on
the success paths (byte-identical); the observable change is exactly
the loud refusal the audit demanded. Mutation-parity pinned by
tests/n553_sql_params_parity.rs — the audit example refused
IDENTICALLY on both backends (red-before: the VM half failed on the
pre-№553 revision), the grant twin likewise, and the proper-List
path still executes in parity; the №465 diff-fuzzer 9/9; the db
suites (№474/№484/№758/№397, registry checks) green. -
Naryad №544 (issue #882; Wave 24, P1, compiler/security; stage 2 of the
type system, №467 canon — the three audit classes on
Labeled(Box<Type>, Label), executed behind the ADR-0179 gate in three
sub-PRs): the stage-2 migration is COMPLETE — SECRET_LEAK, SQL_DYNAMIC
and HTML_INJECTION speak the stage-0 label vocabulary, the bespoke
verdict logic is gone from all three lanes. SECRET_LEAK (№544.1, PR
#900): a secret source (env()/secret()) is
Labeled(String, Private)(src/secret_label.rs); the label joins
through composition, sanitizers strip it,redactlifts it exactly on
the one-way policy (ADR-0136 D2);TaintKind::Secretis deleted from
the enum (consumers 7 → 0). SQL_DYNAMIC (№544.2, PR #901): the literal
string IS the SQL template (the unlabeledString), every derived
string carriesLabeled(String, Internal)(src/sql_label.rs);
Privatewhen a secret part joined in; the two parity type rules —
concatenation is derivation itself (aBinaryOpnever yields the
literal type) and a binding demotes the literal (a variable reference
is never a literal). HTML_INJECTION (№544.3, PR #902): an LLM-output
source isLabeled(String, Untrusted)— the third stage-0 label
(src/html_label.rs); the UserInput kinds mirror as
Labeled(String, Private)(silent at this lane's sink, №268); both
mirrored walks keep the pinned quirks — the wider binding-level source
set (call_llm_schema flags via a binding), the depth-bounded sink walk
(№295,TAINT_NESTING_MAX_DEPTH = 3), the direct-only learnable
match; sanitizers lift at any depth;redactpasses through (masking
is not channel sanitization). All three walks are LEAF modules (the C4
acyclicity ratchet sees no new intra-cycle edge); the walk topologies,
messages, severities, line resolutions and the №98 compile-time
refusals are the exact mirrors — the observable behavior is unchanged
and pinned (leak suite, n172/n274/n292/n322/n325/n386/n455, №123/№201/
№268/№295, the n98 golden, the №465 diff-fuzzer 9/9 on every step; the
full 280-file integration sweep green). Audit-surface measure (the
honest, FINAL one): step 3 alone slimmed audit.rs by −128/+62 (net
−66: the HTML tracker, the bounded walker and the learnable matcher
left the file; the remaining taint machinery serves only the
not-migrated checks); across the whole stage 2 audit.rs moved 7756 →
7796 (+40 — the walk plumbing and the env threading), while the three
manual per-class contours were REPLACED by one-line typed questions
and the lane semantics moved to three typed leaf modules (secret_label
190 / sql_label 163 / html_label 268 lines); the bespoke verdict
consumers: 0 (TaintKind::Secretdeleted, the syntactic SQL match
deleted, the HTML tracker deleted). Contracts: tests/n544_step1_secret_label.rs (8),
tests/n544_step2_sql_label.rs (10), tests/n544_step3_html_label.rs (10)
— the mechanism surfaces + the end-to-end parity shapes + the №98
compile-time classes. -
Issue #892 fix (prod regression on main — introduced by №523's spec
hardening, never shipped in any release; the FOSVED-office-v2 FORGE
pages 500/404): therespond_htmlcontract is restored — all three forms in
one builtin.respond_html(html)(the 1-arg office corpus form) is legal
again — the registry/semantic spec drops №523's hard 2 back to 1..2, so
mlog checkno longer rejects every office HTML route and the runtime no
longer 500s with "requires an argument at position 1". The 2-arg form
disambiguates by fact: when the first argument opens with a valid HTTP
status token ("200", "404 Not Found") it stays the documented
(status, html)form with the body VERBATIM; otherwise it is the office's
(title, body)shape — a full HTML document is built, the title lands
tag-stripped in<head><title>AND verbatim at the top of<body>(it is
not dropped anymore). Every form now servesContent-Type: text/html; charset=utf-8:Value::HttpResponsecarries an optionalcontent_type
(None keeps the historicalrespond()text/plain default) and the server
honors it (value_to_response+ both VM serve paths share the new
http_response_into_response) — the axum String-body text/plain default
that actually broke the office pages no longer applies to HTML responses.
REFERENCE.md §4.13 + the web table document the three forms; contract
pinned bytests/n892_respond_html_contract.rs(12 tests: all three
forms, status-vs-title precedence, out-of-range numbers stay titles,
semantic 1-arg pass / 3-arg fail, registry arity) and 3 server.rs unit
tests (content-type header, the None default, custom status). -
Naryad №545 stage 1 (issue #883; Wave 24, P1; the №472 roadmap,
decision 2-B; docs/refactoring-split-plan.md): the crate-stub of the
reflex domain lands as a workspace member —metalogos-reflex
(version lockstep via [workspace.package]; the crate's own candle
gate mirrors the off-by-default posture; the workspace builds green
on all profiles). The stub ships NO moved code DELIBERATELY: the
physical move of the reflex domain (the NN module, the generative
machinery, the candle surfaces — ADR-0178 §3) is blocked on the
dependency-direction fork the roadmap explicitly reserves for the
owner's gate. The inversion inventory is recorded in the stub's
library docs:Value::Reflex/Value::BpeVocablive in the CORE
value enum, and 6 core files importcrate::nn::directly
(vm.rs, distill_hub.rs, builtins/reflex.rs,
interpreter/{mod,learnable,execution}.rs, builtins/template.rs) —
moving before these edges are inverted would be a cyclic crate
dependency. The fork: (i) the core-first inversion through trait
seams (the №484 DbAccess precedent) or (ii) the tiny
metalogos-valuescrate — the owner decides; the follow-up
sub-PRs (inversion, then the physical move with the stop-list
manifest following 1:1, then the re-export shell) are sequenced
behind it. No contour growth: the split is a transfer, not an
expansion (ADR-0178 §4); the stop-list baseline is untouched. -
Naryad №545 stages 2-3 (issue #883; Wave 24, P1; the owner's gate
decision of 2026-10-02: fork (i) — the core-first inversion through
trait seams, the №484 DbAccess precedent): THE CRATE SPLIT IS
PHYSICAL. Stage 2 (PR #909): the inversion —ModelKind::Sequence/Gen
payloads go through the handle contractsSequenceModelHandle/
GenModelHandle(src/nn/handles.rs, the consumer-owned seam that
stays in the language crate),register_seq/register_gentake the
boxed handles; the Value-speaking spec surface (the
SEQUENCE_LAYER_REGISTRY+ the sixbuild_*fns) lands in
src/nn/sequence_spec.rs— the check-time language surface cannot
move with the machinery; the sharedcheck_tensor_coveragegets its
own leaf (src/vision/coverage.rs, now moved with the diffusion
chain). Stage 3 (this PR): the 16 stop-list files move 1:1 into
metalogos-reflex(tennnmodules + sixvisiondiffusion
modules + the coverage check + the accuracy SSOT
compute_accuracy), the dependency direction lands CORE → REFLEX
(the reflex crate is Value-free by design — the Value marshaling
stays with the language builtins), the re-export shells
(metalogos::nn::*,metalogos::vision::*) preserve every consumer
path — the no-API-change contract holds (the integration suites run
UNCHANGED),save_png's write half stays behind the №475 fs gate in
the language crate (the façade module), and the №463 manifest
entries follow the files in the same PR with the baseline re-frozen:
9430 → 9025 LOC (movement only down; no new contour files, no
expansion — ADR-0178 §4 needs no patch). The tokenizers dependency
moved to the reflex crate;imagestays (the provenance marks).
Verification: check default/candle/vision/portable green; clippy
candle/vision/voice-D warningsgreen; lib tests 894 (default) +
10 (the reflex crate's own moved unit tests) + 906 (portable); the
№183/№184×3/№185/№193/№195×4/№179 seam suites and the №210/№211/№212
vision suites all green; the №463 gate and the core→media gate green. -
Naryad №549 (issue #887; Wave 24, P2; the NLnet/Restack traction
lane, M1 2026-11-03): the draft traction package lands in
metalogos-grants/— the owner-gated surface (nothing here is
published by an executor; the №541 postulate). The one-command demo
script (scripts/demo_traction.sh, three legs over the public
examples VERBATIM: the static taint refusal — the private camera
frame denied at compile time with the exact class/rule/node; the
live serve loop; the action-ledger chain — grant → allow → exhaust
→ deny → key rotation → export →mlog ledger verify, the Ed25519
chain verified WITHOUT the Metalogos runtime; exit 0 = every leg
reproduced, cleanup after itself). The metrics one-pager
(metalogos-grants/nlnet-traction/01-metrics-one-pager.md):
repository facts with dates, machine-generated where a generator
exists — no market analysis (the §16.0-7 sanitization). The demo
narrative (02-demo.md) frames the arc honestly: the three legs
are the design center — authorization as dataflow, refusals that
explain themselves, irreversible actions that leave evidence.
Transmission to the owner: this PR + the naryad report; the
external wordings and any publication stay the owner's.
fe6d21a (Naryad 549 (issue #887): the draft NLnet/Restack traction package lands in metalogos-grants - the one-command demo script (three legs over the public examples verbatim: static taint refusal, live serve, the signed ledger chain verified without the runtime), the metrics one-pager (repo facts with dates, no market analysis), the demo narrative; transmission to the owner, publication stays the owner's gate) -
Naryad №546 (issue #884; Wave 24, P2; ADR-0178 §5 preconditions 4–5):
the generative contour's embedding seam becomes a NAMED, fail-closed
boundary instead of an accident of the String argument contract.
PRECONDITION 4 — the runtime secret check: the secret family
(Value::Secret/Encrypted/Hash) is refused LOUDLY at the seam
([EMBED_SECRET_REJECTED], the error names the legal redact() path)
at the seam's three entry points — theembedbuiltin, the shared
embed_textSSOT helper (the learnable semantic probe, №272/№273)
and the vaultsemantic_searchbuiltin (its own manager instance —
the third seam consumer the audit-style inventory caught). The
value-level guarantee is the whole runtime surface today: a
Secret's content cannot become a plainStringexcept through
redact()(the masked form is by construction not the secret); the
compile-time №322 labels stay the static lane's job — stated in the
seam module, no theater. PRECONDITION 5 — the call budget: one unit
= one embedding operation; the counter is thread-local (the №457
mechanism), reset by an RAIIContourBudgetScopeat the
per-request/per-tick boundaries (the TW route body, the VM route
body,execute_tick_call— the same threads the №457 guards mark,
reset-on-entry because tokio reuses blocking threads); the limit is
configurable (METALOGOS_CONTOUR_BUDGET, default 64, deliberately
conservative); over the limit the seam refuses loudly
([CONTOUR_BUDGET_EXCEEDED]) BEFORE any embed runs — a multi-unit
call that would overshoot is refused whole. vec_store/vec_search
take pre-computed vectors — no seam, no budget unit. Tests: 4 seam
unit tests + the integration file on BOTH backends (the loud
refusals pinned on TW and VM, the within-budget path green, the
scope-reset behavior pinned; serial — the env var and the
thread-local counter are process/thread-global). ADR-0178 §5 is
actualized in the same PR: rows 4–5 CLOSED (this PR), row 3 CLOSED
post factum (№530, PR #859 — the row was not re-read then; recorded
honestly), and the closing line states explicitly that the closures
authorize nothing — §6 stays the gate, the stop-list holds
regardless. The stop-list manifest is untouched (no contour file
grew; the seam module is a NEW file outside the manifest). -
Naryad №543 part 2 (issue #881; Wave 24, the typing line to the 0.28
gate, P1): the pdf package (25 rows) leaves stage 0 — THE NARYAD'S
STOP CONDITION REACHED. 20 rows typed Struct (the pdf.rs make_struct
family: pdf_create, pdf_add_page, pdf_write_text, pdf_draw_line,
pdf_draw_rect, pdf_save, pdf_merge, pdf_split, pdf_metadata,
pdf_set_metadata, html_to_pdf, pdf_draw_table, pdf_add_image,
pdf_set_page_header, pdf_set_page_footer, pdf_page_numbers,
pdf_watermark, pdf_fill_form, pdf_rotate_page, pdf_delete_pages) and
5 rows typed List — INCLUDING the per-helper fact that distinguishes
this package: pdf_classify/pdf_to_markdown/pdf_ocr return the
pdf.rs make_dict, which is a LIST of String items (pdf.rs:363-371),
NOT a Struct (a naive helper-name match would have typed them wrong);
pdf_extract_regions/pdf_extract_images are the plain vectors. 0
Unknown: no mixed paths in the package. Floor: 137/507 → 162/507,
threshold_bp 2702 → 3195 (+493 bp ≈ +4.9 pp) — 3195 ≥ 3000
(goal_typed_share_bp, ADR-0179 §2.1 draft); the final measurement is
in the naryad report. The in-tree lock moves with the baseline:
133 → 158 (compiled default-feature count; the same 4 gated
vec/store rows ride the source count only). The final measurement:
the source count 3195 bp is the CI-gated floor; the compiled count
is 158/507 = 3118 bp — both over the gate. Runtime/semantics
untouched. -
Naryad №543 (issue #881; Wave 24, the typing line to the 0.28 gate,
P1; ADR-0179 §2.1 goal, §13.10 decision 3-A): the bot package — the
largest remaining registry package (35 rows) — leaves stage 0. 30 rows
typed against the VERIFIED handlers (the Ok-path facts, one commit of
archaeology per family): the make_date_struct family → Struct
(memory_score, todo_add, todo_update, goal_get, goal_set, goals_add,
goal_complete, goals_reflect, human_create, human_mood, human_delete,
learn_preference, ask_approval, read_file_tokens); the row vectors →
List (todo_list, goals_list, get_profile, human_personas, human_recall,
extract_entities, check_reminders, list_reminders); the text returns →
String (human_remember, human_respond, compress_html, extract_param,
remind, remind_recurring, cancel_remind); estimate_tokens → Float.
5 rows stay Unknown HONESTLY, not metric-fitted: send_message/
answer_callback_query/edit_message_text/send_document (String on
delivery | Unit on the no-TELEGRAM_BOT_TOKEN fallback — the flat
vocabulary has no env-dependent union) and human_forget (Float deleted
count on the 1-arg all-persona path | String "ok"/"not_found" on the
2-arg path — the arity-dependent union). The floor moves up IN THE SAME
PR (the rule): 107/507 → 137/507 source rows, threshold_bp 2110 → 2702
(+592 bp ≈ +5.9 pp). The №542 registration is closed here: the in-tree
double lock (tests/type_signature_metric.rs TYPED_FLOOR) moves in the
same PR from now on — 50 → 133, and the lock's comment now documents
the by-design gap (the script counts SOURCE rows; the test counts the
COMPILED default-feature registry — the feature-gated vec/store-lane
rows embed/vec_store/vec_search/memory_forget exist in the source
count only). Runtime/semantics untouched (the naryad's boundary); the
№486 warn-contour is not affected (the typed rows' categories were
silent there). Remaining typing mass toward the 3000 bp gate: pdf (25),
diagram (21), web (19), voice (19) — the descending-weight order per
the naryad. -
Naryad №522 (issue #821; the Wave-22 serve-e2e line, P2; the №509
functional criterion, ADR-0179 §2.5): the cron accumulation arc
through the SERVING path — tests/naryad_522_cron_serve_e2e.rs, both
serve backends (the №496 pattern). The arc: a POST route registers
the job (cron_add("* * * * *", "tick_job")— the KV-backed job
store, persisted through thememory { persist }SQLite
write-through);cron_run(id)(the №418 manual force_run) marks the
job and the SCHEDULER's 5-second pass fires the tick (the №426
program-context executor) into a pattern that increments a KV
counter — the accumulation is visible in the /ticks route response;
the RESTART leg proves BOTH survivals: the job is still found (the
force re-queues instead of "not_found") and the counter CONTINUES
from the pre-restart value (the KV SQLite load-back — "SQLite is
authoritative on init") instead of restarting from zero. THE RED RUN
CAUGHT THE HARNESS DEFECT, fixed in the same PR: the test boot
(run_test_server_in_dir_impl) NEVER spawned the scheduler loop —
onlyrun_serverdid — so the cron arc was invisible to the
serve-e2e lane ENTIRELY (a test asserting the arc could never pass,
and no test asserted it). The loop is now the ONE shared constructor
(spawn_scheduler, the №480 rule) called verbatim by BOTH boots —
production behavior byte-unchanged, the test harness gains the
production scheduler. The №457 tick security posture is NOT
re-tested here (its own suite pins it). THE SERVE-E2E INVENTORY IS
NOW FULLY DONE (distill/memory/conversation/cron) — the №509
functional criterion of the 0.28 gate is MET on all four rows; the
unfreeze gate v2 recalculates. -
Naryad №521 (issue #820; the Wave-22 serve-e2e line, P2; the №509
functional criterion, ADR-0179 §2.5): the conversation accumulation
arc through the SERVING path — tests/naryad_521_conversation_serve_e2e.rs,
both serve backends (the №496 pattern). The arc drives six conv_add
calls againstconversation { compress_after: 2, ttl: 3600, max_messages: 5 }(the fields in a NON-canonical order — the boot
re-pins the №508 free-field-order guarantee) through one POST route
and pins the honest per-backend posture: the max_messages ROTATION is
the shared core (the oldest message evicted, both backends); the
ADR-0053 COMPRESSION tail diverges BY DESIGN — the TW collapses
everything beyond compress_after into ONE system summary (3 lines:
system + m5 + m6), the VM keeps the rotated window (5 lines: m2..m6,
the documented no-op tail); and the per-request conversation
isolation (№72: "request A's dialogue must not continue in request
B") is pinned via a fresh request's conv_history reporting the
conversation NOT FOUND on both backends. THE RED RUNS CAUGHT TWO
CONFIG-DELIVERY DEFECTS, fixed in the same PR (the №495 class — the
declared config was dead on the serve path): (1) the TW boot merge
ran ONE declaration per throwaway interpreter and
clone_definitions_into copied the conversation_config UNCONDITIONALLY,
so every later declaration's defaults clobbered the declared config
before any route ran — the copy is now CONDITIONAL on the source
having actually SEEN aconversation {}declaration (a declared-flag
marker; the №381 db_conn clobber class); (2) the VM lane NEVER
received the declared config at all — the compiler ignored the
declaration, so the VM used the defaults unconditionally; the config
now rides the compiled Program (the memory_persist_path plumbing:
a#[serde(default)]Program field — old .mbc files deserialize
cleanly — applied in load_program). The ttl field has NO runtime
enforcement on the access path today — honestly NOT asserted (the
declaration parses; the enforcement is a separate owner-gated
decision). The remaining pending serve-e2e row: cron (№522). -
Naryad №520 (issue #819; the Wave-22 serve-e2e line, P2; the №509
functional criterion, ADR-0179 §2.5): the memory accumulation arc
through the SERVING path — tests/naryad_520_memory_serve_e2e.rs, both
serve backends, one sequential test fn (the №496 pattern). Arc 1, the
memory { persist }+memorize/recalllane: the same-request
roundtrip works on BOTH backends; the cross-request accumulation is
pinned per the honest per-backend posture — on the TW the per-request
interpreter re-opens the SQLite-backed store at the declared path, so
request B's recall finds request A's memorize AND the store survives
a server RESTART; on the VM the simple-memory twin is request-scoped
BY DESIGN (the №442 posture;Vm::reset_for_reuseclears the store
between pooled generations — the isolation class pinned by
naryad_402_step_a), so the test pins the honest ABSENCE — an
accidental cross-request leak turns RED. Arc 2, the typed-memory lane
(memory_open/memory_put/memory_read, the №350 surface): the
container registry is process-global and backend-independent (the
subject IS the address), so the accumulation arc is identical on both
backends — a PUT in request A, a READ in request B, and a READ after
the restart with the persistence anchors (METALOGOS_MEMORY_DB +
METALOGOS_MEMORY_MASTER, ADR-0173 §3.5) set — the value decrypts
across the restart. The serve_e2e inventory row (memory) flips to
done IN THE SAME PR; the security gates (№335 consent, №442 ledger)
are NOT re-tested here — their own suites pin them. -
Naryad №541 (issue #851; the wave-23 line 6-A, P1): the
second-maintainer lane goes operational for the NLnet/Restack
milestone (03.11.2026) — the pool, the onboarding package and the
grant wording, finalized. The good-first-issue pool grows from five
to NINE self-contained tasks: four new guides with the full format
(the what / the why / the reproducible check / the expected scope /
the acceptance criteria / the honest boundary) — the book examples
freshness pass (gh#872), the ADR index freshness review (gh#873),
the limitations.md anchor walk (gh#874), and the fresh-clone
quickstart pass (gh#875) — all inside the narrow docs/tests
perimeter. The onboarding package lands IN THE TREE: CONTRIBUTING.md
gains "Your First PR — the Good-First-Issue Lane" (the fork → branch
→ CI → PR → report path, the reason-line-per-changed-file rule, and
the pool's health metric — the time to the first merge, recorded per
newcomer in the wave reports), and docs/maintainers.md syncs its
pool section to the full nine with the issue numbers (the in-tree
list had drifted to three) and cross-references the first-PR path.
The NLnet application wording is finalized in-tree (the owner's
draft, updated to the current honest fact: "nine documented
good-first-issue tasks with reproducible checks and acceptance
criteria"). The channel-search options and the access/publishing
decisions remain the OWNER's gate (№541 §3) — the materials are
prepared in the M2 report, nothing external is opened by the naryad. -
Naryad №540 (issue #850; the wave-23 line 4-A, P1): the FIRST two
byte-identical ops pairs collapse into suffix-free functions over the
DbAccesstrait (the №484 mechanics) —query_rowanddb_insert
(the ops-pair threshold 10 → 8, moved in the same PR on the actual
collapse;scripts/ci/ops_pair_counter.py+ops_pair_baseline.txt,
the source comments on the collapsed sites carry the fact). The
query_rownot-open error unifies on the RICHER №758 named-remedy
text (the former VM text; the TW lane gains the remedy) and the
db_insertsecond-argument error on the RICHER literal-shape suffix
(the former TW text; the VM lane gains the detail) — both
behavior-neutral per the №484 richer-text discipline, the Err
outcomes are unchanged on every path. The REMAINING EIGHT pairs stay:
the four memory pairs (memorize/forget/recall/recall_top_k + the
dispatch marshaling twins) are the №442 honest simple-memory twin
posture (different stores, different algorithms, documented per
side), and the three db pairs (query, db_execute,
db_execute_with_grant) carry SEMANTIC divergences on the error paths
(the TW-loud/VM-silent non-List params handling; the TW
silent-skip/VM-loud row errors) — named in the docstrings per №480 as
parity-fix candidates (the №474 class), never pinned inside a
transfer (the №466 rule: a transfer PR must not change semantics).
The staledb_execute_vmdocstring (it still claimed the pre-№474
Unit return) is corrected. Verified: the №465 diff-fuzzer, the №503
outcome-parity gate, the crosscheck and property TW/VM parity suites
green; the semantics of every caller path unchanged. -
Naryad №539 (issue #849; the wave-23 typing line 3-A, P1): the memory
package leaves stage 0 — the wave's largest package (38 rows) types
35 rows against the verified handlers: kv_set/kv_delete/memorize →
Unit, kv_get/mem_set/mem_get/mem_delete/session_set/session_get/
session_clear/ref/deref → String, kv_exists → Bool, kv_list → List,
memory_put/memory_retain/memory_release/memory_retain_ttl → Unit,
memory_keys/memory_provenance/memory_retained → List,
memory_export → String (the exported path), memory_cascade_preview/
memory_forget_cascade/forget/memory_decay/memory_boost/memory_prune/
memory_revise/memory_forget/user_profile/vec_store → Struct,
embed/vec_search → List, recall → String. Three rows stay Unknown
HONESTLY with the inline reasons: memory_open returns the opaque
Value::Memoryhandle (the flat vocabulary has no Memory spelling —
the №538 Query posture), memory_read is String|Secret by the
container label (public → String, private → the gated Secret; the
flat vocabulary has no union, and typing String would false-warn the
private lane), and recall_top_k is a handlerless row (the spec!
macro keeps it untyped; the verified intercept fact — both backends
return the serialized JSON hit array as a String — is recorded
inline for stage 1). Floor: 72/507 (1420 bp) → 107/507 = 2110 bp
(+690 bp ≈ +6.9 pp — the package's own mass delivers the +3–5 pp
wave target; the biggest single-package gain of the typing line).
The memory semantics are untouched — registry/spec metadata only. -
Naryad №538 (issue #848; the wave-23 typing line 3-A, P1): the db
package — the honest exhaustion case. The package holds exactly TWO
rows:db_execute→ Unit (the interpreter path verified; the
executed surface is the Query pipeline — the №484 DbAccess trait is
not diverged from), andquerystays Unknown HONESTLY (it returns
the opaqueValue::Querywrapper; the flat vocabulary has no Query
entry, and the execution result type depends on the backend). Floor:
71/507 (1400 bp) → 72/507 = 1420 bp. THE WAVE ARITHMETIC, named: the
+3–5 pp per-package target is unsatisfiable on a 2-row package —
+0.4 pp is the package's entire mass; the honest wave-level
conclusion (the flat-vocabulary wall + the small packages) is
recorded in the M2 report; the remaining typing mass toward the 30%
gate lives in the packages outside the wave-23 line. -
Naryad №537 (issue #847; the wave-23 typing line 3-A, P1): the list
package leaves stage 0 — 9 rows typed against the verified handlers
(slice/zip/sort_by/filter/dedup/condense/chunk/make_list → List;
matches_any → Float, the 1.0/0.0 numeric verdict form). 5 rows stay
Unknown HONESTLY with the inline reasons: get/first/last return the
ELEMENT of a heterogeneous list (first/last add the ADR-0180
soft-failure "" path), reduce returns the ACCUMULATOR, compact_list
is List | the struct report{compacted, removed_count} — the flat
vocabulary has no union and the №486 base-type comparison would warn
falsely on a bent claim. Floor: 62/507 (1222 bp) → 71/507 = 1400 bp
(+178 bp ≈ +1.8 pp; the honest exhaustion: of the 17 list rows 3
were typed before, the 5 polymorphic element/accumulator returns
cannot take a flat type without lying). -
Naryad №536 (issue #846; the wave-23 typing line 3-A, P1): the string
package leaves stage 0 — 10 rows of BUILTIN_REGISTRY carry the
verified return type (capitalize/title_case/redact/squeeze/
hashline_read/hashline_edit → String; regex_match → Bool;
regex_captures → List; regex_replace → String; text_chunk → List),
each verified against the handler's actual returns, NOT the name. Two
rows stay Unknown HONESTLY with the inline reasons: reverse is
polymorphic (String → String, List → List) and fuzzy_find_best is
Unit | struct{index, candidate, score} — the flat vocabulary has no
union, and the №486 base-type comparison would warn falsely on a bent
claim; the metric is not to be gamed. THE BASELINE FIX, named: the
№757 floor (998 bp) never took effect — the gate parser reads the
FIRST threshold_bp line and the history kept it at 980; the
superseded history lines now sit outside the match and ONE live
triple is enforced. The total drifted 501 → 507 since №757 (the №526
voice rows, the №507/№514 *_or twins — to_float_or/to_int_or shipped
typed; the arity rewrites) and is resynced here. Floor: 52/507 actual
pre-PR (1025 bp) → 62/507 = 1222 bp. The package's +3–5 pp target was
unreachable inside the package bounds (only 12 untyped rows existed);
the honest gain is ≈ +2.0 pp, the gap documented in the M2 report. -
Naryad №534 (issue #843; the consolidated audit 30.09, Д-3 — the base
№504, the threshold 3, the three sites): the last three TW-mirror
marks leave src/vm.rs — the distill TRAINING gate sequence collapses
into the ONE canonical copy,train_distill_snapshotin
src/interpreter/learnable.rs (the closed label-set mapping, the №456
holdout gate, the 30-epoch run, the №485 majority-baseline gate and
its single-class carve-out, the NaN guard — the sequence the VM
carried as the inline pre-№489 copy).run_distill_trainingkeeps
its exact contract and becomes the thin TW orchestrator (the short
metadata lock → snapshot_clone → core → the short locked swap with
the №530 slot-changed guard); the VM'stry_train_distilled_model
becomes the thin VM orchestrator (snapshot_clone → core → the direct
post-gate swap). THE BEHAVIOR FIX, named honestly: the VM lane
trained the LIVE model in place and only then decided the gate — the
pre-№530 posture the TW path had already dropped; a refused gate on
the VM path now leaves the weights untouched (pinned by the new
n534_vm_failed_gate_leaves_weights_untouchedtest, mutation-checked
red against the swap-ignores-gate form), and the VM train-error text
carries the shareddistill: training failed:prefix. The
Distilled-mode PREDICT path remains a deliberate parallel form (the
state orchestration is the two runtimes: &mut self vs Mutex + the
№489 spawn) — its outcomes stay pinned by №503/№465; its dedup is
future-counter work, not this naryad. The mirror baseline moves
3 → 0 (ONLY DOWN; the marks left with the duplication, the gate runs
green at 0). Local battery: check clean, clippy -D warnings clean,
fmt clean, lib 887/887, №503 2/2, №465 fuzzer 9/9, №530 3/3,
bug_530 VM recall parity 3/3. -
Naryad №533 (issue #842; the consolidated audit 30.09, N-8,
P2 docs/examples): the 14 COMPAT-tagged examples (№513) leave the
live catalog —examples/compat/is the honest archive. THE MOVE:
exactly the 14.mlogfiles whose FIRST-5-LINES header carries the
№513 tag (the 15th file, p54_regex_crosscheck.txt, mentions COMPAT in
its body and stays — the corpus scan verified the count). THE HEADER:
every archived file now opens with the canonical honesty line —
"archived to examples/compat/ by №533: historical syntax — not parsed
since v0.27.x. See rule №513 (gh#797)" — above the original reason.
THE PATH RULE: the grammar tests and the example-coverage test
exclude by PATH (the archive is never entered), the №513 tag-scan is
retired — a COMPAT-tagged file in the LIVE catalog is now debt, not a
pass (the n513 assert + the simplifieddebt_counters.pyscan, whose
baseline stays 0). THE README:examples/README.mddocuments the
live-catalog contract (sidecar / named check / the suites) and the
archive rules (never delete; resurrect = rewrite + verify + move
back, in its own naryad). THE CROSS-REFS: the book's quickstart and
the architecture tree point at live examples; the two ADRs referencing
the moved paths (№0045, №0063) carry the archive note. Boundaries
held: nothing rewritten to the new grammar, nothing deleted. -
Naryad №532 (issue #841; the consolidated audit 30.09, N-7,
P2 compiler/hygiene): thewildcard_enum_match_armlint leaves
compiler.rs and covers the WHOLE N-1 surface —denyin
src/interpreter/execution.rs,src/vm.rsandsrc/semantic.rs.
THE CORE:is_truthyis now EXPLICIT in BOTH backends — every one of
the 36 Value variants gets its own arm (the truthy set is
{non-empty String, non-zero Float, true Bool, non-empty List}; every
opaque handle, struct, secret, Unit is falsy), the semantics are
byte-preserved and pinned by the №503 outcome-parity suite — the liar
string can never again slip through a wildcard. THE TAIL: the 98
remaining wildcard arms (report/parse/classification/dispatch
catch-alls where NO security decision reads the arm) survive under
FUNCTION-LEVEL#[allow]with the shared reason, recorded in the
grep-protocol ledgerdocs/wildcard-tail-ledger.md(the per-function
list); the found clippy quirk is honored — arm-level#[allow]does
not gate this lint, the function level does. THE GATE: a NEW wildcard
in these files fails the blocking clippy job until it is enumerated
or allowed with a reason (the negative test: a probe wildcard was
verified to trip the build, then removed). -
HOTFIX (the first real CI run, 30.09→01.10): the two shipped-unseen CI
defects the dead event delivery hid, both caught by the first
blocking-checks-sync-carrying run after the Actions
push/pull_request event outage (the last pull_request CI run was
14:30 UTC 30.09; everything merged after that ran unchecked). (1)
№535's tamper-test trap #2 asserted the wrong failure mode:
validate()RETURNS the error list and never raises ValueError on a
verdict-domain violation (onlyload_rowsraises, on the column
count), so theexcept ValueErrorbranch was unreachable, the
bogus-verdict trap could never spring, and the blocking job failed on
every run with "the bogus verdict did NOT trip the check" — the
gate's own first run caught its own bug; the trap now inspects the
returned errors exactly like traps #1/#3 do. (2) №528's
rust-toolchain.toml (channel = "stable") silently overrode the
fuzz-smoke job's nightly install — cargo-fuzz's-Zsanitizerbuild
probes died on the stable rustc ("the optionZis only accepted on
the nightly compiler"); the job now pinsRUSTUP_TOOLCHAIN: nightly
at the job level (the rustup override precedence: env > the
toolchain file), the rest of the repo keeps the №528 stable contract. -
Naryad №535 (issue #844; the consolidated audit 30.09, the gate-0.28
checklist item 3 — ADR-0179, P1 process/testing): the machine-readable
record of WHO BLOCKS EXECUTION lands —
scripts/ci/blocking_checks.tsv: 27 cells over the four commands
(run/check/compile/serve) × phase (read/parse/audit/semantic/codegen/
execute) × check × {blocks, warns, absent}, the state after №523.
THE CELLS: 21 blocks (every read/parse/Cat-A/semantic/codegen/runtime
refusal per command — the №523 startup gates for serve, the VM-compile
gate for compile, the static import resolution everywhere), 2 warns
(check's warning-only exit-0 surface; compile's unconditional
import-warning noise over a PROCEEDING compile), 4 absent (the honest
fixes: semantic warnings are silently dropped on the run path —mlog checkis the reporting surface; the exemption list is empty today;
JIT and mcp-serve are recorded as absent (зафиксировано) — outside the
CLI pipeline by ADR-0022/0073 and ADR-0168). THE BINDING: every
blocks/warns cell names its proving test — 17 new behavioral pins in
tests/naryad_535_blocking_checks.rsdrive the REALmlogbinary
through CARGO_BIN_EXE (exit codes + stderr as the verdicts), the
remaining cells reference the existing n98_/n523_ pins; the blocking
blocking-checks-syncCI job validates the format, the test bindings
(a word-boundary grep — a renamed/deleted test = drift = failure), the
coverage and the absent rows, and the --tamper-test negative mode
proves the traps spring (an unresolvable id, a bogus verdict, a
duplicate key). THE GATE WIRING: the table feeds the №525 gate-facts —
fact_blocking_check_cells: 27machine-verified by
blocking_checks_sync.py --countin the gate-facts-sync job (the
unknown-fact fail-closed trap verified). -
Naryad №529 (issue #838; the consolidated audit 30.09, Д-5+N-6,
P1 reliability/hardening): the bounded registries grow the SECOND
circuit — the total BYTE ceiling next to the №515 count ceiling.
THE FACT: №515 bounded the NUMBER of entries (64 per registry), but
neither the volume nor the aging — 64 entries × a large artifact
stayed unbounded, and a long-lived serve held everything up to the
count cap regardless of size. THE CIRCUITS:PDF_DOCS_MAX_BYTES
(16 MiB, the estimated CONTENT bytes — title/author/every element's
text+rows+path plus a fixed structural overhead, honestly NOT the
serialized-PDF size),VIDEO_ARTIFACTS_MAX_BYTES(64 MiB — render
bytes + latent payload f32×4 + the manifest text fields),
VOICE_ARTIFACTS_MAX_BYTES(32 MiB — the audio bytes and the
voiceprint embeddings, both maps). THE ORDER: one deterministic
victim order per registry — the PDF store is true LRU (every
mutation-path access restamps the seq viapdf_store_touch; the
eviction victim is least-recently-USED), the in-memory video/voice
maps evict by insertion age (the lowest monotonic id — their&self
getters cannot restamp without interior mutability, the honest
boundary is documented in the inventory). THE HONEST ALLOWANCE: a
single artifact larger than the byte cap is admitted into an EMPTY
store — the cap bounds accumulation, not one legit artifact (pinned).
THE OBSERVABILITY: one loud[REGISTRY_EVICTION]stderr line per
victim with the reason (count/bytes) and the store state — the
serve-report primitive — plus the public metrics getters
(pdf_store_eviction_metrics,video_registry_eviction_metrics,
voice_registry_eviction_metrics); a structured HTTP metrics
endpoint does not exist in this codebase and inventing one is outside
the naryad's boundary. THE RATCHET: the machine-checked SSOT of the
bounds lands (scripts/ci/registry_bounds_inventory.txt+ the
blockingregistry-boundsCI job) — every map-shaped process-global
static must carry an inventory row with BOTH bounds or an explicit
exception reason (the LLM stream registry keeps the №263 LOUD
refusal — silent eviction would kill in-flight streams; the per-stream
byte growth is bounded upstream by the №757 max_tokens ceiling;
GLOBAL_TEMPLATES is name-overwrite by design); the job verifies the
constants against the sources (a silently moved cap = drift = failure)
and is mutation-verified (a renamed anchor and a drifted value both
trip it). Tests: the two-circuit pins per registry (1000 small /
several large — each circuit bites on its own, the eviction order is
deterministic), the PDF LRU restamp pin, the single-giant allowance;
the №515 suite stays green unchanged (4/4). -
Naryad №527 (issue #836; the consolidated audit 30.09, N-3,
P1 security/voice): the AES-256-GCM ciphertext is now BOUND TO ITS
SUBJECT — the GCM AAD carries the (subject_id, registry, schema
version) triplet (the registryvoiceprints, the subject name, the
voiceprints-aad-v1schema label — joined with the 0x1F unit
separator so the ordered fields stay unambiguous), so a blob
transplanted onto another subject's row FAILS authentication (the
swap attack the bare tag accepted since №517 is closed — the tag
proved integrity of the bytes, never the binding). THE HONEST
BOUNDARY kept: the storage schema and thealgomark are untouched
(№517's T2 pinsAES-256-GCM-v1), the secret()-gate semantics are
untouched; the discriminator between a bound row and a legacy row is
the GCM authentication itself (try-bound first, then the loud
transitional fallback). THE TRANSITION: №517-era rows (empty AAD)
stay readable in the announced window — every such read emits
[VOICEPRINT_NO_AAD_LEGACY]on stderr AND surfaces the honest
VoiceprintCryptoStatus(LegacyNoAad) through the new
load_voiceprint_with_status; every WRITE is AAD-bound, so the
legacy population only shrinks; the deadline row (the fallback
removal at v0.28.0) lives in limitations.md (the №524 rule), and
privacy.md §2.1/§4 carry the new contour with re-verified anchors.
THE KEY LIFETIME: the decoded 32-byte key buffer lives under
Zeroizingin BOTH directions (wiped at the operation's scope exit
— the crate was already in the tree, zero new dependencies). Tests:
the swap refusal (RED-baseline verified — the binding mutation
reddens exactly the three binding tests), the loud legacy read with
the flag, the re-save rebinds story, the wrong-key refusal surviving
the two-step decrypt, the fresh-writes-are-bound pin; №517 (7/7),
№512 (4/4) and the store unit tests stay green unchanged. -
Naryad №531 (issue #840; the consolidated audit A+D 30.09, N-9,
P1 core/hardening): the LAST silently-soft refusal outside the №514
naming rule enters its TRANSITION period.read_fileof a missing
file still returns the №254 soft"", but EVERY hit now announces
[READ_FILE_MISSING]on stderr (the path named, the migration target
named:read_file_or(path, default)), and the debt gate carries the
marker counter (read_file_soft_missingin
scripts/ci/debt_counters.py+ the baseline — movement only down; the
flip release deletes the sites and the counter reaches 0). The
deadline lives in limitations.md's Error Protocol row (the №524 rule:
the row closes in the same PR as the flip). The release following the
№531 merge flips the branch to a loud[IO_ERROR]. The pinning tests
(№254/№455/№507 suites) carry the transition markers; the functional
examples needed NO migration (none relies on the empty string of
absence — verified by the examples scan; the leak corpus is a
compile-time suite and stays untouched). REFERENCE.md carries the
contract in both read_file rows:*_oris soft, everything else is
loud. -
Naryad №530 (issue #839; the consolidated audit A+D 30.09, N-4,
P1 reliability/llm): distill training no longer blocks the registry —
the 30-epoch run happens on a WEIGHTS SNAPSHOT taken under the short
metadata lock (ReflexModel::snapshot_clone, the Dense-only clone via
Layer::as_any— the trait is untouched), with NO lock held during
the epochs; a serve request answering mid-training acquires the
registry in microseconds (the timed test: every in-training
acquisition < 100ms, ≥3 acquisitions before the finished line). The
trained snapshot is swapped back ATOMICALLY after the holdout gate
passes — one short locked write with a slot-changed guard (a
redeclared reflex is refused loudly, never clobbered). The honesty
bonus: a FAILED gate now leaves the live weights untouched (the
pre-№530 code mutated them in place even when the gate then refused
the switch) — pinned by the weights-fingerprint test.
DISTILLED RETRAINING lands: the low-confidence calls keep recording
fresh (input, llm_output) ground truth in DISTILLED mode, and when
the new examples cross the SAME threshold that built the model (the
honest default: the pattern's own distill_after, the ADR-0115 floor),
a retrain job runs on the single trainer thread; gate passed → the
weights swap in, the mode stays DISTILLED; gate rejected → the LOUD
degradation revert to TEACHING (distill.degradedaudit) — silent
degradation is impossible. The №496 e2e stays green (the TEACHING
semantics byte-preserved). -
Naryad №528 (issue #837; the consolidated audit A+D 30.09, Д-4+N-5,
P1 deploy/ci): the MSRV lives in the build contract.rust-version = "1.93.1"in [workspace.package] (mlogpkg/mlog-lsp inherit) — cargo on
an older toolchain now refuses with a clear resolver error (verified:
1.85 and 1.89 both name the packages that refuse).rust-toolchain.toml
pins the COMPONENT contract (clippy + rustfmt — the CI blocking jobs'
surface); the channel tracks stable BECAUSE the clippy ratchet is
written for new clippy (the №500 precedent:chunks_exact_to_as_chunks
does not exist in clippy 1.93.1 — freezing the toolchain at the floor
would break the -D warnings gate). THE HONEST DEVIATION from the
naryad's draft (1.85), evidence-backed by the toolchain walk: 1.85 →
weezl needs 1.88; 1.88 → rustyline needs 1.89 (file_lock); 1.89 →
serial_test needs 1.93.1; 1.93.1 → the FULLcheck --workspace --all-targetsis green. The README badge and the build-from-source
wording now name 1.93.1 and point at rust-version as the SSOT. THE
DEPLOY-PATH BLINDNESS was REAL and DOUBLE:
the workflow file failed Actions YAML parsing since №511's landing (an
unquoted backtick in a step name — the file could not parse at all, so
NO run ever fired: not the weekly schedule, not the push trigger whose
branches line ([main]) was verifiably intact) and the
pull_request trigger did not exist. Fixed byte-level and
YAML-validated; the pull_request trigger
lands (Dockerfile/.dockerignore/example/src mains/Cargo.toml/Cargo.lock/
rust-toolchain.toml/the workflow) so a dependency refresh rebuilds the
image BEFORE the merge, not after. THE PR-TRIGGERED RUNS EXPOSED A
THIRD LAYER: the dependency-priming step built the dummy source for
core_benchmarksonly while Cargo.toml declares TWO benches
(stage4_benchmarklanded with №482) — the manifest parse failed and
the image could not build at all; the stub is added. AND the builder
image tracks the contract (rust:1.85 → rust:1.93 — the 1.85 builder
refuses the manifest exactly as the new rust-version gate intends)
and carries libssl-dev (the locked tree's native-tls lane needs the
system OpenSSL; the slim image predates that dependency lane). The deploy path
was dead-on-parse AND dead-on-build since №511's landing, unverified
for the whole window — exactly the blindness the №511 job was built
to kill. -
Naryad №526 (issue #835; the consolidated audit A+D 30.09, N-2,
P1 security/voice): the GDPR Art. 17 erasure path for voiceprints
LANDS — the right-to-erasure is no longer a document-only promise
(privacy.md §2.1). THE HOLE: the store had onlyINSERT OR REPLACE
(save), no delete, no list, no purge — a persisted print outlived
every consent revocation. THE PATH:voice_delete(handle)— the
idempotent registry erasure ("deleted"/"absent", a repeated erase
succeeds; Voice handles erase the biometric print, Audio handles erase
the artifact bytes; wrong types refuse loudly) andvoice_list()—
the informed-deletion basis (ids + models/sizes; the listing NEVER
decrypts, the embedding bytes never enter any result). Neither is
feature-gated: the erasure right cannot depend on a build flag. The
PERSISTED twin:VoiceStore::delete_voiceprintruns the
SECURE-DELETE path — the ciphertext blob is zero-overwritten in place
BEFORE the row removal and the same-name voice_artifacts row is
purged the same way ("файл артефакта + запись реестра") — and
VoiceStore::list_voiceprintsenumerates name/model/saved_at/algo/
ciphertext-length without a key. HONEST BOUNDARIES (loud, not
silent): SQLite cannot guarantee per-row block-level erasure
(freelist/WAL page images persist until reused) — the row-level
overwrite erases the row's live copy, the file-level guarantee stays
the DB owner's; the consent_ledger rows SURVIVE erasure by design
(the Art. 9 consent proof). privacy.md §2.1/§3 rewritten in the same
PR (the №524 rule): "Delete path: ABSENT" → "available since the
v0.27.x line", voice_delete/voice_list removed from the ABSENT list,
the anchors re-verified by hand. Tests: the store cycle (save → list
→ delete → list empty), the zero-overwrite pin, the reopen/no-
resurrection restart test, the artifact-row purge, the registry
cycle, the builtin idempotency and loud-type refusals; the
classification SSOT and REFERENCE.md carry both builtins (505→507). -
Naryad №525 (issue #834; the consolidated audit A+D 30.09, Д-2,
P1 process/ci): everyfact_*record ingate_028_goals.txtis now
machine-verified against its generating source by the blocking
gate-facts-syncCI job — a hand-edited drift is a failure. THE
DESYNC: the goals file carriedfact_quorum_num: 9 / den: 9while
the single source of truth (count_duplicated_names.py) reports
ZERO duplicated names since №483 — a liar gate record. THE SYNC: the
counter gains a--quorummode (quorum_num = live groups still
carrying duplicates; quorum_den = the transfer-start group total,
machine-parsed from the baseline history — the 7 №466 group entries
plus the №483 FINAL group — and anchored by a new
groups_at_transfer_start: 8marker in the baseline header, which
the mode cross-checks against the counted entries); the honest record
is now 0/8 (goal 1/3 — MET).fact_open_high_serveris verified
against the liverelease-blocklabel count via the GitHub API
(token always present in Actions; locally the check skips loudly —
the ADR-0179 §6 step-1 sync stays a release-time human step). An
unknownfact_*without a machine source fails the gate (fail-closed:
a fact without a checker is the Д-2 class re-opening). The negative
test (--tamper-test, a CI step): every tampered fact value, and an
unknown fact key, trips the check — the traps all spring. -
Naryad №524 (issue #833; the consolidated audit A+D 30.09, Д-1,
P1 docs): the limitations.md row for the serve-distillation defect
reads Fixed in №495, v0.27.1 — its own condition had come true
(№495 merged, v0.27.1 released, the both-backend e2e
tests/naryad_496_distill_serve_e2e.rsstands), so the row obeyed the
rule it prescribed: closed IN THE SAME PR, by the naryad number named
in its own text, the defect text kept as the historical record — never
removed silently. The closing rule itself (a limitations.md row closes
in the same PR as its fix, by the number from its text — the gate-0.28
checklist item, ADR-0179) is formalized as a wave-reporting protocol
point and a candidate row for the №535 blocking-checks table. -
Naryad №523 (issue #832; the consolidated audit A+D 30.09, N-1 High,
P0 release-block): semantic findings now BLOCKmlog runand
mlog serve— the liar string is gone. THE HOLE:run_program_with_dir
blocked only two substring classes (contains("distill_to"),
contains("[DENY_")),mlog serveran NO semantic pass at all, and the
tree-walking interpreter returned the string
"[ERROR: unknown function 'x']"as the call's VALUE — non-empty
strings are truthy inis_truthy, soif is_admn(user)with no
is_admndefined took the then-branch: a security check became its own
bypass. THE FIX: (1) everysemantic::check_programerror blocks the
run path and the serve startup; exemptions classify ONLY by the
structuredSemanticErrorKindthroughsemantic::is_exempt_from_blocking
— the ONE explicit place, empty today — never by message substring;
(2) the gate resolves the program's import tree STATICALLY first
(semantic::resolve_imports_statically, the same file-lookup rule as
the runtime loader and the bytecode compiler'sresolve_import,
visited-set on module path, main-file-wins dedup with first-wins
module collisions — the runtime flat-merge warns, so the gate must not
invent duplicate-name errors), which removes the historical
false-positive class that once justified NOT blocking; (3) both TW
unknown-function origins (run's call resolution and
eval_expr_with_env) return the coded err-origin
[UNDEFINED_FUNCTION] undefined function: 'x'instead of the truthy
string (the №479 stable code); (4) the refusal carries the FIRST
blocking finding's stable code at position 0 —
[UNDEFINED_FUNCTION] Compilation error (Naryad #523): ...— machine
consumers read the class, not the prose; (5)check_programwalks
ROUTE bodies for calls too (the №264-mutability-walk precedent) — an
undefined function inside a route body now refuses the TW serve startup
instead of failing per-request. The gate surfaced three PRE-EXISTING
spec-vs-implementation arity drifts, fixed at the SSOT:
session_set2→3 andsession_get1→2 (the implementations
hard-require the session-id forms),session_clear0→1 (left the
arity-0 "variadic" list that never checked anything),
respond_html1→2 (status + html);forget(the №72 memory surface
1..2 + the registry surface 3..4 — a non-contiguous union) and
render(template params are data, the 1-arg form is the №448
taint-lift surface) are DYNAMIC-ARITY names — the static check stays
silent, the builtin stays the loud runtime validator. Contracts
repaired per place:examples/p50_unknown_fnflipped from a .expected
golden (the liar string enshrined as the expected stdout) to a .error
contract;examples/p2_multi_errors.errorupdated to the gate text
(the unknown-type refusal arrives one layer earlier);
examples/p71_retry_demodropped itsenv_getreference (a function
that existed in NEITHER backend — the №513 crosscheck exclusion dies
with it) for a placeholder header, since reading env() straight into
network headers is the №391 PII_EGRESS_NETWORK violation; the
REFERENCE.md vector sample runs as its owndoc-test: skipblock
(embed/vec_store arevec-feature, off by default); the №465
diff-fuzzer's three phase-family known-classes closed by the ratchet
(the TW side refuses at the gate with the same №479 code the VM compile
side always had) — the lines and their corpus examples are removed.
Tests: tests/naryad_523_semantic_gate.rs — the exploit shape refused on
TW (gate) and VM (compile), the interpreter err-origin pinned (no liar
string from any call site), serve startup refusals through the real
run_server(route body and pattern body shapes), the
import-using program still runs (no false positives), the №181/№392
classes still block, the exemption list pinned empty. -
Naryad №519 (issue #803; the consolidated audit 28.09 C-20, P2 — the
contribution Г tied to the C-06 voice work): the engineering privacy policy
LANDS —docs/privacy.md, an honest inventory of what the runtime stores,
where, how it is deleted, and what it refuses to promise. In the
REALITY.md discipline every claim carries a verdict — CONFIRMED (code
anchor given), PARTIAL (implemented with the named caveat), ABSENT (not
implemented, the load-bearing list) — and the page opens with the
boundary statement: an engineering policy, NOT a GDPR declaration, NOT a
legal document; the deployer owns the compliance case. The inventory
covers the storage model (no hidden data directory — every persistent
path is program-declared, operator-env-set, or an explicit sandboxed
save/export), the per-category data map (voiceprints — honest AES-256-GCM
library code that is UNWIRED, the voice builtins being loud stubs, so a
shipped runtime stores zero voiceprints; memory lanes with the three
delete flavors — hard DELETE, the №280/№445 soft forget-ledger with an
owner-side vacuum, restart eviction; contacts/calendar as CalDAV/CardDAV
clients with data on the remote server and plaintext session creds in RAM
as a named gap; the process-local consent ledger and RAM-only likeness
tokens; sealed media with mandatory sidecar manifests; opt-in
metadata-only LLM traces; the semantic-cache input-embedding caveat;
profile as a read-only KV projection; the №515-bounded registries), the
consolidated SQLite table map, the encryption-status table with the
plaintext frontier named openly, and the retention defaults. Cross-links:
threat-model.md (the voice at-rest row now points at the file),
limitations.md (the voice-pillar removal condition now points at the
file), README Documentation table + repo tree, llms.txt. The claims never
exceed what the code does: every ABSENT (voice_delete,
memory_forget_all, a consent purge, session eviction) is stated as ABSENT. -
Naryad №518 (issue #802; the consolidated audit 28.09 C-19, P2 — the only
survived contribution Г not covered by В19): the dependency-tree gate LANDS —
deny.toml(cargo-deny) plus the blockingcargo-denyCI job. Before this
naryad nothing verified dependency LICENSES (the cargo-audit job covers
RUSTSEC advisories only), while the project ships as "MIT OR Apache-2.0" —
a legal exposure the moment an external adoption audits its transitive
licenses. The gate runscargo deny check licenses advisories bansover
the whole graph, ALL targets (409 crates at check-in). The allow-list is
the OBSERVED permissive set compatible with the project licensing (MIT,
Apache-2.0, BSD-3-Clause, ISC, Unicode-3.0, BSL-1.0, Zlib, Unlicense, 0BSD,
CDLA-Permissive-2.0 — the last one iswebpki-roots' root-CA data bundle,
a certificate list, not code), every entry carries its reason inline (the
imap/ADR-0109 comment discipline). Advisories: vulnerabilities FAIL, the
unmaintained findings stay advisory warnings —unmaintained = "none"
mirrors the cargo-audit job posture (bincode RUSTSEC-2025-0141 and
ttf-parser RUSTSEC-2026-0192 are the two known warnings at check-in; the
RUSTSEC visibility for those lives in cargo-audit). Bans: duplicate
versions warn, never fail — the explicit naryad choice. One license
exception documented:ical0.11.0 ships an Apache-2.0 LICENSE file but
forgets the manifestlicensefield — recorded via aclarifyentry, the
missing-field warning stays visible on every run. The branch-protection
required-contexts list onmaingrows to 21 entries ("cargo-deny
(blocking)"); the README CI badge moves 27 → 28. Mutation-verified: the
check with an empty allow-list rejects every crate in the graph (red),
dropping CDLA-Permissive-2.0 from the allow-list turns the run red at
webpki-roots,unmaintained = "all"turns the advisories run red on the
two RUSTSEC ids — the gate is load-bearing, not decorative. Scope note:
the naryad does not updateimapand does not touch dependency versions —
control and documentation only. -
Naryad №517 (issue #801; the consolidated audit 28.09 C-06 step 2, P2 —
the wave-21 security/voice): the honest at-rest crypto for voiceprints
LANDS — the №512 fail-closed refusal lifts where it can be lifted
honestly. The real runtime persists voiceprints under AES-256-GCM (the
SAME primitive as the encrypt()/decrypt() builtins, the existing
aes-gcmcrate — no new dependencies); the key arrives ONLY through
the secret()-gate semantics (env-sourced hex-256, NEVER derived from
the name); every write carries a fresh random 96-bit nonce; the blob
is self-containednonce ‖ ciphertext+tag; the schema carries the
algolabel ('AES-256-GCM-v1'). A keyless save stays fail-closed
([VOICE_INSECURE_STORE] — biometric data is never persisted
unencrypted); a legacy pre-№517 XOR row is NEVER silently read as
decrypted — the load refuses loudly ([VOICEPRINT_STALE],
"re-enroll" — the №504 migration posture); a wrong key refuses at the
GCM auth tag ([VOICEPRINT_DECRYPT]). The mock runtime keeps the
insecure placeholder path for the skeleton tests with the insecure
mark VISIBLE IN THE SCHEMA ('INSECURE-XOR-MOCK' — a mock row can never
masquerade as encrypted). threat-model.md now states the true at-rest
contract with the residual exposure named (key management belongs to
the deployment); the №512 honesty pins evolved with the truth (the
crypto name is REQUIRED in store.rs, FORBIDDEN elsewhere in
src/voice/). Blocking tests (tests/naryad_517_voice_aes_gcm.rs): the
keyed roundtrip, the blob-is-ciphertext (not plaintext, not
name-XOR), the wrong-key refusal, the keyless refusal, the loud
legacy migration, the per-write nonce uniqueness, the mock schema
mark. Mutation-verified: a silent legacy read turns T4 red; a fixed
nonce turns the uniqueness test red. The privacy policy cross-ref
follows in №519 (docs/privacy.md). -
Naryad №515 (issue #799; the consolidated audit 28.09 C-12, Low/Medium —
the wave-21 P2 reliability): the process-global registries are BOUNDED —
growth under errors no longer linear. THE HOLE:PDF_DOCS(the
in-progress PDF store) removed an entry ONLY inpdf_save— a route
that calledpdf_createand then errored or was abandoned before the
save accumulated orphans without limit. Now the store is capped
(PDF_DOCS_MAX= 64) with OLDEST-sequence eviction on insert (an
in-flight doc is never the victim unless it outlives MAX newer
creates); the same patch family lands for the same confirmed class:
VIDEO_REGISTRY(VIDEO_ARTIFACTS_MAX= 64 —remove_artifacthad
zero production callers) andVOICE_REGISTRY(VOICE_ARTIFACTS_MAX
= 64, both the audio artifacts and the mock-path voiceprints).
LLM_STREAM_REGISTRYwas already bounded since №263 (the loud
STREAM_LIMIT_REACHED);GLOBAL_TEMPLATESis keyed by declaration
NAME (overwrite semantics — bounded by the program surface, not by
requests). Blocking tests (in the lib suite, serial): 1000 abandoned
creates leave the store AT the cap; the oldest orphan is gone; the
newest in-flight doc survives; 1000 video/voice inserts bound both
registries. Mutation-verified: removing any eviction turns its bound
test red. The honest boundary is on record in docs/limitations.md:
bounded ≠ request-scoped — full request-bound isolation is a separate
infra naryad if a workload ever demonstrates the cap breaking a legit
program. -
Naryad №514 (issue #798; the consolidated audit 28.09 C-10, Medium —
the wave-21 P1 hardening): the ONE soft-failure rule lands for the
conversions — "silence is visible in the name" (ADR-0180, the №481
env/env_or naming rule). THE HOLE:to_float("abc")returned0.0
silently — the audit's vector (to_float(json_body().amount)with
"12,50"→ 0.0 → a zero-value charge with no error). Now
to_float/to_intREFUSE a non-numeric string LOUDLY with the
stable[TYPE_MISMATCH]code, naming the input and pointing at the
_ortwin; the explicit fallback lives in the NEW builtins
to_float_or(value, default)/to_int_or(value, default)
(registry 503→505, append-only) — the parsed value when the string
parses, the default when it does not, every fallback firing announced
on the audit stderr ([TO_FLOAT_OR]/[TO_INT_OR]— the №326 posture:
the FACT, never the value). A non-scalar input and a non-Float default
stay LOUD in the_ortwin too — explicit silence covers DATA, not
type errors;Bool → 1.0/0.0and float truncation are conversions,
not silence — unchanged. ADR-0180 §3 publishes the silent/loud
INVENTORY by group (math, io, memory, http/llm) with every preserved
soft site carrying its owning naryad (read_file№254,mem_get/
session_getKV-shape contract); new soft sites are FORBIDDEN — the
_orform is the only way in. The stale crypto.rs comment claiming
env()is soft (pre-№481) is corrected and pinned by the
source-honesty grep. BREAKING (the intended 0.28 change): programs
converting non-numeric strings withto_float/to_intnow fail at
the exact line — the migration is mechanical
(to_float(x)→to_float_or(x, 0.0)). Blocking tests
(tests/naryad_514_loud_conversions.rs): the audit vector, the loud
refusals, the explicit-silence twins, the unchanged conversions, the
roundtrip throughto_string, the classification drift guard
(№449), mutation-verified. -
Naryad №513 (issue #797; the consolidated audit 28.09 C-09, Medium):
the 35 unverifiable examples are checked or honestly COMPAT-tagged,
and the hole is fenced. The audit found 45examples/*.mlogwithout
a.expected/.errorsidecar and only 10 of them mentioned anywhere
in tests/CI (the fact-check t85 confirmed both numbers exactly) —
serve, file I/O and memory, the most dangerous paths, were verified
by nothing. Now every example has ONE of: a golden sidecar
(10 .expected: p11_utf8_full, p4_benchmark, p6_kv_memory,
p8_session_memory, p_p01_respond_html_query_param, p7_cyrillic,
p161_helper, p161_deep_helper, p9_cyrillic_unicode,
p6_template_composition, p71_retry_demo — the stdout captured from
the REAL run, not invented; 1 .error: v05_final_integration whose
failure IS the contract — the UNTRUSTED_DECISION anti-injection
invariant fires on its unsafe pattern), a named blocking test
(tests/naryad_513_example_coverage.rs: the p120_* family through the
REALmlog testbinary — passing exits 0, failing non-zero, multiple
reports BOTH failures, filter runs the selected test only; the
p31/p51 /fast serve goldens — the /slow routes are external-network
demos and are deliberately not exercised; the vm_serve_realistic_dept
a/b/c ONE-LINE fragments composed into a server and run on the VM
backend — their named purpose, "dept-a: ping" per fragment; the
p7_cyrillic golden — the p7_* exclusion in golden.rs predates the
example and left it unchecked), or a COMPAT-513 header tag (14 stale
prototypes whose grammar predates the current parser —
server {→ mlogserver, top-level statements,#comments,
entity-method patterns: NOT checked with invented behavior, the
rewrite is a separate naryad; the reason per file). The inventory
invariant all_examples_have_a_check_or_an_honest_compat_tag is the
permanent fence: a NEW example without a check fails CI. The debt
gate grows the example_uncovered counter (debt_counters №513: the
sidecar-less, mention-less, COMPAT-less .mlog files) with the
checked-in baselineexample_uncovered: 0— moves ONLY DOWN (the
№468 rule); the golden.rs error suite learned the per-example .env
sidecar (the same ADR-0169 §3.4 posture the .expected suite uses). -
Naryad №511 (issue #795; the consolidated audit 28.09 C-05, Medium —
the wave-21 P1 deploy fix): the documented deployment path actually
works and is VERIFIED. The old image died at clap on startup
(CMD ["mlog", "serve"]— thefileargument is NOT optional,
src/main.rsServe { file: PathBuf }), shipped no .mlog program,
carried a DEADENV METALOGOS_PORT(nothing in src/ ever read it —
the port comes from the program'smlogserver { port: ... }
declaration), and was checked by nothing (0 docker mentions in CI,
unchanged since 2026-08-27). Now:ENTRYPOINT ["mlog", "serve"]+
CMD ["/app/main.mlog"]with the shipped example program
(examples/docker_hello.mlog — volume-mount over /app/main.mlog runs
YOUR program without a rebuild); the dead env is REMOVED; the
deploy-time bind host isMETALOGOS_HOST— read ONLY when the
program declares nohost:(the declaration always wins, the №164
loopback default unchanged; the image sets
ENV METALOGOS_HOST=0.0.0.0); the built-inGET /healthroute
answers 200 "ok" with NO side effects (skipped when the program
declares its own /health — the declaration wins, no duplicate-route
panic); themlog health [url]subcommand (default
http://127.0.0.1:8080/health, METALOGOS_HEALTH_URL override, 5s
timeout, exit 0/1) is the HEALTHCHECK command WITHOUT curl
(bookworm-slim has no curl — the audit's curl-based HEALTHCHECK
would have left the container permanently unhealthy); the base
images pinned to versioned tags (rust:1.85-slim-bookworm /
debian:bookworm-slim); .dockerignore keeps the context lean. The CI
job (.github/workflows/docker.yml — weekly + on Dockerfile/server
changes + dispatch) builds, runs the container, probes /health,
exercisesmlog healthand waits for the Docker HEALTHCHECK
"healthy" state — this IS the №509 0.28-gate criterion №24.
Blocking tests (tests/naryad_511_docker_health.rs): the built-in
/health, the program-declared /health precedence, the bind-host
ladder (declaration > env > loopback). -
Naryad №504 (issue #787; the audit 28.09 §3.1 companion, P1 — the
wave-19 tail): the distillation features leave the byte-position
simple_embedding(the sum of bytes overi % dim— NOT a semantic
representation) and land on the hashed TF-IDF form of the
EmbeddingBackendTF-IDF fallback (src/embeddings.rs): offline,
deterministic, network-free, bounded memory by construction (no
tables — the token hashes into its fixed bucket, FNV-1a 64 per the
№487 stable-hash posture; the SAME tokenizer asTfidfEmbedding).
ALL distill paths call the ONE canonical copy
(distill_hub::distill_features→embeddings::hashed_tfidf_vector):
the hub inference, the TW training/inference, the VM
training/inference — the vm.rs "Ported verbatim" mirror wrapper is
DELETED and the №502 mirror metric moves 4 → 3 as the baseline
planned. The honest measurements (tests/naryad_504_distill_features.rs,
printed): the legacy cosine is ~0.79-0.90 for ANY pair of strings —
paraphrases, rewordings, UNRELATED topics (no discriminative power);
the hashed features separate the worlds (unrelated 0.00, reordered
paraphrase 0.89). On the 40-utterance 4-intent corpus at the
PRODUCTION training budget (30 epochs, lr 0.1) both extractors tie
(holdout 0.375, both stay TEACHING — the Dense head barely converges
at this budget); at a converging budget (lr 0.5, test-side only) the
hashed features reach 0.750 vs the legacy 0.250 — the features are no
longer the ceiling, the training budget is the next lever (the
owner's semantics decision, NOT silently moved). Feature-signature
migration:reflex_savepersists the extractor signature
(feature_signature TEXT, the additive ALTER migration — old databases
extended, never rejected);reflex_loadof a pre-№504 row (NULL →
bytes-pos-v1) SUCCEEDS (weights readable) with a loud
"FEATURES CHANGED, RETRAIN THE MODEL" message, and the distill paths
(hub + TW + VM) refuse the confident answer for a signature-mismatched
model (the [AUDIT] distill.feature-stale line, stay on the LLM path) —
a successfulreflex_trainre-binds the weights to the current
features (the single-point rule inReflexModel::train). The №485
separable fixture re-derived for token-level features (the old "aa*"
corpus keyed on the byte extractor's first-character bucket; the gate
contract unchanged); the №496 serve e2e unaffected (single-class
corpus, feature-independent). -
Naryad №516 (issue #800; the consolidated audit 28.09 C-14, Low): the
stale match-support ignores lifted. The audit's grep-protocol found
the whole "TODO: VM compiler does not yet support match with X"
family obsolete — the support landed long ago (compiler.rs
MatchArm::StartsWith→MatchTest::StartsWith; the VM executes it
at both sites; semantic checks the arms) and №510 made the Ne claim
false too. Six ignores lifted, each proven GREEN before the lift:
starts_with compiles + full run (phase19_22 ×2, the C-14 target),
Ne-in-rule-conditions (obsolete by №510), match with string-literal
arms / contains / compare operators (phase18 ×3). The debt gate
thresholds moved ONLY DOWN with the revision record:
ignore 58 → 52, ignore_todo 22 → 16 (dead_code 37 unchanged); the
remaining ignores in the two suites carry real semantic-checker
TODOs — not touched. Blocking CI now runs 6 previously hidden tests. -
Naryad №512 (issue #796; the consolidated audit 28.09 C-06, Medium —
step 1 of 2): the voiceprint store honesty fix. The comments and the
schema claimed "encrypted at rest (AES-256-GCM via the secret() stack)"
while the code XORs the bytes with a key derived from the PUBLIC
voiceprint name — reversible by anyone who reads the table; a
voiceprint is biometric data (GDPR Art. 9 special category) and a fake
crypto label misleads auditors, users and grant reviewers. Now: every
false crypto mention is gone from src/voice/ (the six store.rs sites +
the encoder.rs contract line — the №480 class rule: fix at the common
point, close the class);encrypt_placeholderis renamed
insecure_placeholder(anddecrypt_placeholder→insecure_restore)
tree-wide; in the REAL runtime (noMETALOGOS_MOCK_LLM, the SSOT
predicate №454)save_voiceprintREFUSES LOUDLY with the new
[VOICE_INSECURE_STORE]coded error (the №413/ADR-0169 stamp
convention, deliberately NOT inORIGIN_STAMPED_CODES— the store has
no program-facing surface yet) and persists NOTHING; the mock runtime
keeps the placeholder path for the skeleton tests (the allowed
insecure-mark boundary). Blocking tests
(tests/naryad_512_voice_insecure_store.rs): the loud refusal (stamp at
position 0, nothing persisted), the mock roundtrip unchanged, the
source-honesty pins (no "AES-256-GCM" / old names in src/voice/,
tree-wide rename check), the stable class constant.
threat-model.md "Phase boundaries" + limitations.md "Voice Pillar" rows
added, cross-referencing the future docs/privacy.md (№519). Step 2 (the
real AES-256-GCM crypto) is naryad №517. -
Naryad №509 (issue #792; the audit 28.09 §4 + §3.4п3): the 0.28
release gate criteria v2 (ADR-0179) — the gate flips from
no-regress ratchets to ABSOLUTE goals: the typed-signature share
REACHES the owner-fixed floor (draft 30 bp%, now 9.96%), zero open
High findings in the server path (the release-block label
discipline, the fact record synced at the gate run), the transfer
domain quorum ≤ 1/3 (draft), the №502 mirror metric carried
only-down, and the FUNCTIONAL criterion — every state-accumulating
declaration (distill_to, memory, conversation, cron) has a
both-backend run_test_server e2e (the inventory:
distill done via №496; memory/conversation/cron pending — follow-up
naryads). unfreeze_gate.py gains --gate-target 0.28 (fail-closed on
any missing record; the legacy 0.27.x read unchanged); the release
checklist (docs/release-checklist.md) names the human steps between
the evidence and the owner-only decision. The ADR number moved to
0179 (0178 was taken by the generative-contour boundary in the
meantime — noted in the naryad report). -
Naryad №501 (issue #784; the audit 28.09 section 3.2 minor + 3.7):
the sensitive-path deny vocabulary is CASE-INSENSITIVE — every name
and component comparison (io.rssensitive_path_matchand
sensitive_name_match, fs_gate.rshard_write_name,
hard_write_path_hit,gate_write_resolved, and the №500 canonical
re-check through them) runs on the LOWERCASED form, so.ENV,
App.DB-WAL,APP.MLOG,Metalogos.TOMLand a.GITcomponent
refuse exactly like their lowercase spellings (on macOS/Windows the
same on-disk file must not become reachable by spelling alone). The
hard write-deny classes stay unconditional under any spelling: the
allowlist crane cannot unlockAPP.MLOG,Metalogos.TOMLor.GIT
writes (the №475 image-integrity posture, now case-independent).
Blocking tests (tests/naryad_501_write_deny_case.rs, 8): the four
audit spellings on both gate sides, the allowlist-still-locked
APP.MLOGwrite, the exact-name crane forApp.DB, and the
legitimate-path boundary. Mutation checks: removing the lowercasing
at either point turns the pinned spellings red (2 + 5 tests). The
limitations.md №455 case-sensitivity row is marked fixed; an honest
row documents the absence of a dedicated macOS CI job
(comparison-level equivalence + the pinning tests as the current
verification). -
Naryad №507 (issue #790; the audit 28.09 §3.7):
read_file_or(path, default)— the EXPLICIT-silence twin ofread_file, the_or
symmetry withenv_or(№481) the external audit asked to make
explicit. A MISSING file yields the caller's default (announced on
the audit stderr — the path is named, the value never, the №326
posture); every other branch is shared withread_fileby
construction (oneread_file_implbody, two missing-file policies):
sandbox violations and the №455 sensitive-name deny-list stay LOUD
[SANDBOX_VIOLATION], open/read failures stay LOUD[IO_ERROR]—
the explicit silence never bypasses them. Theread_filecontract
is UNCHANGED (№254, the empty-string soft default stays). Registered
inBUILTIN_REGISTRYappend-only (501→502, the .mbc indices do not
shift), the SSOT classification/reference regenerated, the
curated REFERENCE.md rows + example added. Verified: 5 blocking
tests (tests/naryad_507_read_file_or.rs) — the explicit default
on a missing file, the unchanged №254 base contract, an existing
file yields the content, the..traversal stays loud, the
deny-list stays loud; lib suite green; clippy-D warningsclean. -
Naryad №503 (issue #786; the audit 28.09 §3.6 Low/Medium): the
OUTCOME rule — a TW/VM divergence where one side is ok and the other
is err is a correctness defect BY SHAPE, un-pinnable in the №465
corpus (a known-outcome line fails the parse like a blocked- line;
an Outcome divergence never matches as known at run time). The two
former outcome classes left the corpus in this PR: (b) the
entity-in-pattern gap is FIXED — the TW ident read falls back to the
declaration globals (self.variables, the exact set the VM models
with StoreGlobal/LoadGlobalByName), local bindings shadow, writes
stay local; pinned by tests/naryad_503_outcome_parity.rs (both
backends agree, mutation-verified). (a) the memory_forget compile
gap is root-caused with the repair plan in the naryad report (the
№272 vec-gate vs the TW soft unknown-function fallback vs the VM
loud compile — a semantics decision for the owner). ALSO: the corpus
seed order is DETERMINISTIC now (stateful seed_*.mlog first — the
same corpus was green with one read_dir order and red with another);
the example-reproduces-class ratchet (№479) stays live. -
Naryad №502 (issue #785; the audit 28.09 §3.3): the THIRD dedup
metric — the explicit TW-mirror MENTIONS insrc/vm.rs
(scripts/ci/mirror_counter.py+ the checked-in baseline
scripts/ci/vm_mirror_baseline.txt+ thevm-mirrors (blocking)CI
job next to the ops-pairs gate; also folded into the unfreeze-gate
summary's Dedup criterion 4.2). Neither №462 (name literals) nor
№484 (ops pairs) saw vm.rs's whole-block mirrors — the distillation
drift (the №489 background training in the interpreter only,
vm.rs training synchronously) lived exactly in that blind spot. The
fixed narrow regex (case-insensitive, over comments and
doc-comments):mirror of the TW | ported verbatim | TW-identical | same as the TW. Deliberately coarse (the audit's posture): counts
MENTIONS, a false positive is fixed by rewording the comment, never
by weakening the metric. Movement ONLY DOWN: growth fails the gate.
The baseline = the fact at adoption, after №495 collapsed the
distillation mirror cluster: 4 marks (the №199/ADR-0121 registry
declaration pass; the №456 holdout gate ×2; the simple_embedding
mark predating the №495 unification — moves down with №504). The
per-mark plan (unify / justify) is in the naryad report. -
Naryad №508 (issue #791; the audit 28.09 §3.7): the free field order
reaches the four remaining CONFIGURATION bodies —memory_body
(kv, persist),sandbox_body(allowed, forbidden, timeout),
eval_body(dataset, metric, threshold) andconversation_body
(ttl, max_messages, compress_after) — completing the №490 rule for
the same language constructs. Each body is an unordered repetition
of its fixed field set (silent field wrappers keep the inner rules
directly in the child list, so the.find()-based extraction is
unchanged); a duplicate field is a LOUD parse error naming the field
and its position (guard_unique_field_groups). The evaldataset
requirement moved from the grammar (a positional rule) to a loud
parser error naming the requirement — an explicitly EMPTY
dataset: []stays the legal PASS-by-convention case (the eval
harness contract, total=0 → passed, pinned). The innerkv {...}
sub-block keeps its fixed order (the №490 posture with the llm
provider sub-block). Verified: 10 blocking tests
(tests/naryad_508_grammar_free_order_4_bodies.rs— every
permutation class parses identically, duplicates loud, the eval
dataset requirement loud, the №490 llm bodies regression-pinned);
the eval/conversation/memory/sandbox contract suites green; lib
872/0. The syntax doc names the free order for all four bodies; the
live examples (p2_full_adapt.mlog,p_eval_harness.mlog) show
permuted layouts.
(nothing yet)
- Naryad №496 (issue #781; the audit 28.09 §3.1 п.3 — the audit's
headline recommendation): the blocking END-TO-END distillation test
through the serving path —tests/naryad_496_distill_serve_e2e.rs.
The audit's finding was that NO test exercisingdistill_towent
throughrun_test_server/serve, which is exactly why the №495
defect was invisible to CI; this test closes the class. It boots the
REAL server (run_test_server) on BOTH backends (TW and VM), with
thelearnable patterndeclaration carryingdistill_afterand
distill_to, drives N+10 POST requests through the route, waits for
thedistill.training-*audit events, verifies the DISTILLED
answers stop calling the LLM (the MockLlm call counter freezes) and
that the accumulated examples SURVIVE a server restart (the
distill_samplestable from №495). Red-before pinned at
merge-base0b55b6e(the test could not even complete the
accumulate-then-serve arc there); green-after on №495. Verified:
green on both serve backends; lib 872/0.