Skip to content

ops — top-level modules

ops.apply

The execute/rollback primitive: write a knob value into the machine-owned overlay (§14).

execute and rollback in the self-mod loop both come down to mutating ONE file — config/levers.toml, a gitignored overlay the loop owns end to end. This module is that mutation, kept deliberately small and free of any policy: it writes the value it is handed. All the judgment (bounds, approval, validation) lives upstream; by the time a value reaches here it has already cleared the gate.

Two properties matter for §14's "reversible": * The loop NEVER edits the owner's hand-authored config/local.toml. It writes only levers.toml, and the loader overlays local.toml on top of it, so a human override always wins (config/loader.py). The writable surface is one file, fully auditable. * Every write returns the EXACT prior overlay state for that key (a value, or "absent"). Rollback replays that prior state — restoring a previous value, or removing the key if the loop had introduced it — so an executed change reverses with no residue.

The on-disk format is a strict subset (sections of scalar key = value) that this module both reads (via tomllib) and re-emits deterministically, so the file round-trips and diffs cleanly.

read_overlay(path=LEVERS_OVERLAY)

Parse the overlay into {section: {key: value}}. Missing file → empty (no knobs tuned).

write_overlay(data, path=LEVERS_OVERLAY)

Emit data deterministically (sorted sections + keys) and replace the file atomically.

Values are written untyped-by-section here because callers always pass already-coerced numbers; the kind only affects formatting, handled in overlay_set/overlay_restore which pre-format. Here we render whatever scalar is present.

overlay_get(lever, path=LEVERS_OVERLAY)

The value this lever currently has IN THE OVERLAY (what the loop last set), or None if the loop has never set it (the knob is at its committed default / a human override).

overlay_set(lever, value, path=LEVERS_OVERLAY)

Set the lever's overlay value; return the PRIOR overlay value (None if it was absent), so the caller can record exactly what to restore on rollback.

overlay_restore(lever, prior, path=LEVERS_OVERLAY)

Reverse an overlay_set: restore the prior value, or remove the key entirely if the loop had introduced it (prior was None). Leaves an empty section pruned so the file stays clean.

ops.ci_witness

The CI witness — attest GitLab pipeline verdicts into the chain (owner rule 2026-07-11).

A green pipeline is a claim by an external system; the witness turns it into an ATTESTED fact: it fetches the pipeline for a commit from the GitLab API and emits ci_witness / pipeline_green|pipeline_red with the commit sha as input and pipeline:<id> as output — chained to the code-sensor's ingest of the same sha, so "this commit's tests passed remotely" is signed history, not a memory of a web page.

Zone note, stated honestly: this runs UNSEALED at the ops tier and talks to gitlab.com (stdlib urllib) — the restic/terraform precedent (ops tools reach services from their own process; the sealed core never does). It is invoked standalone or as a subprocess of palace deploy (whose own process IS sealed; the witness child is not).

Auth: pipeline metadata on this project is public (no token). Playing the manual semantic-release job needs a token — read from Keychain (security find-generic-password -a mind-palace -s gitlab-api -w), never from config or argv. Absent a token, release degrades to printing the pipeline URL for a by-hand play.

PROJECT = 'ascalva-projects/mind-palace' module-attribute

API = 'https://gitlab.com/api/v4/projects/' + urllib.parse.quote(PROJECT, safe='') module-attribute

pipeline_for(sha)

Newest pipeline for the commit, or None if GitLab has none for it.

verdict(pipe)

'green' | 'red' | 'pending' | 'absent' — the witness never guesses.

attest_verdict(sha, pipe, v)

check(sha, *, wait_s=600.0)

Poll to a terminal verdict, attest it, rc 0 only on green.

rotation_expiry(days=30)

rotate()

Self-rotate the Keychain token (owner rule 2026-07-11): the old token is revoked server-side the moment rotation succeeds, so the ordering is fail-safe by design — VERIFY the new token works, THEN overwrite Keychain, THEN read back and compare, THEN attest the event (token id + expiry; never the secret). Any failure after rotation prints loud instructions to re-mint — the fail state is a dead token and a five-minute manual re-mint, never a silently broken credential.

