dkta labs · internal

Dwarf Society — Runtime Boundary Implementation Plan

DATE 2026-07-13 AUTHOR Dakota Secula-Rosell STATUS Working

For agentic workers: REQUIRED SUB-SKILL: use subagent-driven-development (recommended) or executing-plans to execute this plan task-by-task. Steps use checkbox semantics and every task ends at a review and commit gate.

Goal Prove the first vertical boundary in the approved Dwarf Society design: current Windows Dwarf Fortress can expose the seven citizens' canonical state, pause and advance deterministically, accept one allowlisted job claim, return durable receipts, reject stale/duplicate commands correctly, survive save/load, and reach Mac oMLX privately.

Architecture. A Python 3.13 sidecar runs natively on Windows and exchanges strict JSON envelopes with an enabled DFHack Lua script through a single-writer atomic file spool. The Lua request poll is frame-based so it continues while the game is paused. Game advancement is a separate bounded tick timer. The bridge owns world revisions and receipts; Python owns requests, evidence, and later society logic.

Tech stack. Dwarf Fortress 53.15, DFHack 53.15-r2, DFHack Lua 5.3 APIs, Windows Python 3.13, uv, Pydantic 2, HTTPX, standard-library SQLite/JSON/path handling, pytest, Ruff, PowerShell, and GitHub.

§01 · Scope decision

Plan the empirical gate before the society

This is deliberately the implementation plan for design Proof 1, not a speculative micro-plan for all four proofs. The actor, knowledge, scheduling, conversation, and governance plans consume the normalized bridge contract produced here. Writing those plans before observing the current 53.15 structures would bake guesses into load-bearing interfaces.

Once every exit criterion in §06 passes, write the next plan for one embodied dwarf using the committed fixtures and schemas from this repository. No design approval is reopened unless the bridge proves a required observation or legal action impossible.

§02 · Ground truth

Observed target as of 2026-07-13

FactEvidence
Installed gameD:\SteamLibrary\steamapps\common\Dwarf Fortress; release notes.txt identifies 53.15, dated 2026-06-25.
DFHack state[TESTED 2026-07-13] Steam stable installed DFHack 53.15-r2 at D:\SteamLibrary\steamapps\common\DFHack; native dfhack-run version verified the release.
Matching releaseDFHack 53.15-r2 is the current stable 53.15-compatible bug-fix/API release.
Windows runtimePython 3.13 is installed at C:\Users\parzi\AppData\Local\Programs\Python\Python313\python.exe; uv.exe is on PATH.
Source rootC:\Users\parzi\source\repos exists and is empty; it can host the native-Windows working copy without UNC-current-directory failures.
InferenceThe Mac Studio is the sole capable local host. Native Windows-to-Mac reachability is not yet established; WSL's supervised tunnel is only a fallback pattern.

§03 · Global constraints

Invariants every task inherits

§04 · File map

Repository structure locked by this plan

Path in dwarf-societyResponsibility
pyproject.toml, uv.lockPython 3.13 package, runtime dependencies, test/lint commands.
src/dwarf_society/contracts.pyStrict protocol envelopes, health/world/citizen/job/receipt models, protocol version.
src/dwarf_society/config.pyRead and validate local runtime.toml without embedding machine paths in committed code.
src/dwarf_society/ipc.pyAtomic request publication, response wait, timeout, archive, and protocol validation.
src/dwarf_society/client.pyTyped health, observe, advance, and claim_job calls.
src/dwarf_society/cli.pyNative Windows smoke and evidence commands; no policy.
src/dwarf_society/report.pyBuild the redacted runtime-boundary JSON and self-contained HTML reports from recorded receipts.
dfhack/scripts/dwarf-society.luaEnable/disable lifecycle, frame-based poll registration, request dispatch.
dfhack/scripts/dwarf-society/protocol.luaSpool paths, atomic claim/write, strict envelope checks, receipt ledger, world identity and revision.
dfhack/scripts/dwarf-society/snapshot.luaRead-only normalized fortress, citizen, personality, job, and relationship extraction.
dfhack/scripts/dwarf-society/actions.luaPreflight and apply the sole mutating Proof 1 capability: claim an existing legal job.
scripts/install_bridge.ps1Copy bridge scripts into dfhack-config\scripts and verify hashes; never edit game saves.
tests/unit/Pure contract, config, IPC, client, and report tests.
tests/integration/test_runtime_boundary.pyOpt-in live DFHack contract test; skipped unless DWARF_SOCIETY_LIVE=1.
tests/fixtures/runtime/Normalized seven-citizen snapshot, health response, available-job response, stale rejection, and duplicate receipt.
runtime/Gitignored local config, spool pointer, clean save copy, logs, SQLite/evidence, and reports under a UTC run ID.

