Commit Graph

7 Commits

Author SHA1 Message Date
Levi Neuwirth b10d5eee88
Declare a raised index-run ceiling without naming a cause
`run_conditions.index_run_ceiling` offered one value for a raise, and it
named a reason: `raised_because_index_sealing_unimplemented`. The emitter
never established that reason. It derives the declaration by comparing the
configured ceiling with the store default, and a comparison of two numbers
cannot know why they differ -- so the bundle asserted a cause on the
strength of a subtraction.

That was accurate while the unimplemented seal was the only reason to
raise the ceiling. Sealing landed, the raise was dropped, and the value
became a false explanation waiting for the next run that raises the
ceiling for any other purpose -- which the schema would have called valid.

`raised_above_store_default` is added and is what the emitter now writes.
It states the fact the comparison establishes and stops there.

The old spelling is retained and deprecated rather than removed.
`schema_version` is `const: 1`, so there is no later version to move
archived bundles to, and this repository has never held a bundle to check
against; the reference machine may hold ones that declare it. Invalidating
evidence already produced is worse than carrying a spelling nothing emits.

Deprecated does not mean unchecked, and that was the trap. The `allOf`
rule flooring a declared raise at 65 accepts either spelling, so a bundle
using the old one is still cross-checked against the recorded ceiling. Had
the rule kept keying on the new string alone, the deprecated value would
have skipped the cross-check entirely and been valid while recording 64.
`both_raised_spellings_are_accepted_and_bound_the_same_way` asserts, for
each spelling, that it validates and that it is refused when it records
the store default; narrowing the rule to the new value alone fails it.

The protocol crate's `submit_path_bundle` keeps the deprecated spelling
deliberately, as the standing proof that an archived bundle validates.

Recorded as contract review 2026-08-09-B. The scope doc's carry-forward
closed on the grounds that the enum was untouched -- true then, superseded
now -- so it carries an amendment rather than a rewrite, and its "truthful
values today" table now points at the corrections recorded elsewhere in
the same document.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JGdH5V43XWnj1PdHqiPktQ
2026-08-09 18:38:04 +02:00
Levi Neuwirth 062797d4b8 Condition benchmark claims on the run that produced them
A bundle's verification flags were assertions about methodology that
nothing checked. This makes them conditional on machine-readable
declarations of what the run actually did, and fixes five cases where the
emitter stated something it had not established.

bench/result-schema.json gains a required run_conditions block of ten
closed enumerations -- initialization and mutation path, checkpoint and
index state, index-run ceiling, receipt reconciliation, objects_new
source, commit-id uniqueness, build profile, environment fidelity. It is
not disclosure beside the claims; it is what the claims are conditioned
on, so a harness can only assert what its declaration permits. A prose
caveat field was rejected: free text is not a condition a consumer can
check, and a bundle whose caveats live only in a report reads as
unconditional to everyone who receives it.

The branch conditional forbids the three newly earnable claims on the
journal-drive path and pins its four provenance declarations to the only
values that seam can make. Forbidding the claims alone left the hole one
field over -- a drive bundle could otherwise declare exact receipt
reconciliation it has no receipts to perform.

Emitter defects, each found by reading the schema against the code:

  - Setup traffic was inside the measured interval. Both counter baselines
    were read only at the end, so repository creation -- which goes through
    submit, and therefore fences and signs -- was counted as measured work
    while the bundle asserted setup_traffic_excluded. A false exclusion
    claim is worse than a wrong number: a wrong number invites scrutiny
    and this deflects it.
  - A zero-work run produced a schema-valid bundle asserting uniqueness
    over zero ids and three-objects-per-commit over zero commits. Both are
    vacuously true, which is why they must not be earnable that way: the
    result is indistinguishable from a measured run by the consumer the
    schema exists to serve. Refused by name at two altitudes.
  - An ACK-journal write failure ended the run quietly. It set a stop flag
    without recording a refusal, so neither the fatal guard nor the
    zero-work guard saw it, and the bundle omitted a committed transaction
    while still counting its fence and its signature -- one counted
    transaction against two fences and two signings. It is now a fatal
    incomplete-accounting refusal carrying the original errno, because the
    commit happened: folding it into the refused count would report a
    transaction the store committed as one it declined.
  - Widening that class to "a failure that produces a value nobody read"
    found three more. A shard with no counters summed to zero fences,
    silently shrinking the total that bounds every durability claim. A
    digest of an unreadable file returned the digest of empty input -- a
    well-formed 64-hex value indistinguishable from a real one, feeding
    five attested fields. An unreadable /proc/meminfo published one byte
    of RAM. All three refuse now.
  - Index steady state was inferred from any directory entry, so one stray
    file declared the index sealed. Entries are parsed back as index runs
    against the root's own uuid; an unparseable entry is reported as
    unvalidatable rather than lowering a count, and a backlog is refused
    because neither named value describes sealing that did not keep up.