Residual exposure, stated honestly: security add-generic-password -w places the secret on argv for the write (same exposure as the owner's one-time setup command, single-user Mac). Keychain has no stdin write path for -w; accepted and recorded.

release(sha)

Play the manual semantic-release job for the sha's pipeline (token required).

ops.code_sensor

The code sensor — a model-less pipeline agent over the repo instrument.

Proves the agent platform's "code acts" half with zero inference: an agent is its declared tools, wired at build time, every act attested. This one holds exactly three handles — the git instrument (read-only), the snapshot ledger (its ONE designated store), and the attestor — and nothing else: no model, no embedder, no corpus store, no network. It is the same species as the vault watcher (core/ingest/sync.py), the deterministic-ingest agent pattern, and deliberately NOT a factory-minted role: PRE_DECLARED_MAX holds no git or store handle (§10), so instrument agents are wired by build_*, never minted.

Sensor framing (docs/brainstorms/code-as-sensor-stream.md): the repo is an instrument, the commit stream is sensed data, ops/code_snapshot.py is the interpreter φ_code, the ledger is the normalized store — event-log-only, outside the knowledge corpus. sync() is the watcher's rescan semantics: ledger-vs-history reconciliation, oldest first, idempotent — a missed post-commit hook heals on the next invocation, and a no-op sync is free.

CodeSyncReport dataclass

ingested = 0 class-attribute instance-attribute

ledger_total = 0 class-attribute instance-attribute

shas = field(default_factory=list) class-attribute instance-attribute

CodeSensor dataclass

Tools are the wiring; the agent is the discipline over them.

repo instance-attribute

db instance-attribute

attestor = None class-attribute instance-attribute

branch = 'main' class-attribute instance-attribute

sync()

Reconcile the ledger against the ingestion branch; snapshot + attest what's missing.

build_code_sensor(config=None)

Wire the agent's three tools against the real repo, ledger, and attestation chain.

ops.code_snapshot

Per-commit structural snapshot of the code — the build tracking its own shape.

Git already stores the text of every commit; what it does not give the evolution study is the structure as queryable data. This module walks a commit's tracked *.py blobs (the COMMITTED tree via git show, never the working directory), parses each with ast, and records the skeleton — symbols (functions/classes with signatures), imports, LOC, per-file blob hashes — keyed by commit SHA. Diffing two SHAs answers "what changed structurally," which git can only answer textually.

A small dedicated SQLite (data/code_snapshots.sqlite), the same pattern as the run ledger — and like the run ledger it is BUILD history, not corpus: palace reset guards it. Idempotent by SHA (re-snapshot is a no-op), so a post-commit hook and a history backfill share one path.

Deliberately NOT here: ingesting snapshots into the knowledge corpus. Source code as a corpus source class is an open design question (finding-0021 — code is builder-produced reality, not owner belief; its provenance label is unsettled). This ledger stays on the ops side until that is ratified.

Symbol dataclass

kind instance-attribute

qualname instance-attribute

lineno instance-attribute

signature instance-attribute

FileShape dataclass

path instance-attribute

blob_sha instance-attribute

loc = 0 class-attribute instance-attribute

symbols = field(default_factory=list) class-attribute instance-attribute

imports = field(default_factory=set) class-attribute instance-attribute

parse_error = False class-attribute instance-attribute

functions property

classes property

parse_header(subject)

(ctype, scope) from a conventional-commit subject; ('', '') when non-conforming.

parse_source(path, blob_sha, source)

snapshot_commit(db, repo, rev='HEAD', *, _cache=None)

Snapshot one commit's tree. Idempotent: returns None if the SHA is already recorded. _cache (blob_sha -> parsed shape) lets a backfill parse each unique blob once ever.

backfill(db, repo)

Snapshot every commit on the current branch, oldest first. Idempotent.

annotate_headers(db, repo)

Fill subject/ctype/scope on rows snapshotted before the header columns existed — ONE git log for all of history, self-healing on every sync (rescan discipline).

open_snapshot_db(path)

ops.effect_catalog

The effector catalog + the SKILL-mining pipeline as data (Track G, item G4).

The catalog is the SINGLE source of truth for the hands the effector layer can express. Each entry is the recorded outcome of the §8 security audit run as a repeatable process (the SKILL-mining pipeline, docs/design-notes/skill-mining-pipeline.md):

read the source as untrusted → re-implement NATIVELY (never import third-party skill code)
  → classify reversibility (this sets w(β)) → mint SCOPE, not a credential
  → assign the §11 sandbox exec profile → attest proposal + approval → catalog + property-test.

Adding a hand is therefore a reviewed diff to _CATALOG below — the lever-registry move applied to the effector layer: the gate (ops/effect_gate) consumes this registry but does not own it, so a proposal can only ever target a hand that has been through the audit (get_actuator raises on anything else, exactly like get_lever).

Why the acting classes are here yet still safe. draft_reply / calendar_hold / stage_file (REVERSIBLE, β small) and send_email (IRREVERSIBLE, β = ∞) are cataloged so G5/G6 can reference them — but a cataloged hand is not a reachable hand. Three independent structural facts keep them off:

  • the wired sensing surface admits effects at ceiling ε = SENSING (core.sensing.SensingHandoffEffectView.admit(..., ceiling=SENSING)), so a non-sensing effect raises before it reaches any handoff (the §4 filtration as a type);
  • an Effect of a non-sensing class is unconstructable without an approval reference covering w(β) (ops.effects.Effect.__post_init__); and
  • the gate denies unless a matching, unexpired, per-action scoped capability was minted (ops.effect_gate.effect_gate_admits) — no ambient authority exists to fire one.

So cataloging a hand records that it passed the audit; enabling it is a separate, deliberate act (raising ε past its class once its property tests are green — §4).

CATALOG = {(e.spec.name): e for e in _CATALOG} module-attribute

ACTUATORS = {name: (e.spec) for name, e in (CATALOG.items())} module-attribute

ActuatorSpec dataclass

One registered hand — exactly what the GATE needs to decide about it: its name, its reversibility class (which sets w(β)), the capability scope it requires, and the CLOSED set of param keys it accepts. max_param_chars caps one param value (per-actuator, so a draft body is not forced through the sensing-sized 256-char hole). The richer audit metadata lives on CatalogEntry, which wraps this — the gate does not need to know a hand's sandbox profile to decide, only its class and scope.

name instance-attribute

reversibility instance-attribute

scope instance-attribute

param_keys instance-attribute

description = '' class-attribute instance-attribute

max_param_chars = 256 class-attribute instance-attribute

CatalogEntry dataclass

The audited catalog record for one hand: its gate-facing spec plus the §8 audit evidence.

sandbox_profile names the §11 powerless exec profile the hand runs under (a label the pipeline doc defines — the type carries the reference, not the enforcement). source records where the capability was mined from (an ecosystem SKILL.md, or "native"), treated as untrusted and re-implemented. audited is True only when every step of the §8 checklist has been walked — a required field, so a half-audited hand cannot be added without saying so.

spec instance-attribute

sandbox_profile instance-attribute

source = 'native' class-attribute instance-attribute

audited = False class-attribute instance-attribute

notes = '' class-attribute instance-attribute

get_actuator(name)

Look up a cataloged hand's gate-facing spec. Raises (fail-closed) if name is not in the catalog — a proposal can only ever target a hand that passed the §8 audit.

get_entry(name)

Look up a cataloged hand's full audit record. Fail-closed on an uncataloged name.

actuators_for(reversibility)

Every cataloged hand of a given reversibility class (the §4 filtration slice) — the reviewed contents of Effects at exactly this blast-radius band, for the catalog + rollout gate.

ops.effect_exec

Execute an irreversible / external effect under a just-in-time credential (Track G, item G6).

The irreversible class (send, pay, post, actuate) is the hardest gate: no undo. §8.4 answers the confused deputy for it — mint scope, not credential, at the moment of action, never ambient. So this executor is the one place a live credential briefly exists, and it exists only for the span of a single approved effect:

  1. Refuse before minting. The Effect is IRREVERSIBLE and FULL_GATE-approved (the type already guarantees it cannot exist otherwise); the executor re-asserts that, plus the recorded proposed and attested facts. If any is false, it raises — no credential is minted for a doomed effect (a persuaded reasoner cannot even cause a mint).
  2. Mint JIT. Only then does it ask the Vault backend for a short-TTL token scoped to exactly this actuator's policy (secrets.mint_token(scope, ttl)). The freshly minted capability is what satisfies the gate's scoped_cap_valid conjunct — there is no ambient authority to reuse.
  3. Gate, then act. effect_gate_admits is checked over the recorded facts + the fresh capability (defense in depth; True by construction here). Only on admit does the injected transport perform the effect, holding the token transiently.
  4. Attest, then discard. An attested action record is appended (the token accessor, never the token — the Step-5 Vault↔attestation join), and the token leaves scope. Nothing retains it.

The transport is injected (EffectTransport): the real send/pay transports live in Zone B (edge) and touch the network; this module orchestrates mint→gate→attest and never imports them, so it holds no network dependency itself (a FakeEffectTransport proves the wiring, exactly as FakeVault proves the credential path without a live Vault).

EffectTransport

Bases: Protocol

The minimal outward-effect surface. perform receives the JIT token for THIS action and returns a receipt/id (a string the attestation hashes). Real implementations live in Zone B and touch the network; they never see anything but the token minted for this single effect.

perform(actuator, params, *, token)

EffectDenied

Bases: RuntimeError

The effect was refused before/at the gate — no credential minted, nothing performed.

ExecRecord dataclass

The record of one performed irreversible effect. Carries the token ACCESSOR (a non-secret audit handle) and the attestation id — deliberately NO token field, so the credential cannot be logged or persisted through this record (the MintedToken discipline, held one step harder).

actuator instance-attribute

accessor instance-attribute

attestation_id instance-attribute

receipt instance-attribute

IrreversibleExecutor dataclass

Performs approved irreversible effects under a per-action JIT credential. Holds the mint authority (secrets), the attestor, and the transport — never a credential between calls.

secrets instance-attribute

attestor instance-attribute

transport instance-attribute

credential_ttl = '60s' class-attribute instance-attribute

agent_role = 'effector' class-attribute instance-attribute

execute(effect, params, *, proposed, attested)

Perform one approved irreversible effect. proposed / attested are the recorded facts (from the EffectLedger / attestation store) the gate conjoins. Raises EffectDenied (and mints nothing) unless the effect is a FULL_GATE-approved irreversible one with those facts true; the JIT credential is minted only after that check and never retained.

build_irreversible_executor(config=None, *, transport)

Wire the executor against the real Vault backend + attestor. REFUSES unless [secrets] is enabled (there is no mint authority otherwise) — the acting classes are owner-activated, and the irreversible one additionally requires a live credential backend. The transport is supplied by the caller (Zone B): this module never imports a network transport.

ops.effect_gate

The Phase-10 gate generalized from config knobs to world effects (Track G, item G2).

The self-mod gate is already a guarded transition system (I12): s′ = Δ·s iff G_now(Δ,s), else s; the model emits Δ, code applies it, rejection is identity. Hands generalize Δ from a ProposedChange (a bounded numeric knob) to a ProposedEffect (a world effect) — the SAME machine, wider domain, two new conjuncts:

G_effect(E, world) = proposed(E) ∧ approved_{w(β(E))}(E) ∧ scoped_cap_valid(E) ∧ attested(E)
  • the approval conjunct is blast-radius-weighted (§4): the strength a class demands is w(β(E)) (ops.effects.required_approval) — sensing needs none, reversible needs light, irreversible needs the full gate; and
  • the scoped-capability check is a first-class conjunct: no matching, unexpired, minted scope ⇒ no effect, regardless of approval (the confused-deputy answer — even a persuaded reasoner cannot fire an effect no capability was minted for).

Because it is the same machine it inherits the FSM-verification discipline: the decision space is small enough to enumerate in full (3 classes × 2 proposed × 3 approval strengths × 2 capability × 2 attested = 72 states), exactly like the 8-state config gate. Nothing new to trust; one machine, wider inputs.

ProposedEffect inherits ProposedChange's structural ceiling: it is an (actuator-name, allowlisted-string-params) pair and NOTHING more — no field anywhere can hold a file path, a diff, a command, code, or a URL, so "run this" / "fetch that address" is not a proposal the layer can express. Actuators resolve fail-closed against the registry below (sensing-only until G4's catalog process lands; widening it is a visible, reviewable diff — never a guess).

