Skip to content

A typed, append-only record for the project - #38

Draft
nrdxp wants to merge 10 commits into
masterfrom
campaign/record-substrate
Draft

A typed, append-only record for the project#38
nrdxp wants to merge 10 commits into
masterfrom
campaign/record-substrate

Conversation

@nrdxp

@nrdxp nrdxp commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

What this is

A typed, append-only record for this project, built as Nickel contracts with a YAML fixture corpus and a Lisp suite driving them. The premise is that everything else the tool does is a projection downstream of one artefact — a record whose entries state what is claimed, who claims it, and what backs it — so that artefact is specified and machine-checked first, before anything is built on top of it.

An entry carries a grade that says how it is backed. A claim is backed by a check that was actually run, or by a named witness's testimony, or it is derived from other entries and must name them. A question carries the condition that would close it and who can close it. The contracts refuse documents that do not fit, and the refusal names the rule that fired.

What has landed so far

The core entry and evidence contracts. contracts/record/entry.ncl types an entry — its identity, signer, anchor, grade, statement, edges and registers — and contracts/record/evidence.ncl types evidence as an independently signed entry naming its target, rather than as a field on the thing it supports. 54 fixtures under t/fixtures/record/entry/ exercise it, driven by t/record-schema-tests.lisp.

The party, policy and event contracts. contracts/record/party.ncl binds a name to a key and to a declared pen — the entry classes that party may write — and covers lineage declarations, delegation, and the reserved-seat registry. contracts/record/policy.ncl types correlation-class policy, whose admission path differs. contracts/record/event.ncl types protocol events with a closed mapping from kind to payload, carrying the refusal and dispatch kinds. 28 fixtures under t/fixtures/record/party/ and t/fixtures/record/event/, driven by t/record-parties-tests.lisp.

How to review it

The suite is the entry point: just test runs 2617 checks and exits 0 at the current tip. Every fixture whose name ends -fail must be refused, and every fail-side assertion in the newer suite names the rule it expects to fire, so a fixture refused for the wrong reason shows up as a failure rather than a pass.

To see a contract refuse something directly:

nickel export t/fixtures/record/party/lineage-declaration-duplicate-party-fail.yaml \
  --apply-contract contracts/record/party.ncl
# exit 1, naming LineagePartiesDistinct

The contracts are commented with the reasoning behind each closed set and each coherence predicate, which is where to look when a rule seems arbitrary.

Deliberately out of scope

The contracts describe and refuse; they do not yet run anything. Reproducing a check at its anchor, deciding admission at write time, and gating dispatch entries are separate pieces of work landing on this branch later.

Known gaps found during this work and not being fixed here are indexed in #37.

Checklist

  • Core entry and evidence contracts with their fixture corpus.
  • Party, policy and event contracts, including the reserved-seat registry.
  • Check runner: reproduce a recorded check at its anchor and refuse the corroborated grade when reproduction fails.
  • Admission gate: the determinate refusal classes decided at write time, each refusal recorded as an event rather than an exit code.
  • Dispatch gate: dispatch entries checked for their bound procedure hash and the verbatim presence of every operative parameter.
  • Seed corpus exercising every element class the format defines.
  • Query engine verification measured against that corpus.

nrdxp added 10 commits August 31, 2026 11:45
Node N1 (campaign 9) needs a failing acceptance surface before the
entry.ncl/evidence.ncl contracts exist, driven from the architect's
34 format elements and node-boundaries.md [B6]'s four acceptance
criteria. 45 fixtures (28 entry-, 17 evidence-) pin the concrete field
shape the implementer builds to; 48 fiveam assertions verify each
fails for exactly one diagnosed reason (the contract file is missing),
never a Lisp typo, distinguishing a genuine contract refusal from an
accidental pass caused by the contract not existing yet.

