Development record
The admission and surprise verbs: one act, two records, and the ruled order held as a refusal
Summary
spc-2609020626040342 delivers
itd-2609020625400194.
abcd capture admit <rdi-N> --grounds "<text>" records an admission as one
act under the ledger lock: The item's accepted disposition carrying the
grounds, and the admission record joining it to its run's candidate set. Where
an accepted disposition already stands, the admission record is written
alone, and only on the same ground. It refuses an item that is not a widening
item, an item already admitted, a standing disposition in any other state, and
a ground below the substance floor. The ruled order, characterise first and
admit second, is held as one gate in the shared disposition writer: At the
widening position no disposition in any state is written until a committed
comparative run names the item's run, so capture disposition, capture admit and the scribe's ingest all refuse the same way and name what they are
waiting for. abcd capture surprise --occasioned-by <id> "<text>" writes one
surprise entry as its own record and refuses an occasion that does not resolve
to a reading item, an admission or a disposition. abcd adm-N and abcd srp-N dispatch, and the outstanding report gains a per-run summary so the
admitted-against-declined count is a query.
The flagged decision is built under its flagged reading: Admission is the
accepted disposition plus the admission record, written together, which
refines
itd-180
and closes the state
spc-67
left, where an accepted item was still reported unadmitted.
Scope
In: The two verbs, their core functions in internal/core/capture, their
refusals, and their flags; the shared disposition writer, the ordering gate
in it and the probe the gate reads; the one-lock write of two records, and the
mint of disposition moved inside the lock; the dispatcher's three new
families and the plugin page's dispatch sentence; the closed form of a
surprise's occasion, in the schema and in the lint join, and the retired
reservation on the reading envelope; the per-run summary in the outstanding
report and on the capture board; the plugin page and the brief chapter.
Out: The scanner fix for absence as a class (iss-2608301808198621, iss-2608301744268001); the disposition vocabulary; the comparative channel and the committed comparative run it produces, which spc-2609020626039834 defines and this gate only reads; the reading-item locator leaf, which spc-2609020626046252 introduces and this spec calls; enforcing that a session ended.
Approach
Landing order
The Iteration 2 set lands in two phases around the maintainer's readings, on the corrections ruling of 2026-09-02. Phase A, before any reading runs: The two-operand invocation (spc-2609021004075744), itd-194 (spc-2609021003136831), the preset windows (spc-2609020626048722), the comparative channel (spc-2609020626039834) and the reading-occasioned origin (spc-2609020626042168), in that order. The maintainer then runs the four readings in the cycle's order, widening, entailment, comparative and detection, and dispositions their outputs. Phase B, built at Step 5 after those dispositions: The condition disposition (spc-2609020626046252), the admission and surprise verbs (spc-2609020626040342), the reframe record (spc-2609020626048705), the scribe verbs (spc-2609020626045177) and the principles read object (spc-2609020626042471), in that order. No spec names a target version number: Each names the class of bump it makes, and the merging change sets each constant from the merged base and updates every pinned count in the same diff.
This spec moves no assembler, bundle or preset version: It touches neither the
include table nor the bundle shape. It lands second in Phase B, after the
condition disposition, whose readingitem leaf its wrappers resolve through,
and after the comparative channel in Phase A, because the gate reads that
channel's probe; it lands before the scribe because the scribe's ingest
writes through the functions it adds. Between Phase A and this spec the
maintainer's widening reading has run and the comparative reading has
characterised its candidates, so the first admissions are hand-authored in
the target format, with the gate this spec builds applied by hand to them,
until the verb lands.
One act, two records, one lock
internal/core/capture/admit.go adds Admit(req AdmitRequest) (AdmitResult, error) with AdmitRequest{RepoRoot, IssuesRoot, Item, Grounds string} and
AdmitResult{Admission, Disposition, Item, Run, Path, DispositionPath string; Redacted int; Degraded string}. It follows Disposition and
promoteReadingItem in internal/core/capture/reading.go and promote.go:
resolveRoots, recordid.ValidReadingItemID, mutationPreamble, a
pre-flight read of the item through findReadingItem, readRecordGuarded and
validateReadingStrict, then everything that decides anything under
withLedgerLock, re-read there rather than trusted from the pre-flight.
findReadingItem and readingItemPaths are, from spc-2609020626046252
onward, thin wrappers over readingitem.Locate and readingitem.Paths in the
internal/core/readingitem leaf, kept in capture until every caller moves;
this spec calls the wrappers and adds no caller of its own to move later.
Under the lock, in order: The item's position must be widening, refused
otherwise by name, because admission is that position's warm act alone.
ItemFate(repoRoot, run, item), the one admitted-proposal probe, which
spc-2609020626039834 adds to capture with its judgement in issueschema, is
asked once: An admission under admissions/<run>/ whose proposal names the
item, keyed on the (run, proposal) pair exactly as admittedProposals in
internal/core/lint/readingoutstanding.go keys it, refuses naming it. The
same probe carries the standing dispositions: None means both records are
written; exactly one in the accepted state means the admission alone, and
on that branch the supplied ground, after grounds.Fold, must equal the
standing disposition's disposition_grounds, refused otherwise naming both
texts, so the one-ground rule below holds whichever verb reaches this branch;
any other state refuses naming the disposition and its state; a contested or
cyclic set refuses as Disposition already does. Then the shared writer's
gate below. Only then are the ids minted, through minter.Mint for
issueschema.DispositionFamily and issueschema.AdmissionFamily, each
checked by refuseExistingRecord, so the mint sees the tree it writes into.
The disposition is written first, the admission second, and a failure on the
second removes the first, on the both-or-neither shape writePair in
internal/core/reading/assemble.go carries. The admission is assembled by
admissionFields as schema_version, id, run, proposal, grounds, in
that order, validated by validateAdmissionStrict against
issueschema.AdmissionRequired and AdmissionKnown, serialised by
buildIssueText, and written to admissions/<run>/adm-N.md after
ensureFamilyDir. recordid gains ValidAdmissionID and ValidSurpriseID
beside the four grammars it holds, so no path is built from an id nothing has
matched.
The shared writer and the ordering gate
The disposition write is the body Disposition already runs under its lock,
factored into writeDispositionLocked so that every disposition in this
binary passes one validator path (dispositionFields,
validateDispositionStrict against the item's position) and one gate.
Disposition calls the same function, and its mint moves inside the lock:
Today Disposition mints its dsp-N before withLedgerLock, and after this
change it mints under the lock as mintUnusedItemID in IngestReading
already does. That is a behaviour change to capture disposition, not only to
admit, and TestDispositionMintsUnderTheLock records it.
The gate is keyed on the item's position alone. At the widening position,
writeDispositionLocked refuses a disposition in any state the position
admits, accepted, declined or held, until a committed comparative run
names the item's run, and names what it is waiting for: A committed
comparative run whose manifest names rdg-N. The probe is
capture.ComparativeRunFor(repoRoot, run), which spc-2609020626039834 adds:
It walks .abcd/development/readings/*/run.json and returns the comparative
run whose candidate_run names the widening run, whether that run
characterised two or more candidates or was committed with an empty item set
because the position was not exercised. Nothing else satisfies the gate, and
no mutable file anywhere records the outcome. Because the gate sits in the
writer, capture disposition, capture admit and spc-2609020626045177's
scribe ingest all hold the ruled order without each carrying a check, and
the assembler's own refusal of a dispositioned candidate becomes unreachable
through any verb: It stays as a guard against a hand-placed record, which the
scanner fix named in the residue is what catches as a class. There is no
deadlock, because the comparative run the gate waits for is exactly the run
the assembler produces over the undispositioned set.
held is available at the widening position, and the ground is stated here
because the readings companion leaves it open: Its section 4.3 sends the
question to section 11, and section 11 does not settle it. The ground is that
a proposed configuration can be real and not yet articulable, with an
epistemic exit, exactly as a tension can: The researcher can see that the
construal admits it without yet being able to say whether the record should,
and held with its exit_condition records that state as the disposition
vocabulary already does for a detection tension. Routing such a candidate
through the selection vocabulary's deferred instead would move it onto the
selection surface before the comparative reading has characterised it, and
the companion's section 8.3 orders the two the other way, characterisation
first and admission or decline with grounds after. So held is the widening
position's third state and is written through the same gate, after the
comparative run and never before. The choice is registered as an
interpretation of a section the document leaves open, not as a reading of it.
The scope condition holds by construction: The gate reads the comparative run the channel commits, and the channel lands in Phase A before this verb, so by the time the gate exists every widening run it is asked about either has a committed comparative run naming it or has none, and the refusal is not a stub but the honest state of a run nobody has characterised.
The grounds
--grounds on admit is free text without a <token>: prefix. The
admission's grounds is free text by spc-67's schema and the disposition's
disposition_grounds is free text by spc-58's, and AdmissionKnown is a
closed set with no room for a token field, so the grammar requireGrounds
applies to the triage routes does not fit here. What is shared is the floor:
The text is redacted through redactLedgerText, folded by grounds.Fold, and
held to grounds.ValidateText, which refuses the empty, whitespace,
control-character, too-short and vocabulary-only texts. The same folded text
lands in both records, and the admission-alone branch requires it to equal the
standing disposition's, so the disposition and the admission cannot state two
reasons for one act by any path. The refusal writes nothing and says so.
The surprise verb
internal/core/capture/surprise.go adds Surprise(req SurpriseRequest) with
SurpriseRequest{RepoRoot, IssuesRoot, OccasionedBy, Text string} and
SurpriseResult{ID, OccasionedBy, Path string; Redacted int; Degraded string}.
The occasion is resolved before anything is minted through
readingitem.ResolveOccasion(repoRoot, id, families) with the families
rdi, adm and dsp, the one occasion resolver spc-2609020626046252
introduces and spc-2609020626048705 also calls; an id outside those families,
or one that resolves to nothing, refuses naming it. The text is redacted
through redactLedgerText, folded by grounds.Fold and held to
grounds.ValidateText, the same floor every grounds-shaped field in this
workstream applies. The record carries schema_version, id and
occasioned_by in its frontmatter and the text as its body, because spc-67
places the surprise itself in the body, and is written to surprises/srp-N.md
under the ledger lock after refuseExistingRecord. No disposition file is
opened for writing on this path, which is what "never a field on a
disposition" means in code.
The occasion is a closed form. The doc comment on issueschema.SurpriseRequired
admits "a consequence named in prose" today, and the srp join in
internal/core/lint/schema.go declares no sameBucketAs, so a prose value
passes the record gate. This spec closes it: occasioned_by is an rdi-N,
adm-N or dsp-N handle and nothing else, issueschema says so, and the
join gains a resolution over that family set on the pattern sameBucketAs
uses for one family, so a hand-written prose occasion is a lint finding. The
reserved occasioned_by on the reading envelope, ReservedSurpriseFields in
internal/core/issueschema/reading.go, is retired in the same change: The
join lives on the surprise record and nowhere else, and the key on a reading
record is refused as any unknown key is.
Dispatch
record.IDRe in internal/core/record/record.go matches iss, itd, spc
and adr today and nothing else, so abcd rdi-N and abcd dsp-N are refused
as unknown commands; spc-67 records that residual. IDRe becomes
^(iss|itd|spc|adr|adm|srp|rfm)-[0-9]+$ in this change, rfm included
because spc-2609020626048705 lands after this spec and inherits the edit
rather than making a second one; its Describe case is that spec's.
Describe gains the adm and srp cases with the family names admission
and surprise, and the status column reads admitted and recorded,
because neither family has a folder to name. An admission's links are run,
proposal, the proposal's path and the standing disposition's id; a
surprise's are occasioned_by and its path. Neither emits a next move.
commands/abcd.md's dispatch section quotes the positional grammar and its
description names the four families; both are edited to the seven. Widening
the dispatcher to rdi, dsp and rdg stays out of scope and is named in
the residue below.
The outstanding report and the board
OutstandingReadings in internal/core/lint/readingoutstanding.go already
answers the intent's ac-7 in two legs: An undispositioned widening item with
no admission lands in Undispositioned, and an item with a standing
disposition other than declined or held and no admission lands in
Unadmitted. What is missing is the count as a query. The walk gains
WideningRuns []WideningRun with Run, Items, Admitted, Declined and
Outstanding []string, one per run holding a widening item, and
checkReadingOutstanding renders one info finding per run. The bare
capture board in internal/surface/cli/cli.go carries the same report
through lint.ReadReadingOutstanding, so abcd capture --json answers the
count with no second implementation.
The front doors
internal/surface/cli/cli.go registers admit <rdi-N> --grounds "<text>"
and surprise --occasioned-by <id> <text> under capture, on the disposition
sub-verb's shape: No cobra-required flag, the core refusing an empty value,
--json inherited, the render sanitised through termsafe.Sanitize, and
redacted reported whenever it is non-zero. commands/capture.md documents
both verbs and the ordering gate and drops its "written by hand" paragraph;
.abcd/development/brief/04-surfaces/06-capture.md gains the two sub-verb
rows; .abcd/development/release/surface.json is regenerated so
surface_coverage holds in both directions.
How the Acceptance Criteria are satisfied
- ac-1: With no disposition and a comparative run committed over the
item's run,
Admitwritesdsp-Ninacceptedandadm-Nunder one lock; the second call finds the admission by (run, proposal) and refuses. Test:TestAdmitWritesBothRecordsAndRefusesTwice. - ac-2: A standing
accepteddisposition takes the admission-only branch, and the disposition file's bytes are unchanged. Test:TestAdmitWritesTheAdmissionAloneOverAStandingAcceptance. - ac-3: With no comparative run naming the item's run,
writeDispositionLockedrefuses, naming the run and the committed comparative run it waits for, whichever verb reached it. Tests:TestAdmitRefusesBeforeTheComparativeRun,TestDispositionRefusesBeforeTheComparativeRun. - ac-4: A standing
declineddisposition refuses naming its id and state;heldrefuses the same way. Test:TestAdmitRefusesAStandingNonAcceptance. - ac-5:
grounds.ValidateTextover the folded text refuses blank, whitespace and degenerate grounds before any mint; the ledger is byte-identical afterwards. Test:TestAdmitRefusesADegenerateGround. - ac-6:
Surpriseresolves the occasion, writessurprises/srp-N.md, and opens no disposition for writing; the test hashes the dispositions tree before and after. Test:TestSurpriseIsItsOwnRecord. - ac-7: The
WideningRunssummary over a run with one admission, one decline, onehelditem and one untouched item names the fourth alone. Test:TestWideningRunSummaryNamesTheOutstandingItem. - ac-8:
IDReandDescribeacceptadm-Nandsrp-Nand report the record and its joins. Tests:TestDescribeAdmission,TestDescribeSurprise.
Tests
Watched fail before, pass after; each refusal proved by a mutation that removes it.
internal/core/capture/admit_test.go:TestAdmitWritesBothRecordsAndRefusesTwice,TestAdmitWritesTheAdmissionAloneOverAStandingAcceptance,TestAdmissionAloneRequiresTheStandingGround,TestAdmitRefusesBeforeTheComparativeRun,TestAdmitProceedsOnAnEmptyComparativeRun,TestAdmitRefusesAStandingNonAcceptance,TestAdmitRefusesANonWideningItem,TestAdmitRefusesADegenerateGround,TestAdmitRefusesAContestedItem,TestAdmitRemovesTheDispositionWhenTheAdmissionWriteFails(through a write hook on the admission write, on thereadingWriteHookidiom),TestAdmitMintsUnderTheLock(on thelockrace_test.goidiom),TestAdmissionAndDispositionCarryOneGround.internal/core/capture/surprise_test.go:TestSurpriseIsItsOwnRecord,TestSurpriseRefusesAnUnresolvedOccasion(an unknown id, a malformed id, an id of a fourth family, and each of the three families resolving),TestSurpriseRefusesADegenerateText,TestSurpriseRedactsItsBody.internal/core/capture/reading_test.go:TestDispositionAndAdmitShareOneWritePath(the factored function is the oneDispositioncalls, proved by the write hook firing on both),TestDispositionRefusesBeforeTheComparativeRun,TestDispositionMintsUnderTheLock,TestTheGateIsKeyedOnTheWideningPositionAlone(an entailment item is dispositioned with no comparative run anywhere).internal/core/issueschema/reading_test.go:TestOccasionedByIsNoLongerReservedOnTheEnvelope.internal/core/lint/schema_test.go:TestSurpriseOccasionMustResolve(a prose value, an unknown handle, and each family resolving).internal/core/recordid/valid_test.go:TestAdmissionAndSurpriseIDGrammars.internal/core/record/record_test.go:TestDescribeAdmission,TestDescribeSurprise,TestIDReAdmitsTheThreeNewFamilies,TestIDReStillRefusesTheReadingFamilies.internal/core/lint/reading_outstanding_test.go:TestWideningRunSummaryNamesTheOutstandingItem,TestWideningRunSummaryStandsDownOnAnUnreadableRun,TestWideningRunSummaryIsInfoNotBlocker.internal/surface/cli/cli_test.go:TestCaptureAdmitRendersAndRedacts,TestCaptureSurpriseRequiresAnOccasion, and the capture board case gainingwidening_runs.- Evals: No cold-reading eval case of this spec's own. Neither verb touches
the assembler or the read block; the admission records already sit in the
eval's
GROUNDSsentinel class, and the surprise plant arrives with spc-2609020626039834's fixture. The smoke lane's command-tree walk covers the two sub-verbs' help and flag parsing with no edit.
Out of scope
- The scanner fix for absence as a class, which is two recorded issues and ships in its own change; the verb closes the gap for the records it writes, and a hand-written blank stays the gate's to catch.
- A fifth disposition state, or any change to which states the widening position admits.
- Dispatching
abcd <id>onrdi-N,dsp-Norrdg-N, which stays the residual spc-67 named, and therfmcase ofDescribe, which is spc-2609020626048705's. - The comparative channel and the committed comparative run its assembly and ingest produce, which the gate reads and never writes.
- The
internal/core/readingitemleaf and the move ofcapture's locators into it, which spc-2609020626046252 owns.
Frontmatter
| id | spc-2609020626040342 |
|---|---|
| type | spec |
| lifecycle | closed |
.abcd/development/specs/closed/spc-2609020626040342-an-admission-and-a-surprise-are-written.md
open on GitHub ↗ · commit history ↗
References1
- itd-2609020625400194An admission and a surprise are written by a verb, and the order the design fixes is a refusalimplements
mentions15
- iss-2608301744268001a trailing comment on a frontmatter key defeats every blank spelling the record_schema gate refuses because the shared same-line scanner strips no comments
- iss-2608301808198621isAbsentValue decides on literal strings rather than the yaml null and empty class so chasing spellings cannot close it
- itd-180A cold reading's findings land as reading records, and the researcher's response is a separate disposition record — two acts, two writes, never collapsed
- itd-194The reading include table admits only what the exclusion floor can read
- spc-58Reading records and disposition records: two acts, two writes
- spc-67Step-2 admission records: schemas now, command enforcement next cycle
- spc-2609020626039834The comparative channel: one widening run's candidates, two fields each, characterised before anyone admits one
- spc-2609020626042168The reading-occasioned origin: promote stamps the run and the item it graduated, and the stamp primitive refuses the kind without them
- spc-2609020626042471The knowledge record becomes a read object: A principle carries typed claims, projects its statement, and inherits only what held
- spc-2609020626045177The scribe verb: an allow-list assembler, an authoring-refusing ingest, and a per-run context stamp the transcript store can check
- spc-2609020626046252A second writer into the disposition surface: `abcd intent condition` dispositions one scope condition from a reading item or a delivered intent
- spc-2609020626048705The reframe record: `capture reframe` writes one `rfm-N` keyed on the committed fingerprints of the three frame surfaces before and after, joined to the reading item, disposition or surprise that occasioned it
- spc-2609020626048722One committed entry per position, in three parts: the object set, the kinds within it and the window it measures at, held by an eval that re-measures every entry on every committed change
- spc-2609021003136831The include table declares what the floor parses, the floor refuses the markdown it cannot resolve, and the manifest marks every item it did not examine
- spc-2609021004075744The invocation is a position and a target state: the scope operand and the override stamp leave, and the committed preset for the position is applied by the assembler