Scope note (stated, not silent): the durable per-effect ledger — the EffectLedger analogue of ops/ledger.ProposalLedger, with execute/validate/rollback rows — lands with the first class above β=0 (G5), where there is something to roll back. For β=0 sensing there is no world state to restore; the guard, the types, and the attestation trail are the whole machine.

ACTUATORS = {name: (e.spec) for name, e in (CATALOG.items())} module-attribute

ActuatorSpec dataclass

One registered hand — exactly what the GATE needs to decide about it: its name, its reversibility class (which sets w(β)), the capability scope it requires, and the CLOSED set of param keys it accepts. max_param_chars caps one param value (per-actuator, so a draft body is not forced through the sensing-sized 256-char hole). The richer audit metadata lives on CatalogEntry, which wraps this — the gate does not need to know a hand's sandbox profile to decide, only its class and scope.

name instance-attribute

reversibility instance-attribute

scope instance-attribute

param_keys instance-attribute

description = '' class-attribute instance-attribute

max_param_chars = 256 class-attribute instance-attribute

ProposedEffect dataclass

A proposed world effect — the ONLY shape an effect proposal can take (the Δ of §6).

It is an actuator name plus allowlisted string params and nothing more. Deliberately there is no path, diff, command, code, script, or url field: the layer physically cannot propose "edit this file" / "run this" / "fetch this address", the same way ProposedChange cannot carry a code change and ResearchCriteria cannot carry a note. Params are stored as a sorted tuple of (key, value) pairs so the object stays frozen and hashable; resolve() is the fail-closed door to a dict.