Not registered in librecode-test.asd -- outside this dispatch's
declared surface -- so `just test` does not yet run this suite; see
the node's return for that gap.
Nickel contracts for the campaign-9 record schema (node N1): entry.ncl
and evidence.ncl, derived field-by-field from the architect's 34 format
elements and the admission-policy/observation rulings. Turns the
red-first fixture suite in t/fixtures/record/entry/ green.
Wire record-schema-tests.lisp into librecode-test.asd (amended
surface). Registration exposed a latent path-plumbing bug: resolving
fixture/contract paths from *compile-file-truename*/*load-truename*
lands in ASDF's fasl cache under a fasl-based load, not the source
tree, once the file loads through asdf:test-op rather than a direct
`sbcl --load`. Switched to asdf:system-source-directory, which is
stable under both invocation styles.
The lead-maintainer's merge gate withheld consent over three defects
in node N1's entry/evidence contracts, found after the schema was
otherwise green (45/45 fixtures, 2550 checks).

Defect one: entry.ncl was a closed record with no `statement` field,
so it refused one outright rather than merely under-requiring it --
the same omission covered a question's `discharge` signpost and a
directive's `provenance`. The prior test-worker read [F51] as
excusing these fields ("required by no determined query") without
its next clause: "mandatory, on the human seam's authority and not
on a query's." All three are now admitted; discharge and provenance
each carry their own presence/absence coherence rule, and every
pre-existing fixture now carries `statement`.

Defect two: evidence.ncl's species rules were one-directional -- a
vouch validated regardless of what else it carried, and likewise for
a corroboration. Both are now bidirectional, the model being
entry.ncl's own CloserCoherent: each species requires its fields and
refuses the other's.

Defect three: the derivation edge's `role` qualifier had no fixture
at all, leaving node-boundary acceptance criterion (d) -- every
format element exercised by at least one fixture -- unmet for
element 17.

Nine new fixtures pin the negative half of each defect; the fixture
inventory count and this suite's header now read 54 (35 entry, 19
evidence).
Node N2 needs party, lineage, delegation, correlation-class-policy,
reserved-seat-registry, and typed-event contracts before the
admission gate (N3) can refuse out-of-pen writes and reserved-seat
overreach. This pins their field shapes as 28 fixtures plus the
acceptance suite, derived from format-elements [F19] [F21]-[F23]
[F45] and the admission-policy log, following N1's bidirectional-
coherence and closed-record idioms so the implementer builds to a
fixed interface rather than inventing one.

Registered in librecode-test.asd per the standing directive that
registration is the test-worker's own surface; verified red through
`just test` rather than a direct load, since a direct-load
verification was N1's own blocking defect.
Node N2 of campaign 9: implements the schema for the party
declaration, lineage declaration, delegation entry, and reserved-seat
registry (party.ncl), the correlation-class policy (policy.ncl, kept
a distinct entry class from lineage since their admission paths
differ), and the typed protocol event closed to the refusal and
dispatch kinds (event.ncl). Every kind/class multiplexing is
bidirectional (requires its own fields, refuses every other
branch's) rather than repeating N1's one-directional species-
coherence defect. party.ncl deliberately avoids std.contract.any_of
for its four-shape dispatch: it loses the specific rule name in its
failure message, so the four branches are instead selected by a flat
optional-field shape plus per-branch coherence predicates, mirroring
entry.ncl's own kind/register multiplexing. Brings the red suite in
t/record-parties-tests.lisp to green with no fixture changes.
The lead-maintainer's gate reproduced a lineage declaration naming the
same party twice validating at exit 0: it is one party counted as two,
not two correlated parties, and no downstream count can tell the two
apart once admitted. LineagePartyCountExactlyTwo only checked length,
not distinctness. A seat registry naming one seat twice had the same
gap one class over. Both are closed by one shared AllDistinctBy fold
rather than duplicating it per shape.
policy.ncl's own ClassesNonEmpty already refuses a policy naming zero
classes since it partitions nothing, but a named class carrying zero
members passed the same reasoning one level down: it partitions
nothing either, and nothing in ClassesNonEmpty ranges over a single
class's own member list.
event.ncl credited [AG2] alone with all five determinate refusal
classes, but [AG2] names only four (unsigned_or_unattributed,
signer_equals_author, out_of_pen, reserved_seat_overreach) -- the
fifth (cycle) belongs to node-boundaries [B8]'s own scoping of N3.
check-fail asserted only a non-zero exit, never which rule fired --
the same conflation this project's own record already carries two
prior instances of (a probe read by status that was actually
malformed; a contract whose refusal reason was the only thing telling
a regression from a wrong-shaped input apart). Every fail-side call
site now names the expected diagnostic substring, and the three new
REWORK fixtures (duplicate lineage party, duplicate seat, zero-member
class) get their own tests pinning the rule each one exercises.
t/record-schema-tests.lisp (node N1) carries the identical
status-only conflation in its own check-fail but sits outside this
node's declared surface -- left untouched, reported as a coupling.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant