verification: fix sig_for so a directory target can ever be Fresh - #154
Merged
Conversation
verified.test/verified.spec_check records always name a directory
(lex_test/lex_spec_check's own tool arg, e.g. "tests" — never a single
file). sig_for used io.read unconditionally, which always errors on a
directory, so harvest always stamped these records with sig: "". Since
is_fresh treats a non-empty target with an empty sig as always Stale,
VerifiedKindSeen("verified.test") and VerifiedTargetSeen(_,
"verified.test") were permanently unsatisfiable for every project that
ever asks for them -- not flaky, 100% reproducible, for as long as the
tool's target stayed a directory.
Found live in lex-economy: the `treasury` task was the first task in
that project to ask for verified.test at all (identity/contract/
reputation only ever asked for verified.type_check), so this had never
been exercised before.
Fix: sig_for now falls back to hashing the sorted concatenation of
every file's path+content under a directory when a plain io.read
fails. Deliberately shells through `find` (`[proc]`) rather than
std.fs.walk (`[fs_walk]`): sig_for is called from linter.lex's
record_verified, which runs from inside edit/write's Tool.execute -- a
row FIXED by the Tool record type at [net, io, proc] (effect rows
unify by equality, not subtyping), so fs_walk is not an option there.
This reuses the same proc-instead-of-the-typed-effect trade
record_verified's own comment already documents for mkdir, rather than
inventing a second one -- and needed only 3 downstream signatures
widened (harvest, finish_turn, attest_verify_pass_if_clean; task_spec's
is_fresh/presence/presence_on/target_outcome/verified_outcome), not the
20+ files a naive fs_walk widening cascades into.
Single-file targets are unaffected: io.read is tried first, so an
existing sig's meaning does not shift for the common case.
lex check (104 files), lex fmt --check, lex doc-sync --check, and
lex test tests all pass.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alpibrupa
added a commit
to alpibrusl/lex-economy
that referenced
this pull request
Sep 8, 2026
test = ["tests"] used TestPasses, which runs `lex run <path> run_all` -- not this repo's actual `tests/test_*.lex` + `lex test` convention, so it always failed. verified_on's per-file verified.test entry was also wrong: lex_test's attestation always targets the whole "tests" directory, never a single src file. Found both while diagnosing the first treasury build's NOT SATISFIED result, which also surfaced a real upstream bug (a directory-target verified.test record's sig was always computed as "" and therefore always Stale) -- fixed at alpibrusl/lex-code#154 rather than worked around here. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
alpibrupa
added a commit
to alpibrusl/lex-economy
that referenced
this pull request
Sep 8, 2026
Fourth module, first effectful one (identity/contract/reputation were all pure). Storage + balance invariants only, per the task's own scope note: balance_cents - committed_cents never goes negative, commit_funds rejects insufficient_funds/currency_mismatch/invalid_amount cleanly with no partial writes (wrapped in a BEGIN/COMMIT/ROLLBACK transaction), settle_commitment can pay less than reserved but never more, and every state change appends a lex-trail event (treasury.committed/released/ settled). lex-guard's Policy/SpendIntent gating is deliberately deferred (see tasks/treasury.task's header) -- its merchant/category shape doesn't map onto a company-to-company commitment yet. Built via lex-code (opencode/kimi-k2.7-code, impl_test_fix_loop_verified) after fixing two real bugs found along the way: the task spec's own test/verified_on criteria were wrong (lex-economy#-, see the previous commit), and lex-code's verified.test attestation for a directory target was permanently unsatisfiable (alpibrusl/lex-code#154). Independently re-verified: lex check, lex fmt --check, and lex test all pass; test suite covers idempotent open, commit/release/settle round trips, the insufficient_funds boundary, double-release rejection, overpay rejection, and confirms no state changes on every error path. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
verified.test/verified.spec_checkrecords always name a directory (lex_test/lex_spec_check's own tool arg, e.g."tests"), andsig_forusedio.readunconditionally — which always errors on a directory — soharvestalways stamped these records withsig: "". Sinceis_freshtreats a non-empty target with an empty sig as always Stale,VerifiedKindSeen("verified.test")/VerifiedTargetSeen(_, "verified.test")were permanently unsatisfiable for any task that asks for them.lex-economy'streasurymodule — the first task in that project ever to ask forverified.test(its earlieridentity/contract/reputationmodules only asked forverified.type_check).sig_fornow falls back to hashing the sorted concatenation of every file's path+content under a directory when a plainio.readfails, shelled throughfind([proc]) rather thanstd.fs.walk([fs_walk]) —sig_foris reachable fromlinter.lex'srecord_verified, which runs insideedit/write'sTool.execute, a row FIXED at[net, io, proc]by theToolrecord type. Usingprocinstead reuses the exact traderecord_verified's own comment already documents formkdirvsstd.fs.mkdir_p, and needed only 4 files widened instead of the 20+ a naivefs_walkwidening cascades into.io.readis tried first, so an existing sig's meaning doesn't shift for the common case.Test plan
lex checkon all 104src/*.lexfileslex fmt --check src/ tests/lex doc-sync --checklex test --allow-effects crypto,fs_read,fs_write,io,random,sql,time tests(4 passed)sig_foron a real directory is stable across repeated calls, changes when a file under it changes, and matches the pre-fix plain-sha256value for a single-file target (backward compatible)🤖 Generated with Claude Code