actuator instance-attribute

params = () class-attribute instance-attribute

rationale = '' class-attribute instance-attribute

resolve()

Resolve to (spec, validated-params). Raises on an unknown actuator, a param key outside the spec's closed allowlist, a non-string value, or an oversized value — all fail-closed, so an invalid proposal never reaches a decision as proposed.

EffectGateDecision dataclass

The recorded facts G_effect decides on — the effect analogue of GateDecision.

Every field is a FACT an orchestrator recorded (a ledger row, an attestation, a capability check), never a judgment made here. approval is the strength actually held (NONE when no human acted); the strength required is not a field — it is computed from reversibility via w(β), so a decision cannot claim a weaker requirement than its class demands.

reversibility instance-attribute

proposed instance-attribute

approval instance-attribute

capability_valid instance-attribute

attested instance-attribute

get_actuator(name)

Look up a cataloged hand's gate-facing spec. Raises (fail-closed) if name is not in the catalog — a proposal can only ever target a hand that passed the §8 audit.

capability_covers(capability, spec, *, now=None)

scoped_cap_valid as a decidable fact: the capability's scope is EXACTLY the scope the actuator requires (no prefix/glob authority — narrow means narrow), and it has not expired. An unparseable expiry is treated as expired (fail-closed), never as forever.

effect_gate_admits(decision)

G_effect(E, world) = proposed ∧ approved_{w(β)} ∧ scoped_cap_valid ∧ attested (§6).

Fail-closed: every conjunct must hold; any False denies and the world is unchanged (rejection is identity — the caller applies nothing on False). Pure data-in/bool-out: there is no E handle and no apply callback here, so an effect can never self-apply through the gate (I12, inherited). FSM-verified over all 72 states.

ops.effect_ledger

The durable effect ledger (Track G, item G5).

The in-memory HumanGate records that a routed request happened; this SQLite table is the tamper-evident memory of what the hands tried to DO in the world, whether the owner let them, what was staged, and — for a reversible write the owner undid — that it was rolled back. It is the effector twin of ops/ledger.ProposalLedger (which tracks self-mod knob changes); it reuses the same §14 lifecycle FSM (ops.ledger.LedgerStatus / IllegalTransition) because it is the same machine, wider domain (§6) — only the recorded facts differ (an actuator + params, not a lever + numeric target).

The lifecycle, enforced here so a caller cannot skip the safety ordering (I5):

PROPOSED ─approve→ APPROVED ─execute→ EXECUTED ─validate→ VALIDATED   (kept)
   │                                     └────rollback→ ROLLED_BACK   (undone)
   └──────deny──────────────────────→ DENIED

artifact_ref is what rollback acts on: the staged draft path for a reversible write (the edge effector unlinks it), or the attested send-record id for an irreversible one (which has no world undo — that is why it is full-gated; the ledger still records it happened). Storage only: the judgment (the gate predicate, the class→w(β) mapping, the actual staging/send) lives in ops/effect_gate.py, ops/effects.py, ops/effect_exec.py, and the edge effectors.

Thread-safety mirrors ProposalLedger / AttestationStore: opened check_same_thread=False with a reentrant lock guarding every method (a scheduler thread may propose while the owner approves).

EffectRecord dataclass

id instance-attribute

actuator instance-attribute

reversibility instance-attribute

scope instance-attribute

params instance-attribute

status instance-attribute

rationale instance-attribute

proposer instance-attribute

approver instance-attribute

approval_strength instance-attribute

artifact_ref instance-attribute

attestation_id instance-attribute

rollback_reason instance-attribute

proposed_at instance-attribute

decided_at instance-attribute

executed_at instance-attribute

resolved_at instance-attribute

EffectLedger dataclass

path instance-attribute

get(effect_id)

all()

pending()

propose(actuator, reversibility, *, scope='', params=None, rationale='', proposer='')

Record a new PROPOSED effect. Nothing is staged or sent — this is only the inbox row (the model advises; a human moves it to APPROVED; only then does code act).

approve(effect_id, *, approver='owner', strength=ApprovalStrength.LIGHT)

PROPOSED → APPROVED, recording the strength of the human act. Fail-closed if the strength does not COVER w(β) for the effect's class — you cannot record a LIGHT ack for an irreversible effect (the same invariant Effect.__post_init__ enforces, checked earlier here so the ledger never holds an under-approved record). Never auto-called.

deny(effect_id, *, approver='owner')

PROPOSED → DENIED (terminal).

mark_executed(effect_id, *, artifact_ref=None)

APPROVED → EXECUTED. Records the artifact rollback acts on (a staged draft path for a reversible write; an attested send-record id for an irreversible one).

mark_validated(effect_id)

EXECUTED → VALIDATED (kept — the staged artifact stands / the send is confirmed).

mark_rolled_back(effect_id, *, reason)

EXECUTED → ROLLED_BACK (undone). For a reversible write the edge effector has removed the staged artifact; this records why. (An irreversible effect has no world undo — the full gate is the point; a rollback here means the SEND itself failed, not an un-send.)

attach_attestation(effect_id, attestation_id)

Link the attested action record to this effect (records the accessor-join seam).

close()

open_effect_ledger(config=None)

Wire the effect ledger against the configured effectors data dir (beside sensing handoff). Independent of [effectors] enabled: the ledger is a record, safe to open read-only for audit even when the hands are off.

ops.effects

The effector layer's first-class types (Track G, items G1; hands-and-the-effector-layer.md).

Effection is the mirror image of ingestion (companion IV, family 1): MirrorView constrains read-flow into the introspective agents (a non-authored view is untypable); Effect / EffectView constrain action-flow out into the world (an unapproved consequential effect is unbuildable). Same object family — typed labels that constrain flow, enforced by making the wrong flow unrepresentable — so the guarantees are inherited, not invented.