§05 · Task plan

Seven reviewable deliveries

Task 1 — Preserve the game, install DFHack, and bootstrap the sibling repo

Consumes: observed 53.15 Steam install, Windows Python 3.13, uv. Produces: backed-up saves, DFHack 53.15-r2, disposable fixed embark, private repository, and passing package smoke/build checks.

  • From native PowerShell, record and verify the installed state without changing it:
    $DfRoot = 'D:\SteamLibrary\steamapps\common\Dwarf Fortress'
    $stamp = Get-Date -AsUTC -Format 'yyyyMMddTHHmmssZ'
    Select-String -Path "$DfRoot\release notes.txt" -Pattern '^Release notes for 53\.15'
    if (Test-Path "$DfRoot\hack") { throw 'Unexpected DFHack install already present' }
    $backup = "D:\SteamLibrary\df-saves-backup-$stamp"
    New-Item -ItemType Directory -Path $backup | Out-Null
    robocopy "$DfRoot\save" "$backup\save" /E /COPY:DAT /DCOPY:DAT /R:1 /W:1
    if ($LASTEXITCODE -gt 7) { throw "save backup failed: $LASTEXITCODE" }
    Get-FileHash "$DfRoot\Dwarf Fortress.exe" -Algorithm SHA256
    Expected: one 53.15 match, no existing hack, robocopy exit 0–7, non-empty backup and executable hash.
  • Install Steam app 2346660 (DFHack), pin it to the stable channel, and launch Dwarf Fortress through the DFHack app. In the DFHack console run version. Record output showing DF 53.15 and DFHack 53.15-r2. Stop if either value differs.
  • Create and clone the private sibling repository from native PowerShell:
    Set-Location C:\Users\parzi\source\repos
    gh repo create dkta-labs/dwarf-society --private --clone
    Set-Location .\dwarf-society
    uv init --package --name dwarf-society --python 3.13
    Remove-Item .\README.md -ErrorAction SilentlyContinue
    uv add 'pydantic>=2.11,<3' 'httpx>=0.28,<1'
    uv add --dev 'pytest>=8.4,<9' 'ruff>=0.12,<1'
    New-Item -ItemType Directory -Force -Path `
      src\dwarf_society,tests\unit,tests\integration,tests\fixtures\runtime, `
      dfhack\scripts\dwarf-society,scripts,runtime | Out-Null
  • Create a new, disposable seven-dwarf embark named agent_v0_fixture. Immediately pause and save. Copy its save directory to runtime\saves\agent_v0_clean; never use an existing region as the fixture.
  • Write .gitignore with exactly:
    .venv/
    __pycache__/
    .pytest_cache/
    .ruff_cache/
    *.pyc
    runtime/*
    !runtime/.gitkeep
    
    Add empty runtime\.gitkeep.
  • Configure pytest and Ruff in pyproject.toml:
    [tool.pytest.ini_options]
    testpaths = ["tests"]
    addopts = "-ra"
    markers = ["live: requires a running disposable DFHack fortress"]
    
    [tool.ruff]
    target-version = "py313"
    line-length = 100
    
    [tool.ruff.lint]
    select = ["E", "F", "I", "UP", "B", "SIM"]
  • Create tests/unit/test_smoke.py:
    import dwarf_society
    
    
    def test_package_imports() -> None:
        assert dwarf_society.__package__ == "dwarf_society"
  • Set project metadata to name = "dwarf-society", a non-placeholder description, and Dakota's author name. Remove the premature [project.scripts] table; Task 7 adds the real CLI after dwarf_society.cli exists.
  • Run uv run pytest, uv build, and uv run ruff check .. Expected: one smoke test passes; wheel and sdist build with the dwarf_society package; Ruff exits 0.
  • Commit:
    git add .gitignore .python-version pyproject.toml uv.lock runtime/.gitkeep `
      src/dwarf_society/__init__.py tests/unit/test_smoke.py
    git commit -m "chore: bootstrap dwarf society runtime"

Task 2 — Lock strict request, response, and snapshot contracts

Consumes: package scaffold. Produces: RequestEnvelope, ResponseEnvelope, WorldSnapshot, CitizenSnapshot, JobSnapshot, and ActionReceipt with Pydantic extra-field rejection.

  • Write failing tests in tests/unit/test_contracts.py:
    from uuid import uuid4
    
    import pytest
    from pydantic import ValidationError
    
    from dwarf_society.contracts import PROTOCOL_VERSION, RequestEnvelope, ResponseEnvelope
    
    
    def test_request_rejects_unknown_fields() -> None:
        payload = {
            "protocol": PROTOCOL_VERSION,
            "request_id": str(uuid4()),
            "kind": "health",
            "payload": {},
            "unexpected": True,
        }
        with pytest.raises(ValidationError):
            RequestEnvelope.model_validate(payload)
    
    
    def test_response_requires_matching_protocol() -> None:
        with pytest.raises(ValidationError):
            ResponseEnvelope.model_validate({
                "protocol": "999",
                "request_id": str(uuid4()),
                "status": "ok",
                "result": {},
            })
  • Run uv run pytest tests/unit/test_contracts.py -v. Expected: import failure for dwarf_society.contracts.
  • Create src/dwarf_society/contracts.py. Use frozen, strict models and these exact top-level interfaces:
    from typing import Literal
    from uuid import UUID
    
    from pydantic import BaseModel, ConfigDict, Field, model_validator
    
    PROTOCOL_VERSION = "1"
    
    
    class StrictModel(BaseModel):
        model_config = ConfigDict(extra="forbid", frozen=True)
    
    
    class RequestEnvelope(StrictModel):
        protocol: Literal["1"] = PROTOCOL_VERSION
        request_id: UUID
        kind: Literal["health", "observe", "advance", "claim_job"]
        run_id: UUID | None = None
        save_id: str | None = None
        bridge_epoch: str | None = None
        expected_revision: int | None = Field(default=None, ge=0)
        command_id: UUID | None = None
        payload: dict[str, object]
    
    
    class ProtocolError(StrictModel):
        code: Literal[
            "invalid_request", "wrong_save", "wrong_epoch", "stale_revision", "bridge_busy",
            "unsupported_capability", "precondition_failed", "internal_error"
        ]
        message: str
    
    
    class ResponseEnvelope(StrictModel):
        protocol: Literal["1"] = PROTOCOL_VERSION
        request_id: UUID
        status: Literal["ok", "rejected", "error"]
        save_id: str | None = None
        bridge_epoch: str | None = None
        observed_revision: int | None = Field(default=None, ge=0)
        result: dict[str, object] | None = None
        error: ProtocolError | None = None
    
        @model_validator(mode="after")
        def error_matches_status(self):
            if self.status == "ok" and self.error is not None:
                raise ValueError("ok response cannot carry an error")
            if self.status != "ok" and self.error is None:
                raise ValueError("non-ok response requires an error")
            return self
  • In the same file define the normalized records used by Task 4: Position(x,y,z), EnumScore(name,value), NeedState(name,deity_id,focus_level,need_level), SkillState(name,rating,experience), RelationshipState(kind,target_hf_id,strength), JobSnapshot(id,type_name,worker_unit_id), CitizenSnapshot(unit_id,histfig_id,name,profession,position,stress, stress_category,current_job_id,traits,values,needs,skills,relationships), and WorldSnapshot(save_id,bridge_epoch,revision,year,year_tick,paused,citizens,jobs). All lists default to empty through Field(default_factory=list); all models inherit StrictModel.
  • Add tests that parse one complete seven-citizen fixture assembled in the test, reject duplicate unit_id values through a WorldSnapshot model validator, and reject a relationship whose target_hf_id is negative.
  • Run uv run pytest tests/unit/test_contracts.py -v. Expected: all contract tests pass.
  • Commit:
    git add src/dwarf_society/contracts.py tests/unit/test_contracts.py
    git commit -m "feat: define strict bridge contracts"

Task 3 — Build atomic Python IPC and the frame-based bridge heartbeat

Consumes: protocol models. Produces: SpoolClient.request(envelope, timeout_s) -> ResponseEnvelope, enabled Lua bridge, and a live health round trip while the game is paused.

  • Write failing unit tests in tests/unit/test_ipc.py for atomic publication, response correlation, malformed response rejection, archive movement, and timeout. The happy-path test uses a background thread that moves one request into processing and writes the matching response.
  • Run uv run pytest tests/unit/test_ipc.py -v. Expected: import failure for SpoolClient.
  • Create src/dwarf_society/ipc.py with this public surface and atomic write:
    class BridgeTimeout(TimeoutError):
        pass
    
    
    class SpoolClient:
        def __init__(self, root: Path) -> None:
            self.root = root
            self.requests = root / "requests"
            self.responses = root / "responses"
            self.archive = root / "archive"
            for path in (self.requests, self.responses, self.archive):
                path.mkdir(parents=True, exist_ok=True)
    
        @staticmethod
        def _write_atomic(path: Path, content: str) -> None:
            temp = path.with_suffix(path.suffix + ".tmp")
            with temp.open("w", encoding="utf-8", newline="\n") as handle:
                handle.write(content)
                handle.flush()
                os.fsync(handle.fileno())
            os.replace(temp, path)
    
        def request(self, envelope: RequestEnvelope, timeout_s: float = 10.0) -> ResponseEnvelope:
            name = f"{time.time_ns():020d}-{envelope.request_id}.json"
            request_path = self.requests / name
            self._write_atomic(request_path, envelope.model_dump_json())
            response_path = self.responses / f"{envelope.request_id}.json"
            deadline = time.monotonic() + timeout_s
            while time.monotonic() < deadline:
                if response_path.exists():
                    response = ResponseEnvelope.model_validate_json(response_path.read_text("utf-8"))
                    if response.request_id != envelope.request_id:
                        raise ValueError("response request_id mismatch")
                    os.replace(response_path, self.archive / response_path.name)
                    return response
                time.sleep(0.025)
            raise BridgeTimeout(f"bridge timed out after {timeout_s:.3f}s")
  • Create dfhack/scripts/dwarf-society/protocol.lua. Its init_spool() uses scriptmanager.getModStatePath('dwarf-society') and dfhack.filesystem.mkdir_recursive() for requests, processing, responses, and archive. Its claim_next() sorts dfhack.filesystem.listdir(requests) and uses os.rename to move one .json request into processing before decoding it.
  • Implement response publication in Lua with a same-directory temporary and rename:
    local function write_response(request_id, response)
        local final = paths.responses .. request_id .. '.json'
        local temp = final .. '.tmp'
        json.encode_file(response, temp, {pretty=false})
        assert(os.rename(temp, final))
    end
  • Create dfhack/scripts/dwarf-society.lua with enable semantics. Register the poll with frames so paused requests still complete:
    --@ enable=true
    --@ module=true
    local repeat_util = require('repeat-util')
    local protocol = reqscript('dwarf-society/protocol')
    local KEY = 'dwarf-society'
    state = state or {enabled=false}
    runtime = runtime or {}
    
    local function poll()
        protocol.process_one()
    end
    
    local function do_enable()
        runtime.bridge_epoch =
            ('%d-%d-%s'):format(os.time(), dfhack.getTickCount(), tostring({}))
        protocol.init_spool(runtime)
        repeat_util.scheduleEvery(KEY, 5, 'frames', poll)
    end
    
    local function do_disable()
        repeat_util.cancel(KEY)
    end
    
    function isEnabled() return state.enabled end
    function setEnabled(enabled)
        if enabled == state.enabled then return end
        state.enabled = enabled
        if enabled then do_enable() else do_disable() end
    end
    Dispatch only health in this task. Health returns DF version, DFHack version, fortress-mode flag, paused state, dfhack.getSavePath(), current site ID, the module-lifetime runtime.bridge_epoch, revision 0, and the four-item capability manifest. In do_enable(), assign the epoch exactly once with ('%d-%d-%s'):format(os.time(), dfhack.getTickCount(), tostring({})) before scheduling the frame poll; every handler reuses that value.
  • Create scripts/install_bridge.ps1 to copy the four repository Lua paths into $DfRoot\dfhack-config\scripts, then compare SHA-256 hashes source to destination. It accepts only a validated -DfRoot parameter and refuses a root whose release notes do not contain 53.15.
  • Run pure tests, install the bridge, launch the disposable save, run enable dwarf-society, pause, and issue a health request from native PowerShell through a temporary Python command. Expected: response status ok, DF 53.15, DFHack 53.15-r2, paused=true, and no timeout.
  • Call health twice while the bridge remains enabled and assert the epoch is identical. Disable and re-enable the bridge, call health again, and assert the new epoch differs. A health request may observe the epoch; it may never regenerate it.
  • Commit:
    git add src/dwarf_society/ipc.py tests/unit/test_ipc.py dfhack scripts/install_bridge.ps1
    git commit -m "feat: add paused-safe bridge transport"

Task 4 — Extract and lock the seven-citizen canonical snapshot

Consumes: live health bridge, normalized contracts. Produces: observe response, committed normalized fixture, exact 53.15 field map for personality, work, and relationships.

  • Write tests/integration/test_runtime_boundary.py with @pytest.mark.live and an environment gate. The first test calls client.observe() and asserts: paused; exactly seven citizens; unique unit and historical-figure IDs; non-empty names, traits, values, and needs; at least one skill across the seven citizens; valid positions; every non-null current job refers to a known job; and every relationship target that exists is a non-negative historical-figure ID.
  • Run without the gate: uv run pytest tests/integration/test_runtime_boundary.py -v. Expected: one skip explaining DWARF_SOCIETY_LIVE=1.
  • Create dfhack/scripts/dwarf-society/snapshot.lua. Iterate dfhack.units.getCitizens(true, false). Read only these verified 53.15 sources: unit and historical-figure IDs; getReadableName; getProfessionName; getPosition; current job ID; getStressCategory; status.current_soul.skills; and status.current_soul.personality.{traits,values,needs,stress}.
  • Normalize enum-keyed records by name, never by unexplained integer alone. For example:
    local function enum_name(enum, value)
        return enum[value] or ('UNKNOWN_' .. tostring(value))
    end
    
    local function extract_needs(personality)
        local out = {}
        for _, need in ipairs(personality.needs) do
            table.insert(out, {
                name=enum_name(df.need_type, need.id),
                deity_id=need.deity_id,
                focus_level=need.focus_level,
                need_level=need.need_level,
            })
        end
        return out
    end
    
    local function extract_skills(soul)
        local out = {}
        for _, skill in ipairs(soul.skills) do
            table.insert(out, {
                name=enum_name(df.job_skill, skill.id),
                rating=skill.rating,
                experience=skill.experience,
            })
        end
        return out
    end
  • Extract simple family links from unit.relationship_ids indexed by df.unit_relationship_type. Extract social attitudes from the unit's historical figure relationships.hf_visual and relationships.hf_historical, preserving relationship kind, target historical-figure ID, and paired counter as strength. Do not write these structures.
  • Expose posted jobs as JobSnapshot records with ID, enum name, and current worker ID. The bridge may traverse the current world job list read-only; it does not create or cancel jobs.
  • Implement observe dispatch and validate the Python result through WorldSnapshot.model_validate(response.result). Reject the response if one citizen has no current soul; do not fabricate personality defaults.
  • Run the live test against a fresh copy of agent_v0_clean. Copy the normalized response—not raw save data—to tests/fixtures/runtime/initial_snapshot.json. Add a pure fixture test that parses it with WorldSnapshot.
  • Run:
    uv run pytest tests/unit tests/integration/test_runtime_boundary.py -v
    uv run ruff check .
    Expected: pure tests pass; live test passes only with the live gate; Ruff exits 0.
  • Commit:
    git add dfhack/scripts/dwarf-society/snapshot.lua \
      src/dwarf_society/contracts.py src/dwarf_society/client.py \
      tests/integration/test_runtime_boundary.py tests/fixtures/runtime/initial_snapshot.json
    git commit -m "feat: expose canonical citizen snapshot"

Task 5 — Add bounded pause/advance and revision fencing

Consumes: health/observe bridge. Produces: client.advance(ticks, expected_revision), monotonic site revision, changed bridge epoch after restart, and stale-request rejection before mutation.

  • Add failing live tests: advancing 10 ticks begins paused, ends paused, increases the observed year tick, and increments revision exactly once; an old revision returns rejected/stale_revision; a wrong save ID returns rejected/wrong_save; and a wrong bridge epoch returns rejected/wrong_epoch. Start a longer advance and, while it is pending, assert health returns busy=true while observe and claim-job requests return rejected/bridge_busy without revision or world changes.
  • Add a pure client test proving mutating requests cannot be constructed without run_id, save_id, bridge_epoch, expected_revision, and command_id. Enforce this with a RequestEnvelope model validator for advance and claim_job.
  • Persist protocol state with dfhack.persistent.getSiteData('dwarf-society', {revision=0, receipts={}}) and saveSiteData after every successful mutating command. Save identity is dfhack.getSavePath() .. ':' .. dfhack.world.getCurrentSite().id.
  • Implement advance with two distinct clocks and one bridge-wide busy gate:
    local function advance(request, respond)
        validate_mutation_fence(request)
        if runtime.busy then
            return reject(request, 'bridge_busy', 'advance already pending')
        end
        local ticks = tonumber(request.payload.ticks)
        if not ticks or ticks < 1 or ticks > 1200 then
            return reject(request, 'invalid_request', 'ticks must be 1..1200')
        end
        if not dfhack.world.ReadPauseState() then
            return reject(request, 'precondition_failed', 'game must begin paused')
        end
        runtime.busy = {request_id=request.request_id, kind='advance'}
        dfhack.timeout(ticks, 'ticks', function()
            dfhack.world.SetPauseState(true)
            state.revision = state.revision + 1
            persist_state()
            respond(ok_receipt(request, {ticks=ticks, busy=false}))
            runtime.busy = nil
        end)
        dfhack.world.SetPauseState(false)
        return 'deferred'
    end
    The outer request poll remains scheduled every five frames and may not be replaced with a tick schedule. While runtime.busy is set, dispatch health with the pending request ID and reject every other request with bridge_busy before snapshot extraction or action preflight.
  • Keep the accepted advance request in processing until its tick callback re-pauses the game, increments and persists revision, writes and archives the response, and only then clears runtime.busy. On disable, map unload, or world unload, force pause, cancel the tick callback with dfhack.timeout_active, publish an internal_error response for the deferred request when the spool remains available, archive it, and clear the gate. Never leave a deferred request silently pending.
  • Run the live tests twice from clean save copies. Assert repeated health calls within one enable lifetime keep the same epoch; disable/re-enable and a DFHack process restart each produce a different epoch. Save/load once and assert persisted revision matches the saved checkpoint.
  • Commit:
    git add dfhack src/dwarf_society tests
    git commit -m "feat: add fenced pause-step clock"

Task 6 — Claim one existing legal job with durable idempotent receipts

Consumes: citizen/job snapshot and revision fence. Produces: client.claim_job(unit_id, job_id, command_id, expected_revision), strict preflight, one legal mutation, duplicate receipt replay, and post-action reconciliation.

  • In a fresh disposable save copy, use the normal DF UI to create one safe, reversible designation that produces an unclaimed job. Pause. Record its job ID from observe; do not construct a raw job in Lua.
  • Add failing live tests for: successful claim by an available citizen; second citizen rejected because the job is already claimed; uninterruptible/socially unavailable citizen rejected; unknown unit/job rejected; stale revision rejected; and exact duplicate command ID returning the first receipt without a second revision increment.
  • Create dfhack/scripts/dwarf-society/actions.lua with an allowlist table containing only claim_job. Preflight requires a current citizen returned by getCitizens, a current job with no worker, and dfhack.units.isJobAvailable(unit, true). The true preserves interruptible social activity rather than stealing the dwarf from it.
  • Apply through dfhack.job.addWorker(job, unit). Immediately read dfhack.job.getWorker(job); success requires the requested unit ID. Then increment revision, persist this exact receipt under command ID, and respond:
    {
      command_id = request.command_id,
      capability = 'claim_job',
      outcome = 'applied',
      unit_id = unit.id,
      job_id = job.id,
      before_worker_unit_id = -1,
      after_worker_unit_id = unit.id,
      revision_before = revision_before,
      revision_after = state.revision,
    }
  • If a known command ID arrives again, return the stored receipt with duplicate=true and do not re-run preflight or mutate revision. If the command ID is known but request payload differs, return rejected/precondition_failed with “command ID payload mismatch.” Store a canonical payload hash or canonical encoded payload beside each receipt.
  • After every applied receipt, call observe and assert the world snapshot shows the requested worker on the requested job. The Python client records attempted request, receipt, and reconciled snapshot under one causal record.
  • Save, exit, reload the same disposable fortress, enable the bridge, and deliver the same command ID. Expected: the persisted receipt returns as duplicate and the job is not claimed twice. Restore from agent_v0_clean after the test.
  • Commit normalized receipt fixtures for applied, duplicate, and each rejection class. Run all pure and live tests, then commit:
    git add dfhack src/dwarf_society tests
    git commit -m "feat: add idempotent legal job claim"

Task 7 — Verify private inference reachability and publish the boundary report

Consumes: complete bridge, fixtures, Mac oMLX ground truth. Produces: verified native-Windows private route, redacted self-contained report, focused full-suite result, and the evidence contract for the next implementation plan.

  • Add src/dwarf_society/config.py and tests/unit/test_config.py. Parse gitignored runtime/runtime.toml with standard tomllib. Required keys are df_root, spool_root, omlx_base_url, and inference_route constrained to tailscale or supervised_ssh_tunnel. Reject URLs whose host is neither loopback nor the recorded private Mac name/address.
  • From native PowerShell, test the direct private route first:
    Invoke-RestMethod -Method Get -TimeoutSec 5 `
      -Uri 'http://dakotas-mac-studio:8080/v1/models'
    If this fails, open a supervised Windows OpenSSH tunnel in a dedicated terminal:
    ssh.exe -NT -L 127.0.0.1:18080:127.0.0.1:8080 dakotas-mac-studio
    Then verify http://127.0.0.1:18080/v1/models. Record only route kind, status, latency, and model count—no private address or model inventory in the repo.
  • Add client.check_inference_route() using an HTTPX client with 5-second connect/read/write/pool timeouts, redirects disabled, and exactly one GET to /v1/models. No completion method exists in Proof 1.
  • Create src/dwarf_society/report.py and pure tests. The report consumes recorded health, initial snapshot, advance receipt, stale rejection, applied claim, duplicate receipt, reconciled snapshot, save/load receipt, and inference-route result. It emits runtime/<run-id>/runtime-boundary.json and a self-contained HTML rendering with PASS/FAIL for each §06 criterion.
  • Create src/dwarf_society/cli.py commands health, observe, advance, claim-job, check-inference, and report-runtime-boundary. Every mutating command requires an explicit --expected-revision and prints the receipt; no command accepts arbitrary Lua or DFHack text.
  • Run the complete focused verification from a fresh save copy:
    uv run pytest -m "not live" -v
    $env:DWARF_SOCIETY_LIVE = '1'
    uv run pytest -m live tests/integration/test_runtime_boundary.py -v
    uv run ruff check .
    uv run python -m dwarf_society.cli report-runtime-boundary
    Expected: all pure and live tests pass; Ruff exits 0; report marks every exit criterion PASS.
  • Only after the working smoke succeeds, add the cleanup documentation: a concise README.md with exact install/enable/test/report commands, DF/DFHack version lock, disposable-save warning, frame-vs-tick invariant, capability manifest, and link to the approved design. Do not document actor/governance features as implemented.
  • Commit:
    git add README.md src/dwarf_society tests pyproject.toml uv.lock
    git commit -m "docs: record runtime boundary proof"

§06 · Exit gate

Conditions for planning the one-dwarf proof

All conditions are mandatory. A bridge that can read but not safely mutate is not a completed runtime boundary.

CriterionEvidence
Exact compatibilityRecorded DF 53.15 + DFHack 53.15-r2; disposable save only.
Paused-safe transportHealth and observe requests complete while paused; source shows a frame-based poll.
Canonical citizensStrict fixture parses exactly seven unique citizens with identity, position, work, traits, values, needs, skills, stress, and relationships.
Bounded clockAdvance begins and ends paused, advances the requested bounded ticks, increments revision once, exposes busy health, and rejects all non-health requests while the world is advancing.
FencingWrong save, wrong epoch, and stale revision reject before mutation.
Legal actionOne existing game job is assigned to one available citizen via DFHack job APIs; fresh observation confirms it.
IdempotencyExact duplicate command returns the durable first receipt across save/load and does not increment revision or reapply.
Private inference routeNative Windows sidecar reaches Mac oMLX /v1/models over a verified private route or supervised loopback tunnel.
RecoverabilityEvery live test begins from a clean copied save; existing personal saves remain untouched; bridge restart changes epoch and reconciles safely.
EvidenceFocused tests and Ruff pass; report contains no private address, model inventory, personal save name, secret, or raw game binary.
Stop and revise If a required canonical field or safe job claim is absent in DFHack Lua 53.15-r2, record the missing contract with a minimal reproduction. Add only the narrow native extension needed for that field or action, then rerun this same gate. Do not jump to screen scraping, arbitrary command execution, teleportation, or a fortress-level agent.

§07 · Plan self-review

Coverage and consistency check