DEV Community

Taylor Zhu
Taylor Zhu

Posted on

Stamp Every Write or Don't Share the Host: A Fail-Closed Attribution Checklist

If you cannot name the principal, the tool, and the rollback owner for each write, do not share the agent host. A single-owner lab can be loose. A shared writer cannot.

Free model access helps you draft the change. A free server gives you a place to rehearse it. Neither one draws a tenancy line. You draw that line with gates, stored evidence, and a fail-closed default.

Share is a privilege, not a side effect

You can boot an agent service in an afternoon. That speed hides the boundary. While you are the only caller, a bad write stays local, and you are the person who notices it.

Add a second workflow and the same process becomes a shared runtime. Two jobs now share a credential, a working directory, and a log stream. A changed row no longer points at anyone. A vanished route takes both jobs down, and the record still has no owner.

Stop before that second writer is enabled. Isolation is the release gate. Missing fields are a stop, not a warning you park for later.

Six gates you can copy

A gate passes only when the evidence under it sits in the run record. Empty, null, or unverifiable means fail closed. The host stays single-tenant until every gate for that tenant is true.

Gate 1: Bind a principal

Every mutating call carries principal_id, workflow_id, and purpose. A shared API key is not an identity. If two jobs present the same key, you have not separated them.

File the request log line and the inventory row that maps that key to exactly one workflow. No inventory row, no share.

Gate 2: Deny tools by default

The host may call only tools on that workflow's allowlist. A new tool is a reviewed config change. A prompt that claims it needs a tool is not approval.

File the allowlist and its SHA-256 beside the run id. Start from a file like this, then replace the names with your own:

# Proposed allowlist. Not a live policy until you load it.
workflows:
  invoice-draft-v3:
    principal_id: team-billing/bot-invoices
    deny_by_default: true
    tools:
      - name: draft_invoice
        mutating: true
        rollback: revert_invoice_draft
Enter fullscreen mode Exit fullscreen mode

Gate 3: Prove rollback on a fixture

Each write record holds resource_id, a before-image reference, and a rollback command or a compensating action. If you cannot reverse the write on a fixture, the tool stays disabled for that tenant.

File the command output from a rollback you actually ran. A comment in the ticket does not count. A fixture that only prints "would revert" is not enough unless your real runner uses that same command.

Gate 4: Keep logs from mixing

Every log line includes tenant_id. A query for tenant A must return zero lines for tenant B. Strip secrets and prompt bodies that contain credentials before the line leaves the host.

File a fixture result that shows the cross-tenant count is zero, plus the exact command. This probe is a proposal. Point it at your real log layout:

grep -c 'tenant_id=tenant-b' logs/tenant-a.log
Enter fullscreen mode Exit fullscreen mode

A count other than 0 fails the gate. Do not explain the hit away in chat. Fix the split, then run the probe again.

Gate 5: Lose the free path, stop the writes

Free model access and a free server can throttle, change, or disappear. You do not control that window. Do not hard-wire a shared writer to a route you cannot replace. If the configured route or host identity is missing, refuse the mutation.

File durable_route_accepted only after a named owner reads the provider's current terms and records a fallback. Do not invent a quota, a box size, or an expiry date. If the durable route is not accepted, shared_writer_enabled stays false.

Gate 6: Cut the wire in one command

You need one switch that disables every mutating tool on that host. Practice it on a lab target. An untested switch is a paragraph, not a control.

File timestamped output of the disable command, then a follow-up call that shows the write was rejected. A local drill can be this small:

mkdir -p ./lab/run
touch ./lab/run/writes.disabled
test -f ./lab/run/writes.disabled && date -u +%Y-%m-%dT%H:%M:%SZ
Enter fullscreen mode Exit fullscreen mode

Your real runner must check that flag, or an equivalent switch, before any mutate tool runs. The file alone does nothing.

How to read the results

Do not average the gates. One miss blocks the share.

  • Any required field missing or empty: do not share the host.
  • Fields present, but shared_writer_enabled is false: do not share the host.
  • Kill switch not tested in this change window: do not share the host.
  • All marks true, owner named, raw logs read: you may enable that one tenant. You may not silently add another.

A checker you can run before the second writer

This script is a proposed release gate. It does not call a model, and it does not open a socket. It reads a JSON evidence file and exits non-zero unless every required mark is present. Treat it as unexecuted until you run it on your files.

#!/usr/bin/env python3
"""Proposed fail-closed tenancy checker. Not a runtime sandbox."""
import json
import sys

REQUIRED = (
    "principal_id",
    "workflow_id",
    "tool_allowlist_sha256",
    "rollback_dry_run_ok",
    "log_isolation_ok",
    "durable_route_accepted",
    "kill_switch_tested_at",
)

def fail(msg: str) -> int:
    print("FAIL closed.", msg)
    return 1

def main(path: str) -> int:
    with open(path, encoding="utf-8") as handle:
        evidence = json.load(handle)
    missing = [key for key in REQUIRED if not evidence.get(key)]
    if missing:
        return fail("missing or empty: " + ", ".join(missing))
    if evidence.get("shared_writer_enabled") is not True:
        return fail("shared_writer_enabled is not true")
    print("PASS. Fields are present. Read the raw artifacts before you share.")
    return 0

if __name__ == "__main__":
    if len(sys.argv) != 2:
        print("usage: python3 tenancy_gate.py evidence.json")
        raise SystemExit(2)
    raise SystemExit(main(sys.argv[1]))
Enter fullscreen mode Exit fullscreen mode

Save a starter record that should fail. The timestamp is a placeholder, not a drill log.

{
  "principal_id": "team-billing/bot-invoices",
  "workflow_id": "invoice-draft-v3",
  "tool_allowlist_sha256": "replace-with-real-hash",
  "rollback_dry_run_ok": true,
  "log_isolation_ok": true,
  "durable_route_accepted": false,
  "kill_switch_tested_at": "2026-10-08T00:00:00Z",
  "shared_writer_enabled": false
}
Enter fullscreen mode Exit fullscreen mode
python3 tenancy_gate.py evidence.json
echo "exit=$?"
sha256sum allowlist.json
Enter fullscreen mode Exit fullscreen mode

You want a non-zero exit on that file. false is a failing value because the checker treats it as empty. That is deliberate. Flip a mark only after the artifact exists, paste the real hash, then run the checker again.

A pass means the record is complete. It does not mean the host is safe. Go read the log probe and the kill-switch output before you change your mind.

Use a free lab, then leave the share off

Disclosure: This article was prepared as part of MonkeyCode's product outreach.

MonkeyCode's free model access is enough to draft allowlist notes, rollback steps, and fixture prompts. The free server option is enough to host the single-owner drill: run the checker, run the kill switch, and keep the second workflow disconnected. That is the slice that matches this method. The gates still work if you use another lab.

Do not paste production secrets, customer records, or live tokens into that draft path. A lab model is still a copy destination. Use synthetic fixtures.

Do not treat the offer as a capacity plan. This article names no model, no token quota, no hardware size, no duration, and no benchmark.

Those details change, and they are not assumed here. Read the current terms, write what you read next to durable_route_accepted, and date the note. If you cannot replace the route, leave the share off.

If you already have that free server, use it for this drill and keep shared_writer_enabled false until a named owner accepts the evidence. Do the drill on synthetic data. Stop when the checker fails.

Limits to say out loud

The checker inspects a file. It does not watch the process. A complete JSON record can still sit beside a tool that ignores the allowlist, a document that hides a write, or a pipeline that drops tenant_id after the test.

Enforcement belongs in the tool runner. This script only blocks a release that has no record.

It also does not recompute the hash. A non-empty string passes the field check. Compare sha256sum output in review, or extend the script before you call it strict.

There is no measured miss rate here. Do not invent one for a status slide.

Clocks skew. Keep the kill-switch command output, not only kill_switch_tested_at. A fresh-looking string with no log is still a fail.

Who should skip this

Skip it if you have one person, one workflow, and no mutating tools. A notebook is not a tenant problem. You would be adding ceremony without a side effect to contain.

Skip it if untrusted customers share the process. A JSON gate is not a security boundary. You need separate credentials, separate network policy, and a real tenant model. Use this list only to stop yourself from calling the lab that model.

Skip it if nobody will store the evidence next to the change. An unfiled checklist is theater. You are safer with the second writer left disabled.

After the exit code

Keep the host single-writer until the file passes and a person has read the raw logs. Then enable one tenant. Add the next tenant only with its own principal, its own allowlist hash, and its own rollback drill.

No second writer without a stamp. That is the promotion rule.

Top comments (0)