Three structural facts live here:

  1. An illegal effect is unconstructable. Effect.__post_init__ raises if a non-SENSING reversibility class has no approval reference, or an approval weaker than its class demands. There is no code path that "checks and refuses" a rogue effect later — the object cannot exist (the MirrorView / ProposedChange move).
  2. An effect carries no confidence of its own. Worth-doing-ness is u-like — subjective, owner-judged — and must never be read off the adjudicator's c (companion III's axis separation, held as a hard rule at the actuator). An Effect may cite the motivating interpretations (cites), but deliberately has no confidence field: a high-c dream cannot earn an automatic action.
  3. The rollout is a filtration by blast radius (§4). blast_radius (β) places each reversibility class at a distance from the reversible origin; required_approval (w∘β) is non-decreasing in that distance; EffectView is Effects_{β≤ε} as a type, with the ceiling ε defaulting to the origin (SENSING). Raising ε is a deliberate, visible act — "you do not get a class until the one below is solid" is structural, not convention.

No credential appears anywhere in these types: ScopedCapability carries a scope name and a Vault token accessor (a non-secret reference) — execution-time code resolves the accessor to a live credential at the moment of action (G6, not built), the same discipline as MintedAgent.token staying off the prompt and off the repr, held one step harder: here the field does not exist at all.

ReversibilityClass

Bases: IntEnum

The blast-radius classes, in rollout order (hands-and-the-effector-layer.md §3).

Deliberately an IntEnum: unlike provenance — where G8 retired the trust order because no code used it — the order here is load-bearing. It is the §4 filtration index: β is monotone in it, w(β) is monotone in it, and EffectView's ceiling comparison is > on it.

SENSING = 0 class-attribute instance-attribute

REVERSIBLE = 1 class-attribute instance-attribute

IRREVERSIBLE = 2 class-attribute instance-attribute

ApprovalStrength

Bases: IntEnum

How much human approval an effect holds (or requires). Ordered so >= means 'covers': a FULL_GATE approval satisfies a LIGHT requirement, never the reverse.

NONE = 0 class-attribute instance-attribute

LIGHT = 1 class-attribute instance-attribute

FULL_GATE = 2 class-attribute instance-attribute

ScopedCapability dataclass

The authority envelope an effect requires — a NAME and a reference, never a credential.

scope is what the capability authorizes ("sense:fetch", "send:one-email-to:") — minted per-task, narrow by construction. accessor is the Vault token accessor (the non-secret handle the factory already attests); "" means credential-free (sensing). expires_at is an ISO timestamp; "" means bounded by the task, not the clock.

Deliberately there is NO token / secret field — the type physically cannot carry a live credential, so no agent holding an Effect holds authority (Track G constraint: security comes from the effector being narrow, not the reasoner being trusted).

scope instance-attribute

accessor = '' class-attribute instance-attribute

expires_at = '' class-attribute instance-attribute

ApprovalRef dataclass

A reference to a recorded human approval act: who approved, at what strength, and where the act is written down (a ledger row / attestation id — auditable, never implied). The honesty lives in the orchestrator that only constructs one from a real record, exactly as GateDecision.approved is a recorded fact, not a guess.

approver instance-attribute

strength instance-attribute

ref = '' class-attribute instance-attribute

UnapprovedEffectError

Bases: ValueError

A consequential effect was constructed without an approval covering its class — the illegal state the type deletes (dual of NonMirrorRowError).

Effect dataclass

One typed, scoped, attested world effect (hands-and-the-effector-layer.md §3).

Unconstructable illegally: __post_init__ raises unless the approval reference covers the reversibility class (None is admissible ONLY for SENSING). Functions typed to accept an Effect therefore inherit the proof that any consequential effect they see was approved.

cites names the interpretations that motivated the effect (ids into the derived layer) — a citation, never a confidence. There is deliberately no confidence field here (module docstring, point 2): the gate weighs an effect by its reversibility class and the owner's judgment, never by a c it could smuggle in.

actuator instance-attribute

capability instance-attribute

reversibility instance-attribute

proposal_att instance-attribute

approval_ref = None class-attribute instance-attribute

cites = () class-attribute instance-attribute

CeilingExceededError

Bases: ValueError

An effect above the view's blast-radius ceiling ε was offered to an EffectView.

EffectView dataclass

Effects_{β≤ε} as a type — the write-only-into-world boundary (dual of MirrorView).

Every contained effect is guaranteed to sit within the ceiling ε — the type itself is the proof, so code typed to accept an EffectView (a dispatcher, a handoff emitter) inherits "nothing above ε reaches the world through me". Obtain one via admit; direct construction re-validates, so a hand-built view cannot smuggle a higher class past the filtration.

The ceiling defaults to SENSING (ε = 0, the reversible origin): a fresh surface can dispatch read-only hands and nothing else. Raising ε is a deliberate, per-surface act — the §4 graduated rollout expressed structurally.

ceiling = ReversibilityClass.SENSING class-attribute instance-attribute

admit(effects, *, ceiling=ReversibilityClass.SENSING) classmethod

The sanctioned constructor: admit effects under a declared ceiling. Raises CeilingExceededError (fail-closed) if any effect's class exceeds ε — admission is the construction; there is no admitted-but-unchecked state.

effects()

The admitted effects (a fresh list; the view is immutable).

blast_radius(reversibility)

β : Effect → ℝ≥0 — distance from the reversible origin (§4). Sensing sits AT the origin (reversible by definition); reversible writes are a bounded undo away; irreversible external effects are at infinity (no undo exists at any cost).

required_approval(reversibility)

w(β) — approval strength as a non-decreasing function of blast radius (§4): sensing needs none, reversible needs light approval, irreversible needs the full gate. Monotonicity is property-tested (β(a) ≤ β(b) ⟹ w(a) ≤ w(b)) — the effector analogue of 'confidence decays with derivational depth'.

ops.gate

The human gate — seam for routing privileged requests (BUILD-SPEC §10, §14; Invariant 5).

When the factory is asked for capability beyond an agent's scope ceiling, it routes the request HERE instead of minting a privileged agent (§10). This is the Phase-5 seam: it records the request as PENDING so nothing privileged ever happens unattended (Invariant 5, Constitution §II.4). The full propose → approve → execute → validate → rollback ledger landed in Phase 10 as the durable ops/ledger.ProposalLedger (orchestrated by ops/selfmod.py); this module remains the lightweight in-memory inbox for routed privileged requests, plus the decidable gate predicate (gate_admits) that the Phase-10 validate step consumes.

GateStatus

Bases: StrEnum

PENDING = 'pending' class-attribute instance-attribute

APPROVED = 'approved' class-attribute instance-attribute

DENIED = 'denied' class-attribute instance-attribute

GateDecision dataclass

The measurable facts the live gate decides on (gap G5). conforms is intentionally absent — it is deferred, not assumed.

approved instance-attribute

golden_non_regressing instance-attribute

drift_within_tolerance instance-attribute

GateRequest dataclass

id instance-attribute

kind instance-attribute

detail instance-attribute

status instance-attribute

created_at instance-attribute

agent = None class-attribute instance-attribute

HumanGate dataclass

Records routed requests. Nothing is auto-approved — approval is a human act (Phase 10 wires the durable SQLite ledger + the approve/execute/validate/rollback loop).

submit(kind, detail, *, agent=None)

pending()

all()

gate_admits(decision)

G_now(Δ,s) = approved ∧ golden-non-regressing ∧ drift-within-tolerance (gap G5).

Fail-closed: every conjunct must hold; any False (or unknown, surfaced as False) denies. The deferred conforms conjunct is omitted honestly rather than stubbed True.

ops.import_lint

Static import-graph firewall lint (Invariant 2, BUILD-SPEC §3).

I2 is a property of the import closure: if no module under core/ can reach a network-capable module, then no egress path exists regardless of edge behavior — composition cannot create one. The Phase-0 runtime egress guard (core.sealing) enforces this at run time; this lint promotes it to the static tier — provable without running, by reading the AST. It is the discharge the formal-properties catalog asks for (I2: "promote from runtime to static").

Two rules, of different strength:

  1. Zone non-interference (hard, zero exceptions). No core/ module may import edge (Zone B) or cloud (Zone C). This is the load-bearing structural fact: the sealed core cannot even name the networked zones, so there is no core → edge → net path to create. There is no audited exception and there never should be — core↔edge is a filesystem handoff, never an import (CONVENTIONS).

  2. Networking primitives (allowlisted). No core/ module may import a networking module (socket, ssl, http, urllib, requests, …) EXCEPT the two audited loopback/seal modules:

    • core/sealing.py — imports socket precisely to WRAP and seal it (the guard).
    • core/models/ollama_client.py — the single sanctioned loopback IPC channel to the local Ollama server (127.0.0.1), which the egress guard permits. Every other core module is thereby statically proven free of networking imports. We state this honestly: the guarantee is "exactly these two audited files touch networking primitives, both loopback-only", not "no core file imports http" — overclaiming the latter would be dishonest (the Ollama channel is real).

Run: python -m ops.import_lint (or scripts/check_imports.py); also asserted in tests/test_import_firewall.py and wired into CI.

FORBIDDEN_ZONES = frozenset({'edge', 'cloud'}) module-attribute

NETWORK_MODULES = frozenset({'socket', 'ssl', 'http', 'urllib', 'ftplib', 'smtplib', 'poplib', 'imaplib', 'nntplib', 'telnetlib', 'socketserver', 'xmlrpc', 'asyncore', 'asynchat', 'requests', 'httpx', 'aiohttp', 'urllib3', 'websockets', 'websocket', 'boto3', 'botocore', 'paramiko', 'pycurl', 'grpc', 'hvac'}) module-attribute

NETWORK_ALLOWLIST = frozenset({'core/sealing.py', 'core/models/ollama_client.py'}) module-attribute

Violation dataclass

path instance-attribute

lineno instance-attribute

imported instance-attribute

rule instance-attribute

scan_file(path, *, repo_root)

scan_core(repo_root=None)

Scan every module under core/ for forbidden imports (I2).

main()

ops.ledger

The durable propose→approve→execute→validate→rollback ledger (BUILD-SPEC §14; Invariant 5).

This is the Phase-10 upgrade of the in-memory HumanGate inbox (ops/gate.py): a SQLite table that records every self-modification proposal and walks it through a strict lifecycle. The table is the system's tamper-evident memory of what it tried to change to itself, whether a human let it, what happened, and — when a change regressed — that it was reversed.

The lifecycle is a finite state machine, enforced here so the safety ordering of §14 cannot be skipped by a caller (Invariant 5: "no step skipped"):

PROPOSED ─approve→ APPROVED ─execute→ EXECUTED ─validate→ VALIDATED   (kept)
   │                                      └────rollback→ ROLLED_BACK  (reverted)
   └──────deny──────────────────────→ DENIED

Each transition asserts the current state is exactly its precondition (fail-closed): you cannot execute something un-approved, validate something un-executed, or approve something already decided. A model can write a PROPOSED row; only a human moves it to APPROVED; only code that holds that approval moves it onward. Storage only — the judgment (bounds, the admit predicate, the apply) lives in ops/levers.py, ops/gate.py, ops/apply.py and is orchestrated by ops/selfmod.py.

Thread-safety mirrors the AttestationStore / JobQueue idiom (PROGRESS 2026-06-27): opened check_same_thread=False with a reentrant lock guarding every method, since a background scheduler thread may propose while the foreground approves.

LedgerStatus

Bases: StrEnum

PROPOSED = 'proposed' class-attribute instance-attribute

APPROVED = 'approved' class-attribute instance-attribute

DENIED = 'denied' class-attribute instance-attribute

EXECUTED = 'executed' class-attribute instance-attribute

VALIDATED = 'validated' class-attribute instance-attribute

ROLLED_BACK = 'rolled_back' class-attribute instance-attribute

Proposal dataclass

id instance-attribute

lever instance-attribute

current_value instance-attribute

target_value instance-attribute

status instance-attribute

rationale instance-attribute

proposer instance-attribute

approver instance-attribute

prior_overlay instance-attribute

metrics instance-attribute

rollback_reason instance-attribute

attestation_id instance-attribute

proposed_at instance-attribute

decided_at instance-attribute

executed_at instance-attribute

resolved_at instance-attribute

IllegalTransition

Bases: RuntimeError

A lifecycle step was attempted from the wrong state (fail-closed §14 ordering guard).

ProposalLedger dataclass

path instance-attribute

get(proposal_id)

all()

pending()

propose(lever, current_value, target_value, *, rationale='', proposer='')

Record a new PROPOSED change. Nothing is applied — this is just the inbox row.

approve(proposal_id, *, approver='owner')

PROPOSED → APPROVED. A human act — never auto-called by the loop (Invariant 5).

deny(proposal_id, *, approver='owner')

PROPOSED → DENIED (terminal).

mark_executed(proposal_id, *, prior_overlay)

APPROVED → EXECUTED. Records the prior overlay value so rollback is exact (NULL means the loop introduced the key and rollback should remove it).

mark_validated(proposal_id, *, metrics=None)

EXECUTED → VALIDATED (kept). Stores the validation metrics that cleared the gate.

mark_rolled_back(proposal_id, *, reason, metrics=None)

EXECUTED → ROLLED_BACK (reverted). Records why an anchor regressed.

attach_attestation(proposal_id, attestation_id)

Link a signed gate-decision attestation to this proposal (records the accessor-join seam; optional, used when [attestation] signing is on).

close()

open_ledger(config=None)

ops.levers

The lever registry — the ENTIRE self-modifiable surface (BUILD-SPEC §14; Invariant 5).

This is the trust boundary the owner drew for Phase 10: self-modification may tune knobs (numeric alignment/quality parameters) and nothing else. Infrastructure and code are an extremely privileged resource the loop cannot write to.

That restriction is structural, not a policy check. A ProposedChange references a Lever by name and carries a numeric target. There is no field on it — anywhere — that can hold a file path, a diff, a command, or a Terraform plan. So "edit this file" / "run this" is not a proposal the loop can express, the same way the airlock's ResearchCriteria has no field that can carry note content (core/research/criteria.py). A future code/infra lever would be a deliberate, separately-gated extension with its own apply path and human-only ceiling — adding it here would be a visible, reviewable diff against this registry, never a guess.

Each lever names a single scalar config key (section.key) and its HARD bounds. The bounds are the ones already written into config/defaults.toml's [dreaming] comments — promoted here from prose to enforced code. A target outside [lo, hi] is rejected (fail-closed); the loop cannot push a knob past the envelope the owner declared safe even with approval.

LEVERS = {(lever.name): lever for lever in _LEVERS} module-attribute

LeverKind

Bases: StrEnum

FLOAT = 'float' class-attribute instance-attribute

INT = 'int' class-attribute instance-attribute

Lever dataclass

One tunable knob: a single scalar config key with hard, owner-declared bounds.

name is the stable identifier a proposal references. section/key locate the value in the TOML config (and in the machine-owned overlay the loop writes). lo/hi are INCLUSIVE bounds — the whole admissible range for this knob, never to be exceeded by any approved change.

name instance-attribute

section instance-attribute

key instance-attribute

kind instance-attribute

lo instance-attribute

hi instance-attribute

description = '' class-attribute instance-attribute

coerce(value)

Snap a value to the lever's type (int levers carry whole numbers).

in_bounds(value)

validate(value)

Coerce + bounds-check. Raises (fail-closed) on out-of-bounds — the caller never silently clamps, because a proposal that wanted an out-of-range value is a proposal we refuse, not one we quietly rewrite.

ProposedChange dataclass

A proposed knob change — the ONLY shape a self-modification can take.

It is a (lever-name, target-number) pair and nothing more. Deliberately there is no path, diff, command, code, or script field: the loop physically cannot propose a code or infrastructure change because the value object has nowhere to put one. This is the structural expression of the owner's Phase-10 ceiling (see module docstring).

lever instance-attribute

target instance-attribute

rationale = '' class-attribute instance-attribute

resolve()

Resolve to (lever, validated-target). Raises on unknown lever or out-of-bounds target — both fail-closed, so an invalid proposal never reaches the ledger as PROPOSED.

get_lever(name)

Look up a registered lever. Raises (fail-closed) if name is not in the registry — a proposal can only ever target a knob that exists here.

ops.selfmod

The self-modification loop (BUILD-SPEC §14, Phase 10 — the last + most privileged capability).

Ties the pieces together into the §14 cycle, with every step held by deterministic code or a human — never a model:

propose   a model may write a PROPOSED knob change (ops/levers.ProposedChange — knobs only,
          code/infra structurally unrepresentable). Bounds are checked here; out-of-range
          never reaches the ledger.
approve   a human moves it to APPROVED (ProposalLedger). The loop never auto-approves on the
          attended path.
execute   deterministic code writes the knob into the machine-owned overlay (ops/apply.py)
          and records the exact prior value for an exact rollback. No model holds the write.
validate  run the validator → build the §15 gate decision (ops/gate.GateDecision) from the
          FROZEN golden anchor (capability) + the rolling baseline (acute drift). `conforms`
          (behavioral) stays honestly deferred, exactly as ops/gate documents.
rollback  if the gate denies, mechanically restore the prior overlay value. Keep-or-revert is
          not a judgment call — it is `gate_admits(...)` returning False.

Two fail-closed switches gate all of this (config/loader.SelfModConfig): selfmod.enabled is the master switch; selfmod.unattended_enabled separately gates the ONLY path that skips human approval (the §14 "safe levers"), and is OFF by default. The owner's Phase-10 ceiling — tune alignment/quality knobs, treat code and infrastructure as an extremely privileged resource the loop cannot write — is enforced upstream in ops/levers.py (structural) and here (the switches).

SAFE_LEVERS = frozenset({'dream_max_clusters'}) module-attribute

Validator = Callable[[Lever, float], ValidationResult] module-attribute

SelfModDisabled

Bases: RuntimeError

The master switch [selfmod] enabled is off — the loop refuses to act (fail-closed).

UnattendedDisabled

Bases: RuntimeError

[selfmod] unattended_enabled is off — no change may skip the human gate (fail-closed).

NotASafeLever

Bases: RuntimeError

A lever not in the pre-declared SAFE_LEVERS set was offered to the unattended path.

ValidationResult dataclass

What the validator measured about the post-change state. Mirrors the gate's MEASURABLE conjuncts (ops/gate.GateDecision); conforms (behavioral) is deferred, not stubbed True.

golden_non_regressing instance-attribute

drift_within_tolerance instance-attribute

metrics = field(default_factory=dict) class-attribute instance-attribute

SelfModLoop dataclass

Orchestrates one proposal through the §14 cycle. Construct with the live config + ledger for production; tests inject a crafted config, an in-memory ledger, a tmp overlay path, and a stub validator.

config instance-attribute

ledger instance-attribute

validator instance-attribute

overlay_path = LEVERS_OVERLAY class-attribute instance-attribute

propose(change, *, proposer='')

Record a PROPOSED knob change. change.resolve() validates bounds first (fail-closed), so an unknown lever or out-of-range target raises here and never reaches the ledger.

approve(proposal_id, *, approver='owner')

deny(proposal_id, *, approver='owner')

execute(proposal_id)

Apply an APPROVED proposal's knob to the overlay (deterministic code, no model). The ledger enforces APPROVED→EXECUTED, so an un-approved proposal cannot be executed.

validate(proposal_id)

Run the validator on the EXECUTED change and let the §15 gate decide, mechanically: admit → VALIDATED (kept); deny → restore the prior overlay value → ROLLED_BACK.

execute_and_validate(proposal_id)

For an already-APPROVED proposal, run execute then validate. Still no auto-approval — approval is the human act that happened before this is called.

apply_unattended(change, *, proposer='watchdog')

Propose → AUTO-approve → execute → validate, with NO human, for a pre-declared safe lever. Gated by BOTH switches: refuses unless enabled AND unattended_enabled, and the lever must be in SAFE_LEVERS. The auto-approval is recorded as approver='unattended' so the ledger shows plainly that no human signed off. Default config never reaches the body — unattended_enabled is off.

build_golden_validator(retriever, *, golden=None, frozen_baseline=None, rolling_baseline=None)

Default validator: run the frozen golden set through retriever and decide both §15 conjuncts from it. golden_non_regressing is the STRICT capability check against the FROZEN anchor (baseline.json, Invariant 9 — any metric below baseline fails). drift_within_tolerance is now the REAL §15 drift gauge D(Δ·s) ≤ Θ (eval.drift, item A1): a one-sided, normalized, aggregate deterioration distance over the mixed profile (capability rates ⊕ Constitution conformance) vs the same frozen anchor — the honest realization of the gate's drift conjunct (G4/G5), replacing the earlier rolling-regression stand-in.

The two conjuncts are complementary defense-in-depth against the boiling-frog problem (§15): golden_non_regressing trips on ANY capability drop; the gauge tolerates healthy drift up to Θ but hard-trips on a Constitution-fingerprint breach and on aggregate deterioration past the blessed band. The optional rolling_baseline is retained as ADVISORY acute-regression detail in metrics (it no longer decides the gate).

Imports eval lazily so ops/selfmod stays importable without the eval fixtures loaded.

build_loop(validator, *, config=None, ledger=None)

Wire a loop from live config + the durable ledger. The validator is required (the caller decides whether to use build_golden_validator with a live retriever or a custom one).

ops.selfmod_cli

Operator front door to the self-modification gate (BUILD-SPEC §14, Phase 10; Invariant 5).

A model may write PROPOSED knob changes; only a human moves one to APPROVED. This CLI is that human's hands on the gate:

list                      pending proposals awaiting a decision
history                   every proposal and its outcome
show <id>                 full detail of one proposal
propose <lever> <target>  hand-author a proposal (also the seam a future agent calls)
deny <id>                 reject a pending proposal (terminal)
approve <id>              approve → execute → VALIDATE against the frozen golden anchor →
                          keep, or AUTO-ROLLBACK on regression (§15). Validation runs the real
                          anchor; if the embedder is unavailable the approval is REFUSED —
                          never keep an unvalidated change (fail-closed).

[selfmod] enabled gates every state-changing command; reads work regardless so the owner can always inspect history. The live golden validator is built only for approve, lazily, so reads don't pull in the embedder.

cmd_list(ledger)

cmd_history(ledger)

cmd_show(ledger, pid)

cmd_propose(loop, lever, target, rationale)

cmd_deny(loop, pid)

cmd_approve(loop, pid)

Approve, then mechanically execute + validate. The outcome (kept vs auto-rolled-back) is the gate's, not ours — we just report it.

build_live_validator()

Validate against the frozen golden anchor with the real embedder: ingest the self-contained fixture corpus into a throwaway store and score the blessed queries (mirrors tests/e2e/test_golden_live.py). Raises if the embedder isn't available — the caller must NOT keep a change it could not validate. Imports core lazily so reads stay light.

main(argv)

ops.supersede

Owner CLI logic: declare an authored-historical supersession between two vault notes.

The phone capture shortcut emits a NEW timestamp-named file per note, so a phone-side "edit" is a new document, not an in-place amendment — the version machinery never fires. The correct semantics for that flow is the owner-declared K₀↔K₀ cross-document supersession (the-edge-model.md §4a; the founding edge's mechanism): the new note goes active, the original stays intact in raw + history, and the edge records the owner's ruling. This module resolves the two notes and mints the declaration; it is an OWNER-OPERATED entry point (mind-palace supersede) — the store's boundary check refuses any machine caller regardless (MachineAuthorityRefused).

In-place edits (Mac / Files app) still take the version-amendment path automatically; this CLI is only for the new-file flow. Retrieval demotion of the superseded note is the active-projection consumer (Item 8 territory) — until it lands, both notes surface in retrieval and the edge is durable provenance awaiting its consumer.

AmbiguousRef

Bases: ValueError

A ref that matches zero or several active documents — refuse, never guess.

resolve(catalog, ref)

(source_path, digest) for a unique active doc matched by full path or suffix.

declare(catalog, store, old_ref, new_ref, note='')

Resolve both notes and record new supersedes old. Returns the digest pair.