deployment.tmpfs, persistent_data_mount, and hardware.filesystem were
constants -- the emitter could assert deployment facts it had never
checked. They are read from /proc/mounts now. Both deployment fields relax
to booleans so a diagnostic run is representable at all: it was previously
not disqualified but unencodable, and a schema that can only express
successful runs is not a record of what was measured. outcome=pass
requires reference fidelity at every gate, and a diagnostic run may never
carry a pass verdict.

Hardware profiles are derived, never accepted. store-bench parses the
whole frozen profile tables and names a profile only by exact comparison,
iterating every pinned fact rather than every supplied one -- so a fact
the emitter does not model eliminates the profile instead of being
invisible. That took the honest unobserved list on this host from 7 facts
to 24, which is the inversion working. A deployed-node harness supplies
privileged facts as evidence to compare, never as a label. The outcome
derivation now also requires the checkpoint and index conditions, because
fidelity was the only thing preventing a pass and would have stopped being
so the moment profile recognition started working, at which point the
emitter would have produced a pass its own validator rejects.

operation_receipts_reconciled is expressible and deliberately not emitted:
the bench accepts any Committed status without comparing the payload, and
the digest it records is of the operation id rather than the receipt.

Contract review 2026-07-28-C records the amendment and the ceiling it does
not close: run_conditions is self-reported, and only the index-run ceiling
is cross-checked against an independent value. Scope 5.1 records why a
full filesystem is indistinguishable from a concurrency flake by symptom,
and that an I/O error must reach a report with its errno intact -- the
same requirement as the incomplete-accounting refusal above.

scripts/check-phase1.sh GATE_EXIT=0; verify-store-recovery.sh reports
bundle=schema-valid on both paths, zero_work_run=refused, and
unaccounted_ack_run=refused.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-29 17:38:03 -04:00
Levi Neuwirth ef7d263fed Amend Wave B storage contracts after adversarial review
The B2 review of the first B1/B3 slice found five defects whose fixes were
not available to the packages that had to make them: each needed a change
to a surface those packages do not own. Rather than let them restate a
frozen fact locally, the lead amends the surfaces and the packages consume
them. Contract review 2026-07-28-A records all five with the weaker
alternative that was rejected for each.

  - CommitEvidenceSigner::sign_event's parameter becomes signing_digest.
    The message is SignedCommittedTransactionV1::signing_digest, which
    binds key epoch and durability result around the canonical event; the
    bare event digest is chain identity, not signature material. A doc line
    was not enough: misuse is undetectable until after durability, and is
    first observed by a mirror on another instance.

  - TransactionEvidenceV1::actor() is new, exhaustive over all six
    variants. A destination event's actor must restate the evidence's
    source instance, and deriving it a second way store-side is what
    produced the defect it closes.

  - format::object_type_code becomes pub(crate) and recovery.rs's private
    twin is deleted, so one table exists where three did.

  - RefRecord gains from_target/target() in terms of RefTarget, with the
    code table stated once per direction and an unknown kind refused by
    value as CheckpointError::RefKind. Defaulting an unknown kind would
    launder it into the next checkpoint within one interval.

  - max_projection_objects is capped at MAX_CANONICAL_ITEMS and its default
    lowered to it; max_projection_chunks likewise, and max_projection_bytes
    against the transitive per-chunk allowance. The old default described a
    projection no manifest could encode. Supporting a hundred million
    objects requires a versioned chunked or indexed manifest design, not a
    larger hostile-decode ceiling.

