feat: forge discipline, readiness gauging, comprehension probes#1
Merged
Conversation
Author conditioning/modules/council.ncl, the shared station every council seat carries, distilled from the architect-council ARCHITECTURE (0b/2/3/5/5a/5b/6). It mirrors modules/reviewer.ncl's shape: a header comment plus one plain-String literal, composed between core and a per-seat lens delta and guarded verbatim by HasModule at wire time. The module encodes only the class spine: the pact (R/I/U as Nickel contracts that fail on omission, gating gate-advancement and binding residue); the deliberation protocol (independent-deposit-first, then driver-relayed hub-and-spoke correction); and the governance spine (domain sovereignty plus delegation table, the composer-moderator hub checked by the auditor, the auditor's load-bearing independence and head-escalation, the barring lifecycle, Evidence>Authority both ways, and proportional standing convening). Thin overlay above core: it adds the council station only and does not restate the always-on law (the core sentinel is absent). The per-seat lens deltas and compose wiring are separate nodes.
The persistent council's seats accumulate standing across a campaign, so the balance of power between them must be machine-enforced, not prose. Add council.ncl as a first-class peer of dag.ncl: a two-layer intrinsic contract (Constitution + DecisionLedger) where `nickel export` IS the gate. A gapped, unilateral, ungrounded, barred, no-consent, or un-ratified law fails the export with no skippable roundtrip step. Finalize the corrected decision taxonomy: reconcile-accept and head-ratified dag-amendment route to the architect, dispatch/convene/ lifecycle to the composer, merge to the maintainer, bar as a 'human type. Two named must_assent guards make defeater-resistance structural: merge-consent (maintainer) and head-ratification (human) are required regardless of the assent class and checked before the generic predicate, so a later reclassification cannot silently drop a co-signer.
Prove the council contract is non-trivial: a wrong verdict changes an exit code. test_council.sh runs `nickel export` per committed fixture under ledger/fixtures/council/ and asserts both the exit code and that each rejection names its OWN invariant, so no negative control can pass by an incidental shape error. Six controls: a complete constitution with a routine owner-only decision exports clean (proving proportional convening is lawful); gapped, unilateral, merge-without-consent, barred-seat, and dag-amendment- without-head each fail for their named reason. Fixtures carry the house EXPECT polarity annotation so a future recursive sweep classifies them correctly. Standalone-runnable; not yet wired into an aggregate runner.
The head (human) was a standing seat counted in the 'full machine- consensus tally, so a 'close decision was satisfied by folding the head into consensus. nrd ratified two distinct gates instead: the machine council reaches consensus, THEN the head ratifies on top. - mark the head structurally (Seat.head) and exclude it from the 'full tally, which now sums the four machine seats only; - require head ratification on 'close via the existing must_assent mechanism (must_assent = ["human"]), the same guard that makes 'merge require the maintainer and 'dag-amendment require the head. A 'close with full machine consensus but no head now fails specifically on head-ratification, not on a generic consensus shortfall. New fixture close_no_head.ncl proves it; unilateral.ncl is re-pointed to prove the complementary gate (head present, machine consensus absent -> anti-unilateral). good.ncl already carried the head, so it stays clean.
Author the three standing-seat persona deltas (architect, lead- maintainer, process-auditor) as thin Nickel String lenses riding the N2 council-class module. Each states only its own basin and decision-domain — boundary/coherence, the hostile merge-consent gate, and the independent process+residue audit — and references the shared spine rather than restating it, mirroring the hickey-reviewer shape. This decorrelates the single architect basin into role-divergent seats; wiring into compose.ncl and install.sh is deferred to N5.
The council fixtures were Nickel instances that imported and applied council.ncl inline. The house rule is contracts stay Nickel while data instances are YAML, so re-render every fixture as a pure-data YAML instance and validate it externally. Add council_apply.ncl, a single shape-dispatching apply-contract: a value carrying `seats` is validated by Constitution, otherwise by DecisionLedger threaded with the canonical constitution.yaml (the constructor's argument must be supplied in the apply-file, since --apply-contract has no parameter mechanism). One apply-file because the node's file surface names exactly one. council.ncl is the law and stays byte-unchanged. Its Seat.standing and DelegationRule.required_assent are bare enum contracts that reject the plain strings a pure-data YAML instance carries, so the apply-file lifts those known strings to the tags the contracts expect before the law runs. Every fixture value is a valid tag, so the lift is lossless and no invariant is weakened. Rewrite test_council.sh to validate each YAML fixture via `nickel export <fixture>.yaml --apply-contract council_apply.ncl`, preserving all case semantics: the valid ledger and the canonical constitution export clean; gap/unilateral/merge-no-consent/barred/ dag-amend-no-head/close-no-head each still fail for their own named invariant keyword. The gate never re-implements an invariant in bash.
The council had a station (the shared module) and a law contract (council.ncl) but no skill binding a dispatched SEAT to a procedure — the LAW half of workers : worker-skills :: council-seats : skill + persona. Without it, "decision-type X routes to seat Y" had no discipline to route into. Author skills/council/SKILL.md as the operating SOP a seat runs: the deliberation protocol (independent-deposit-first, hub-and-spoke as the degrade-to-primitive fallback), the pact's gate-blocking R/I/U obligations, the governance spine (delegation table, composer checked by an independent auditor, the barring lifecycle), and the act of recording assent and findings. The skill references the station and council.ncl rather than restating them. A seat's deposit and the decision ledger are YAML instances validated by an externally-applied Nickel contract (deposit.ncl, council.ncl's DecisionLedger) — contracts are Nickel, data is YAML. Fidelity and non-restatement confirmed by decorrelated review.
Green deterministic gates are necessary but never sufficient to land a node: the lead maintainer must affirmatively consent to a merge. That floor had no machine check — an orchestrator under execution momentum could merge a 'merge decision the maintainer never assented to, and only a human would catch it. council_consent.sh closes the gap. It reduces to council.ncl via `nickel export <ledger> --apply-contract council_apply.ncl` rather than re-implementing assent-membership in bash, so the contract stays the one source of the law; a non-zero export is only read as a consent failure when it names the `merge-consent:` guard, so an incidental shape error cannot masquerade as a verdict. Wired into the MERGE state as a precondition run before `git merge`, mirroring the adherence_audit and recorder_close_check sibling gates.
The recommendation to prefer `--no-ff` for sequential/distinct-concern landings was agent-fabricated and never specced. Isolation is verified by branch reachability (every non-merge first-parent commit traces to a node/* branch), not by merge shape — adherence_audit.sh already encodes that merge-shape-agnostic law, so the prose recommending a specific strategy contradicted the audit it claimed to serve. This was recorded as a correction but never applied to source. Remove the `--no-ff` clause from the MERGE row (SKILL.md), the guard_note parenthetical (state_machine.ncl), and the three demo invocations (TRANSCRIPT.md), leaving the already-correct any-strategy framing. The descriptive `--no-ff` comment in adherence_audit.sh is the correct framing and is left untouched.
The three landed standing-council seats (architect, lead-maintainer, process-auditor) had a contract, a module, and personas but no path into the generator, so they could not be dispatched. Wire the council class into compose.ncl mirroring the reviewer class: a council import-record plus per-seat fields composed core ++ council_module ++ seat-delta, each guarded by HasCore and HasModule council_module and pulling no producer (a seat reasons about architecture, it does not write code). Register COUNCIL_ROLES in install.sh, fold it into AGENT_ROLES so each seat materializes as a predicate-<seat>.md agent, and give each a routing description. Mirror the F6 lockstep and the seat-assertion block in the test suite, with COUNCIL_ROLES kept byte-identical across both files.
The single output-style slot rendered the architect orchestrator, but the architect now holds a council SEAT (its judgment) — one identity competing in two places (finding F3). nrd ratified the composer as the single output style and the clean rename of the slot token. Author personas/composer.ncl, the conductor/moderator: the live front door that scales ceremony, convenes the council proportionally, sequences deliberation, and integrates decisions — checked by the process-auditor. It pulls no producer (a moderator is not a code-writer), so the slot sheds the action procedure. Repoint the output-style field to it and delete the orphaned architect persona (now the architect-SEAT). Clean-rename the slot token architect->composer everywhere it renders the output style: the compose field, OUTPUT_STYLE_NAME/FILE and the role default in the generator, the bootstrap delivery call, the round-trip tests, and the architecture doc's role table. The conceptual "report to architect" escalation in the doc is left for the narrative pass (N7), which pluralizes single-architect prose.
F11: decorrelated review found the composer's record-linkage wording weak in two places against ARCHITECTURE.md 0b/5b. Integration said only "integrate the result" with no tie to a durable decision, and the auditor's independence was grounded in convene/control limits but not in the actual basis: it reads the durable record directly. Tighten both existing clauses in place so integration produces a recorded decision in the durable decision-ledger and the auditor's independence rests on reading that record rather than a composer-curated summary.
F9: the module's bar-ratification line read "the HEAD (or a quorum) ratifies", contradicting the council.ncl contract it documents. A bar is a 'human decision-type, and the head-ratification guard requires the literal "human" in assent (council.ncl:115,120-127) -- a quorum alone fails. Drop the quorum path so the prose matches the contract; the line-82 'subset gloss legitimately keeps its own quorum wording.
council.ncl's 'human decision-class makes a bar require literal human assent; a quorum alone fails the gate (council.ncl:115). The persona text claimed "the HEAD (or a quorum) ratifies", contradicting the contract and the already-corrected modules/council.ncl. Align the wording to "the HEAD ratifies".
The campaign/orchestration narrative described a single architect who surveyed, judged, drove, and merged. The standing-council model splits those decisions across seats, so route each to its owner per the delegation table (constitution.yaml): boundary/plan/reconcile-accept -> architect seat, drive/dispatch/lifecycle -> composer, merge -> the lead-maintainer's affirmative consent, and the human-seam poles -> the HEAD seat. Narrate the accept-not-equal-merge split and rename ARCHITECT_AS_JUDGE to COUNCIL_AS_JUDGE; the re-run-evaluators and worker-self-cert-void rules keep their meaning. Capability terms (architect-tier/-class) and the mechanical executor -- pseudocode steps, exit-code routing, gate commands, frozen state tables -- and rules.md citations are left unchanged.
The conditioning output style installs as predicate-composer.md (the composer is the output-style persona; see install.sh), but the docs still pointed at the old predicate-architect.md and called it "the architect law". Update the stale filename and law-name references in the craft guide, getting-started, and conditioning-layer docs. The distinct predicate-architecture.md document is left untouched.
The DAG tracker had dag_apply.ncl, paving "author the instance as pure-data YAML, gate via nickel export --apply-contract". The findings, context-map, and reconcile-log trackers had only their law contracts and .ncl test fixtures — no apply-file — so inline-Nickel was their only paved instance path, the asymmetry that drove trackers to be written in Nickel instead of data. Each apply-file imports its law contract (unchanged) and exposes a custom contract that lifts the bare-enum fields a pure-data YAML carries as strings to the tags the law expects, then delegates: findings adapts severity/status, context_map adapts kind, reconcile adapts verdict/state. The lift is lossless and the intrinsic gate stays intact (a string status would otherwise make the resolved-requires-evaluator check silently inert).
The artifact table cited the bare law contracts and only the DAG row gave the "authored as YAML, validated via --apply-contract" treatment, so the findings and reconcile rows still implied a Nickel-authored instance and there was no row for the context map at all. Give the findings and reconcile rows the DAG row's treatment, add the missing context-map row, and point every Contract cell at the apply-file that gates it. Reword the lead so the instances-are-YAML model covers all four trackers, not just the DAG.
test_council.sh proves council.ncl's invariants in isolation. It does not prove the law, the merge-consent gate, and the YAML decision-ledger instances COMPOSE — that the consent gate and the law agree over the same instances and each catches the failure class it owns. This smoke composes the landed primitives over the existing fixtures: good.yaml is the joint PASS witness (consent gate rc 0 AND law rc 0); merge_no_consent.yaml is caught by the consent gate (merge-consent); close_no_head.yaml is routed to the law directly (head-ratification), since the consent gate returns rc 2 for any non-merge-consent failure. Every verdict reduces to council_consent.sh or the apply-contract — no invariant is re-implemented in bash.
The reviewer-class campaign built the standing council but shipped no discoverability reference, so a zero-context maintainer had no durable entry point to understand the roster, the law, or how to convene it. Add docs/council.md (Divio reference): the seat roster and lifecycles, the two-layer council.ncl law (decision taxonomy, assent classes, the merge-consent and head-ratification guards, the intrinsic gates), the deposit-and-decision flow, and a minimal convene path via /council. Every claim links to a committed authority (council.ncl, the canonical constitution.yaml, the consent gate, the /council skill). The doc does not link the design-notes ARCHITECTURE.md: it lives only in ephemeral .scratch, so a permanent doc must ground in durable sources instead.
The architect→composer rename landed in conditioning/ARCHITECTURE.md but the sibling conditioning-layer.md was missed except one line, leaving stale references to the deleted personas/architect.ncl. Mirror the ARCHITECTURE.md target forms onto the persona table, compose.ncl block, --field call, and the subagent-injection note so both documents agree on the composer role.
Post-council, the reconcile-time judge that can overrule a grounded reviewer finding is the architect-seat specifically, not the architect role at large. Name the seat so the veto authority is unambiguous.
The composer CONDUCTS the campaign: it convenes the seats, sequences deliberation, and integrates the vote. If it also casts a required vote in the 'full machine-consensus for a close, it self-certifies the very work it orchestrated — the un-decorrelation the council exists to prevent. Add Seat.conductor (Bool, default false), mirroring how head is already excluded, and filter machine_ids on `standing == 'standing && !s.head && !s.conductor`. The 'full tally is now exactly the independent judging seats (architect, maintainer, auditor); the composer stays a declared member that conducts the vote and the head ratifies atop it — both members, neither counted. Mark the composer seat conductor: true; drop composer from good.yaml's close assent (no longer required) and add close_missing_seat.yaml proving a close short one judging seat still fails anti-unilateral — the exclusion narrows who votes, it does not lower the quorum bar.
Resolve the law-vs-prose contradiction (F15): the constitution declares the composer a standing seat, yet the prose called it "NOT a seat". The law now excludes the conductor from the 'full tally, so the precise statement is that the composer is a council MEMBER that CONDUCTS the consensus but does not vote in it — it cannot self-certify the close it orchestrated. Tighten the persona header and body and the module's delegation and moderator notes from "the whole council" / "NOT a seat" to "the hub/conductor, not a consensus-voter". Both files stay thin.
Predicate's council law lived only in a test fixture; the production merge-consent gate validated real decision-ledgers against test scaffolding (council_apply imported the fixture). Add the constitution as a first-class CONDITIONING value (conditioning/constitution.ncl, a Nickel record conforming to council.ncl's Constitution with native enum tags) and repoint the production apply at it, so consent reduces to predicate's own law. Split off council_fixture_apply.ncl (byte-identical but threading the fixture) and point the two unit-test gates at it, so the law's tests stay self-contained. Lands atomically: a partial split would red a gate.
The composer CONDUCTS the council but carried no data-derived view of the law it routes. Fold a constitution->prose render (the seats plus the decision_type->owner->required_assent delegation table) into the composer's system prompt as a compose segment, generated from conditioning/constitution.ncl so editing the law re-renders the prompt. Pin it with a test_conditioning assertion on the rendered table, and fix the council SKILL to point at the conditioning constitution rather than the false "declared in AGENTS.md".
Two documents described the conditioning composition layer: docs/conditioning-layer.md (human-facing, linked from the architecture entry point) and conditioning/ARCHITECTURE.md (normative contract, cited from code comments). Both had lagged the generator -- conditioning-layer.md by two campaigns -- and they disagreed with each other: the live compose.ncl is a three-segment law (core ++ modules ++ delta) over 19 roles in three classes with a rendered constitution, while the docs still described a two-segment, seven-role world. Consolidate into docs/conditioning-layer.md, authored against the live generator as ground truth: the three-segment law, the producer/reviewer/council module classes, all 19 roles, the single-sourced constitution render, and the real delivery surfaces (output style + 18 agents + GEMINI.md). Delete the redundant ARCHITECTURE.md, re-point its four code citations to the surviving doc, and drop its vestigial campaign scaffolding (per-node slot table, downstream-node checklist, reserved halts). The generator itself was already correct; this is a docs-only reconciliation. Verified: all 19 roles export, doc-audit link gate passes, conditioning sweep 5/5.
The prior commit removed conditioning/ARCHITECTURE.md but, due to a staging error, did not include the artifacts the consolidation depends on: the rewritten docs/conditioning-layer.md (authored against the live generator) and the four code-comment citations re-pointed away from the deleted file. Without this commit the deletion stands alone and the surviving doc is stale. This lands them: docs/conditioning-layer.md now documents the three-segment law, the producer/reviewer/council modules, all 19 roles, the constitution render, and the real delivery surfaces; compose.ncl, core.ncl, craft-guide.md, and install.sh point at the surviving doc instead of ARCHITECTURE.md. Verified: all 19 roles export, doc-audit link gate passes, conditioning sweep 5/5.
The forge is the public half of the durable record: branch-to-PR mapping (stacked PRs included), self-contained PR prose with zero process-internal references and SHA-pinned permalinks, review findings and triage recorded on the PR, merge as a consent act through the lead-maintainer seat, and the forge audit checklist run at merge-consent and campaign CLOSE. Applicability is conditional by design: no forge, no obligation.
Campaign (drafting-time): the Readiness Is Measurable philosophy - four pre-dispatch numbers (evaluator coverage, probe scores, seam-type completeness, red-test/golden-vector inventories) reported with the routing table and approved with it; layer 0 manufactures the campaign's own evaluators before any implementation node; the anti-over-preparation rule (prepare evaluators and seams, never solutions); READINESS_GATE and FORGE_WHEN_PRESENT prime directives. Boundary: the comprehension probe - a zero-context cheap-tier dry run that simulates the IBC's consumer (plan plus unanswerable questions; canary traps for known doctrine temptations) as the empirical validator of S1-S7. Orchestration (run-time): probe step for JIT-authored IBCs at the layer boundary; the forge-wiring table binding forge acts to driver states (draft PR at DERIVE, accuracy plus review-on-record through MERGE/CHECKPOINT, audit at CLOSE); FORGE input parameter. Commit-hygiene: merge type cross-references the forge discipline.
Lead-maintainer: merge-consent includes the forge audit - the PR is the public half of the record the seat maintains; accurate prose, zero process-internal references, review-on-record. Architect: owns the readiness numbers at dispatch; unmeasured readiness is a boundary that does not bound. Boundary-worker: probe before present - the comprehension probe simulates the IBC's consumer before Approve. Conditioning e2e suite: 126/126 pass.
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
Writes back into predicate's doctrine two disciplines refined in the field across recent campaigns: measurable pre-dispatch readiness and forge discipline — plus the role-lens language carrying both into the council seats.
What's here
skills/forge/— a conditional discipline (no forge → no obligation; forge → full obligation): branch↔PR mapping including stacked PRs, self-contained PR prose (zero process-internal references, SHA-pinned permalinks), review findings + triage recorded on the PR, merge as a consent act, and a five-point forge audit checklist owned by the lead-maintainer seat at merge-consent and campaign CLOSE.READINESS_GATEandFORGE_WHEN_PRESENTprime directives.FORGEinput parameter.Design decisions
Out of scope
conditioning/core.ncl) editing — deliberately untouched: it is shared with a live campaign, and the identified tightening candidates are proposed separately for the head's ratification rather than landed here.Verification
conditioning/test_conditioning.sh: 126/126 pass with the persona deltas.