Part V · Contracts · Plate 08
Concern lifecycle · ledger
6. Soma Concern Lifecycle, Fire Ledger and Tolerance (S7, 2026-10-31)
Standalone: Soma alone gains concern history, specificity (tolerance) and author ≠ certifier with no other organ present. Composition: the census reads the S4 verdict log (5.5) when it exists; cns curate runs recall and tolerance.
6.1 Concern front matter (writer: the concern author and soma concerns init/promote; reader: every soma concerns command)
born: '<commit sha>'(quoted; 7 to 64 lowercase hex, ornull): written bysoma concerns initas theHEADit ran at;nullwhen the repository has no commit.state: shadow | active | quarantined:initwritesshadow.soma concerns promote <id>setsactiveonly after the concern's tolerance check (6.3) passes on at least one entry naming it (no entry or notolerance.yaml: refused, exit 3; above tolerance: refused, exit 1). Quarantine is a person's edit; Soma only proposes it (6.4). A concern without astate:key predates S7 and is grandfatheredactive. An unrecognised value failsconcerns validateand is treated asactivebyconcerns check(fail closed: a typo never switches protection off).tolerance: <rate in [0, 1]>: the highest fire rate on the tolerance set this concern may show; default0.
6.2 soma concerns check: author is not certifier
A concern gates only in the form certified at the merge base of the evaluated changeset, and only when that form is active. The base's concern files are read from the base commit itself, through the base's own soma.toml docs_dir, so a changeset that moves the concerns directory cannot hide them.
- A concern file that is byte-identical at the base gates when it is
active;shadowandquarantinedconcerns are evaluated and reported but never gate. - A concern file added or modified in the changeset (absent at the base, or any byte differs) is
shadowfor that run: evaluated, reported ([shadow: added in this changeset; does not gate]), never in the exit code and never sent to the System 1 rubric. - When the changeset modifies or deletes a concern that was
activeat the base, the base version is evaluated against the changeset and gates in its place (base versions gating: <ids>). So editing, quarantining or deleting a concern takes effect only once that change has itself landed.
Markdown adds, after fired: <n>, not gating: <id> (<state>), ... or not gating: none; JSON adds per fired entry state, stateReason, version (current | base), gating, and a top-level lifecycle object. fired: <n> counts distinct fired concern ids, gating or not; the rubric is vacuous when no gating concern fired.
6.3 Tolerance set (writer: the consumer; reader: soma concerns tolerance, promote, cns curate)
<docs_dir>/concerns/tolerance.yaml, the "self" counterpart of recall.yaml: commits known to be good (they passed the consumer's sealed tests), each naming concerns that must stay silent on it.
schemaVersion: 1
entries:
- commit: '<sha>' # quoted; a root commit cannot be evaluated (no parent) and is refused
expect_silent: [concern-a, concern-b]
note: why it is known to be good
Unknown keys are refused by name (exit 2); a named concern that is not a valid concern is refused (exit 2). An optional structural delta sits at <docs_dir>/concerns/tolerance/<commit>.delta.json, as for recall. soma concerns tolerance [--only <id>] evaluates every entry against its first parent and fails (exit 1) when a concern fires on more of the commits naming it than its tolerance: rate allows (strictly greater); a concern no entry names is not evaluated (with --only: exit 3). No tolerance.yaml: tolerance: not evaluated (no <path>), exit 3. soma concerns recall without a recall.yaml likewise prints recall: not evaluated (no recall.yaml) and exits 3 (it was exit 2 before S7).
6.4 Concern ledger (writer: soma concerns census; readers: soma concerns ledger [--verify], S9)
<generated_dir>/concern-ledger.jsonl (default .soma/concern-ledger.jsonl, git-excluded). Each soma concerns census --last <n> [--ref <rev>] appends one row per valid concern, schema soma.concern-ledger.v1:
{schema, ts, concern_id, rev_range: {ref, head, last, commits, hash}, concern_sha256, state, severity, fired, fired_expected, fired_unexpected, never_fired, rubric_pass, rubric_fail, not_evaluated, overrides, fires: [{sha, delta_sha, expected, disposition, outcome}], inputs: {recall_sha256, verdicts: {lines, sha256} | null}, prev, row_sha256}.
rev_range.hashissha256:of the evaluated commit ids (non-merge, with a parent), newline-joined ingit rev-listorder; two censuses of the same range give the same hash.fired_expected: fires on a commit thatrecall.yamllists asintroducingwith this concern inexpect_fire;fired_unexpected = fired − fired_expected;never_fired = (fired == 0).delta_shais sha256 of the commit's unified diff against its first parent, the same function and the samegit diffinvocation as 5.5'sdelta_sha. A fire'sdispositionjoins the verdict log on(concern_id, delta_sha):rubric_failif any logged question failed (asking again does not erase a fail),rubric_passif at least one passed and none failed, elsenot_evaluated. Without a verdict log every fire isnot_evaluated. A verdict log line that is not a JSON object with stringconcern_id, stringdelta_shaand booleanpassmakes the census refuse (exit 2).outcome(Engram S6 guard vocabulary):fired-and-overriddenwhen the fire was red underconcerns check --check(anactivecritical/warning concern, or anactiveconcern whose rubric failed) and the commit landed anyway;fired-and-passedwhen the rubric passed it and nothing made it red;firedotherwise.overridescountsfired-and-overridden. Severity and state are the concern's current ones, not those at the time of the commit.- Integrity:
row_sha256is sha256 of the row's RFC 8785 (JCS) form withoutrow_sha256;previs the previous row'srow_sha256(genesisfor the first).censusrefuses (exit 2) to append to a ledger that does not verify.soma concerns ledger --verifychecks the chain and re-derives every row whose inputs are unchanged (the head and its rev-list, the concern file's sha256,recall.yaml's sha256, the firstlineslines of the verdict log), comparing every derived field; a mismatch or chain break is exit 1 naming the row and field. Rows whose inputs changed are reportedchain-only. Not detected: removal of the newest rows, and deletion of the whole ledger (ledger: absent, exit 0). - Quarantine proposals:
censusprintsPROPOSE quarantine <id>: fired N times, 0 rubric_fail, M overrideswithrevs: <shas>only when a verdict log exists, the concern fired at least once, every fire has a verdict and none failed. Without a verdict log it printsquarantine proposals: not evaluated (no rubric verdict stream; fire rate alone never proposes a quarantine); with fires lacking a verdict,no proposal for <id>: K of N fire(s) have no rubric verdict. A proposal never edits a concern.
6.5 cns curate (reader of 6.3 and of recall.yaml)
In a Soma-initialised repository cns curate runs soma concerns recall and then soma concerns tolerance, after soma verify and before the Engram layers. Both always run. Exit 0 is a pass. Exit 3 (no recall.yaml or no tolerance.yaml) is printed as Soma: concerns <recall|tolerance> not evaluated (exit 3) ... and does not fail curate. Any other exit fails curate with ERROR: 'soma concerns <recall|tolerance>' failed (exit <n>): 1 (a recall miss, or a concern above its tolerance), 2 (a refusal, such as a malformed set) or a missing binary. This needs a soma with S7; an older soma has no concerns tolerance (exit 2), so curate fails and names it.