Both governing documents are updated where they now misstate a frozen
fact, including §6.4's claim that signing covers the event digest. §6.5
records the two carry-forwards from B3's first slice — production staging
use is incomplete, and expire() has no scheduler — and records that the
requested StoreEngine staging accessor is a pending amendment which must
not be a bare Arc<ProjectionStaging>, since a clone could outlive
EngineShared, survive release of the root LOCK, and keep serving a root
this process no longer holds.

No package file is touched: engine.rs, transaction.rs, and staging.rs
consume these in their own commits.

levcs-protocol 6 consumer tests, levcs-store 109 library tests.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-28 18:01:05 -04:00
Levi Neuwirth e85a159525 Scope Wave B storage work 2026-07-27 21:31:52 -04:00
Levi Neuwirth 5ee9c6b78b Freeze Wave A: Phase 1 storage spine
Freezes the Wave A interfaces, frame format, durability ordering, and
crash/fault fixtures per doc/phase1-storage-spine-scope.md section 5. Wave B
may now start against these.

D0 lands crates/levcs-store: the sealed public API, the file-ownership split,
the single durability syscall funnel with its counters and fault hooks, the
17-entry failpoint registry in compiler-enforced correspondence with
oracle::AppendFailpoint, and the journal-level drive seam. Wave A lands the
frame codec and journal/segment lifecycle (A1), the recovery index and
checkpoints (A2), and the durable-ingest benchmark and crash harness (A3).

The adversarial review found five defects behind a green gate, three of them
blockers, all closed here. Two were the same shape: drive::reopen_through_
recovery had reimplemented a simplified recovery and called none of recovery.rs
-- so it adopted segment footer sequences without validating frame bytes,
journal_id, or root_uuid, and it double-adopted interrupted seals. The seam
between two packages was untested precisely because each package's own tests
passed. reopen_through_recovery now delegates rather than decides, and
DriveRecovery carries the recovery report verbatim so tests assert the
disposition and not merely its effect: a double adoption and a correct replay
produce the same adopted set, which is how the defect stayed invisible.

Also closed: store-bench now schema-validates its own emitted artifact with a
five-mutation negative control instead of matching JSON substrings; the ACK
reconciler distinguishes duplicates and regressions from forward gaps;
checkpoint writes and a journal truncation are routed through the durability
funnel, whose guard now covers writes and truncations rather than only sync,
rename, and unlink.

bench/result-schema.json is amended (contract review 2026-07-24-B, second and
third amendments): per-gate latency ceilings conditional on outcome so a failed
run is representable, and the verification claims split per gate so a storage
run cannot certify an object graph it never touches. Not-applicable claims are
forbidden rather than falsified; applicable-but-not-performed report false.
Every relaxation is re-pinned in the else branch and asserted member by member,
after an edit in this series silently un-pinned all eleven validation flags and
was caught only by revalidating against constructed bundles.

Arming the fault registry now requires a FaultSerial token, so the invariant is
a compile error rather than a comment. The file where this was diagnosed
carried a header saying it was deliberately the only test in it, and a second
test had been added under that comment anyway -- an 8-in-40 failure rate that
read as flakiness.

Evidence at this commit: check-phase1.sh GATE_EXIT=0 across all four feature
configurations, 124 test binaries, zero failures; verify-store-recovery.sh
--cycles 100 with recovery_failures=0, acknowledged_loss=0,
torn_transactions=0, repeated_adoptions=0, bundle=schema-valid; recovery_eio
40/40 at four test threads; golden corpus byte-stable; fmt clean.

Carry-forwards, explicitly not Wave A blockers and recorded in scope section 5:
extend the crash matrix to generate sealed-frame corruption and cross-shard
journal movement, since it structurally cannot express the class the first
blocker belonged to; and wire GroupBuilder through B1's production path, since
deliverable 4-A1.2 is presently asserted only over a type nothing calls.

Charter item 9 -- ask every package what of its work is correct but uncalled --
is accepted for every subsequent wave. It, not the review, found the class.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QNy4Ve7mogg4X1ezJTnFxG
2026-07-26 19:47:03 -04:00
Levi Neuwirth 1996db37b2 integrate CI and docs 2026-05-01 11:29:18 -04:00
Levi Neuwirth 21c6056ae6 levcs 0.1.0 - initial core 2026-05-01 11:14:36 -04:00