# DOS workspace config -- fleet's own governance, dogfooded.
#
# fak IS an OS-for-agents (a "DOS"); this file turns the SAME kernel on the repo
# that builds it. Until now `dos doctor` saw fleet through trivial defaults: one
# `**/*` lane (so `dos arbitrate` treated the whole repo as a single tree -> max
# concurrency 1), a plans glob that matched none of fleet's plans, and no fleet
# ship grammar. This file replaces those with fleet's real shape, so the DOS loops
# (arbitrate / verify / next-up / self-improve / goal-gate) operate on the truth
# instead of a stub. The mechanism stays in the installed `dos` package; this is
# the only policy that crosses into the tree.
#
# SAFETY (this is a LIVE multi-session fleet): every table only makes DOS smarter
# about fleet -- real lanes, fleet's ship anchor, fleet's own invariants as refusal
# vocabulary. None of it introduces a spontaneous refusal: the custom [reasons.*]
# are DATA that fire only at the opt-in `dos apply` surface / their named floor.
# Validated with `dos doctor`, `dos verify` (recognizes a `(fak <leaf>)` trailer / a
# `fak/<leaf>:` ship within dos's 50-commit grep window -- re-run for the live witness
# sha, which drifts as the window advances), and `dos arbitrate` (two disjoint leaf
# lanes co-acquire) before commit.
workspace = "."

# Lane taxonomy -- a concurrent lane may run iff its file-tree is disjoint from
# every live lease; an exclusive lane runs alone. fak's architecture is a STAR of
# disjoint leaf trees (PARTITION.md: "dos-arbitrate takes one lease per tree"), and
# the arbiter keys concurrency on the LANE: two workers wanting the same named lane
# serialize even on disjoint sub-trees. So the honest partition is ONE LANE PER LEAF
# (matching the dos-kernel repo's own one-lane-per-dir convention) -- that is what
# lets the live fleet edit N leaves in parallel (e.g. a model-peer and an architest
# worker at once). The trees are prefix-based and pairwise disjoint by construction.
# `internal/abi` is the frozen, human-owned spine (PARTITION.md "wave-0, unleasable")
# -> EXCLUSIVE: an ABI change is the serial gate that every leaf imports.
[lanes]
concurrent = [
  "adjudicator", "agent", "agentdojo", "architest", "bench", "blob", "canon", "compute",
  "cachemeta",
  "cdb", "ctxmmu", "engine", "gateway", "ggufload", "grammar", "harvest", "ifc", "kernel",
  "kvmmu", "metrics", "model", "normgate", "plancfi", "policy", "preflight",
  "provenance", "recall", "registrations", "shipgate", "steward", "turnbench",
  "vdso", "witness",
  "cmd", "experiments", "docs", "tools", "visuals", "claude", "ci",
  "tokenizer",
  "contextq", "journal", "examples",
  "spec", "rulesynth", "wirescreen",
  "appversion", "boundarylint", "deletioncert", "demoui", "enginecache", "gpulease",
  "leakcheck", "metalgemm", "modelengine", "pathlint", "pathutil", "radixkv",
  "ratelimit", "rsiloop", "swebench", "toollint", "tracesink", "urllint", "webbench",
  "answershape",
  "codelint",
  "headroom",
  "residency",
  "ctxresidency",
  # reconciled 2026-06-23: real committed internal/ leaves that had NO lane, so a
  # `(fak <leaf>)` ship-stamp bound to a phantom unit and the arbiter could not detect a
  # same-tree collision on them (the same class the 2026-06-22 reconciliation above fixed).
  "a2achan",
  "rungobs",
  "vcachechain",
  "dropin",
  "comm",
  "cohort",
  "agenttopo",
  "promptmmu",
  "vcacheobserve",
  "loopdrive",
  "devindex",
  "operatorbrief",
  # They landed by hand / during the new_leaf.py path-bug window, so the `# new-leaf:lane`
  # auto-insert never fired. All are real, architest-tiered packages.
  "blobfs", "blobhttp", "ctxplan", "dgxbridge", "gitgate", "hfhub", "memq", "polymodel", "storedrv",
  "advmodel",
  "modelroute",
  # reconciled 2026-06-27 (#1029): internal/modelreg is a real committed leaf (the
  # friendly-name -> model-ref registry behind `fak run ornith:9b` / `fak serve --gguf`)
  # that had NO declared lane -> a `(fak modelreg)` ship-stamp bound but the arbiter could
  # not detect a same-tree collision on it. (Two sessions raced internal/modelreg/modelreg.go
  # editing the Ornith #1029 aliases with no serialization precisely because the lane was
  # undeclared.) Declared here so the referee binds the stamp and the arbiter protects
  # internal/modelreg/** edits. Tree = internal/modelreg/** only; its cmd/fak shim stays
  # in the `cmd` lane, keeping every leaf tree pairwise-disjoint per the header invariant.
  "modelreg",
  # reconciled 2026-06-28 (dos lint --strict CONCURRENT_LANES_OVERLAP): a2achan, rungobs,
  # vcachechain, dropin, comm, cohort, agenttopo, promptmmu and vcacheobserve were each listed
  # TWICE in this concurrent roster (once in the 2026-06-23 block above, once interleaved here)
  # -- a self-overlap that made the arbiter's roster spawn-order-sensitive for those lanes. The
  # duplicate second occurrences are removed; each lane stays declared exactly once above.
  "sessionimage",
  "snapshot",
  "sharedtask",
  "vcachegov", # vCache M5 Governor (#720): steady-state pin/lazy/evict + warm-budget + affinity policy over cachemeta
  "vcachewarm",
  "taskmgr",
  # reconciled 2026-06-25 (#756, Pillar 2 / #748): internal/session is a real committed
  # leaf (the per-session DRIVE state) that had NO declared lane; declared here per #756's
  # fence so a `(fak session)` ship-stamp binds and the arbiter detects same-tree collisions.
  "session",
  # reconciled 2026-06-26 (#763, the `fak cron` sub-epic #749 / epic #748): internal/loopmgr
  # is a real committed leaf (the loop event ledger + governor + recurring-job schedule) that
  # had NO declared lane -> it folded into `global`, so a `(fak loopmgr)` ship-stamp could not
  # bind and the arbiter could not detect a same-tree collision. Declared per #763's fence.
  "loopmgr",
  "fakrpc",
  # reconciled 2026-06-26 (#825): internal/leaseref is a real committed leaf (the
  # cross-machine lease VISIBILITY substrate, refs/fak/locks/*) that landed by hand in
  # f0cf3f6 with NO declared lane -> a `(fak leaseref)` ship-stamp bound to a phantom unit
  # and the arbiter could not detect a same-tree collision on it. Declared here so the
  # referee binds the stamp and the arbiter protects internal/leaseref/** edits.
  "leaseref",
  "windowgate", # internal/windowgate leaf (the no-desktop-popup gate); declared so the referee binds a `(fak windowgate)` stamp and the arbiter protects internal/windowgate/** edits.
  "worktype",   # internal/worktype leaf (the work-class taxonomy: ongoing PROGRAM vs DISCRETE epic) the milestone roadmap + `fak program` report sort by; declared so the referee binds `(fak worktype)` and the arbiter protects internal/worktype/** edits.
  "programreport", # internal/programreport leaf (the ongoing-program report: kernel-opt + cache-opt frontier+trend, sibling of milestonereport/cadencereport); declared so the referee binds `(fak programreport)` and the arbiter protects internal/programreport/** edits.
  # reconciled 2026-06-27 (#1000): internal/scoreboard is a real committed leaf (the `fak
  # scoreboard` Slack scorecard publisher, shipped 52ed934b with the `(fak scoreboard)`
  # trailer) that had NO declared lane -> a `(fak scoreboard)` ship-stamp bound to a phantom
  # unit and `fak commit --preview` warned the arbiter could not detect a same-tree collision
  # on it. Declared here (tree = internal/scoreboard/** only; its cmd/fak/scoreboard.go shim
  # stays in the `cmd` lane, keeping every leaf tree pairwise-disjoint as the header invariant
  # requires) so the referee binds the stamp and the arbiter protects internal/scoreboard/** edits.
  "scoreboard",
  # reconciled 2026-06-27 (#1070): internal/ailuminate is a new committed leaf (the
  # MLCommons-AILuminate benchmark-entry scoping/go-no-go contract). Declared so a
  # `(fak ailuminate)` ship-stamp binds and the arbiter detects a same-tree collision
  # on internal/ailuminate/** edits; its cmd/fak/ailuminate.go shim stays in the `cmd`
  # lane, keeping every leaf tree pairwise-disjoint per the header invariant.
  "ailuminate",
  # reconciled 2026-06-28: internal/marketing is a new leaf, code not yet committed (the completion-driven
  # marketing subsystem — witnessed-ship -> artifact, the AEO/AgentEO refresh, the auto loop).
  # Declared so a `(fak marketing)` ship-stamp binds and the arbiter detects a same-tree
  # collision on internal/marketing/** edits; any cmd/fak shim will live in the `cmd`
  # lane, keeping every leaf tree pairwise-disjoint per the header invariant.
  "marketing",
  # reconciled 2026-06-28 (dos lint --strict AUTOPICK_LANE_UNDECLARED): internal/dojo (#951) and
  # internal/nightrun are real committed leaves already in [lanes].autopick and [lanes.trees], but
  # were absent from this concurrent roster -- so the autopick walk SILENTLY SKIPPED them and a
  # `(fak dojo)` / `(fak nightrun)` ship-stamp could not bind to a concurrent lane. Declared here
  # so the walk reaches them and the arbiter protects internal/dojo/** & internal/nightrun/** edits.
  "dojo",
  "nightrun",
  # reconciled 2026-06-29 (dos lint CONCURRENT_LANES_OVERLAP 'loopdrive+loopdrive'): loopdrive
  # was re-added here with the 2026-06-28 dojo/nightrun batch but is ALREADY declared once in
  # the 2026-06-23 block above -- a self-overlap that made the roster spawn-order-sensitive for
  # loopdrive. The duplicate second occurrence is removed; loopdrive stays declared once above.
  "loopgate",
  "dispatchorder",
  "devindex",
  "learningdebt",
  "opttarget",
  "guardcomplaint", # the agent APPEAL channel (`fak complain`): the subjective complement of guardroute; tree = internal/guardcomplaint/** only (its cmd/fak/complain.go shim stays in the `cmd` lane, keeping every leaf tree pairwise-disjoint per the header invariant)
  "maturity",
  "loopindex", # #1152: internal/loopindex leaf (the agentic-coding loop-index spine); cmd/fak/loopindex.go shim stays in the `cmd` lane to keep leaf trees pairwise-disjoint
  "codexmcphealth",
  "operatorbrief",
  # new-leaf:lane -- `python tools/new_leaf.py` inserts a generated leaf lane above
]
# `dos.toml` edits change the lane/refusal taxonomy that dispatchers rely on, so
# they serialize like release/ABI/global work instead of running as ordinary docs.
exclusive = ["abi", "release", "dos", "global"]
autopick = [
  "adjudicator", "agent", "agentdojo", "architest", "bench", "blob", "canon", "compute",
  "cachemeta",
  "cdb", "ctxmmu", "engine", "gateway", "ggufload", "grammar", "harvest", "ifc", "kernel",
  "kvmmu", "metrics", "model", "normgate", "plancfi", "policy", "preflight",
  "provenance", "recall", "registrations", "shipgate", "steward", "turnbench",
  "vdso", "witness",
  "cmd", "experiments", "docs", "tools", "visuals", "claude", "ci",
  "tokenizer",
  "contextq", "journal", "examples",
  "spec", "rulesynth", "wirescreen",
  "appversion", "boundarylint", "deletioncert", "demoui", "enginecache", "gpulease",
  "leakcheck", "metalgemm", "modelengine", "pathlint", "pathutil", "radixkv",
  "ratelimit", "rsiloop", "swebench", "toollint", "tracesink", "urllint", "webbench",
  "answershape",
  "codelint",
  "headroom",
  "residency",
  "ctxresidency",
  "blobfs", "blobhttp", "ctxplan", "dgxbridge", "gitgate", "hfhub", "memq", "polymodel", "storedrv",
  "advmodel",
  "modelroute",
  "modelreg", # #1029: internal/modelreg leaf (the run-by-name model registry), see [lanes].concurrent note
  "sessionimage",
  "a2achan",
  "snapshot",
  "rungobs",
  "sharedtask",
  "vcachegov",
  "vcachechain",
  "vcachewarm",
  "taskmgr",
  "dropin",
  "comm",
  "cohort",
  "agenttopo",
  "promptmmu",
  "session", # #756: internal/session leaf, see [lanes].concurrent note
  "loopmgr", # #763: internal/loopmgr leaf, see [lanes].concurrent note
  "fakrpc",
  "vcacheobserve",
  "leaseref", # #825: internal/leaseref leaf, see [lanes].concurrent note
  "dojo", # #951: internal/dojo leaf (the prediction-vs-reality gym), see [lanes].concurrent note
  "windowgate", # internal/windowgate leaf (the no-desktop-popup gate), see [lanes].concurrent note
  "worktype", # internal/worktype leaf (the work-class taxonomy), see [lanes].concurrent note
  "programreport", # internal/programreport leaf (the ongoing-program report), see [lanes].concurrent note
  "nightrun", # internal/nightrun leaf (the run-it-all-night data-collection door), see [lanes].concurrent note
  "scoreboard", # #1000: internal/scoreboard leaf (the fak scoreboard Slack publisher), see [lanes].concurrent note
  "ailuminate", # #1070: internal/ailuminate leaf (the AILuminate benchmark-entry scoping contract), see [lanes].concurrent note
  "marketing", # internal/marketing leaf (completion-driven marketing subsystem), see [lanes].concurrent note
  "loopdrive",
  "loopgate",
  "dispatchorder",
  "devindex",
  "learningdebt",
  "opttarget",
  "guardcomplaint", # agent APPEAL channel (`fak complain`); see [lanes].concurrent note
  "maturity", # internal/maturity leaf (the feature-maturity lifecycle scorecard), see [lanes].concurrent note
  "loopindex", # #1152: internal/loopindex leaf (the agentic-coding loop-index spine), see [lanes].concurrent note
  "codexmcphealth",
  "operatorbrief",
  # new-leaf:lane
]

# NOTE (2026-06-22, dos-effective-usage audit): these globs were prefixed
# `fak/internal/...` / `fak/cmd/...`, but the Go module is the REPOSITORY ROOT
# (AGENTS.md) -- the real tree is `internal/...` and `cmd/...` with NO `fak/`
# segment. So every glob below matched ZERO files, and the arbiter's disjointness
# rung was running on phantom trees: a worker editing the REAL path
# internal/gateway/server.go was ADMITTED against a peer holding the gateway lane
# (proven: `dos arbitrate --lane gateway --tree internal/gateway/server.go
# --leases '[{"lane":"x","tree":["fak/internal/gateway/**"]}]'` -> acquire; the
# same with the corrected `internal/gateway/**` lease -> refuse). The prefix is
# stripped to the real layout, and the leaves that existed in internal/ but had no
# lane are added, so arbitrate now actually detects same-tree collisions.
[lanes.trees]
adjudicator = ["internal/adjudicator/**"]
agent = ["internal/agent/**"]
agentdojo = ["internal/agentdojo/**"]
architest = ["internal/architest/**"]
windowgate = ["internal/windowgate/**"]
worktype = ["internal/worktype/**"]
programreport = ["internal/programreport/**"]
nightrun = ["internal/nightrun/**"]
bench = ["internal/bench/**"]
blob = ["internal/blob/**"]
canon = ["internal/canon/**"]
cachemeta = ["internal/cachemeta/**"]
capindex = ["internal/capindex/**"]
capindexgw = ["internal/capindexgw/**"]
compute = ["internal/compute/**"]
cdb = ["internal/cdb/**"]
ctxmmu = ["internal/ctxmmu/**"]
engine = ["internal/engine/**"]
gateway = ["internal/gateway/**"]
ggufload = ["internal/ggufload/**"]
grammar = ["internal/grammar/**"]
harvest = ["internal/harvest/**"]
ifc = ["internal/ifc/**"]
kernel = ["internal/kernel/**"]
kvmmu = ["internal/kvmmu/**"]
metrics = ["internal/metrics/**"]
model = ["internal/model/**"]
normgate = ["internal/normgate/**"]
plancfi = ["internal/plancfi/**"]
policy = ["internal/policy/**"]
preflight = ["internal/preflight/**"]
provenance = ["internal/provenance/**"]
recall = ["internal/recall/**"]
registrations = ["internal/registrations/**"]
shipgate = ["internal/shipgate/**"]
steward = ["internal/steward/**"]
turnbench = ["internal/turnbench/**"]
vdso = ["internal/vdso/**"]
witness = ["internal/witness/**"]
tokenizer = ["internal/tokenizer/**"]
answershape = ["internal/answershape/**"]
contextq = ["internal/contextq/**"]
journal = ["internal/journal/**"]
# reconciled 2026-06-23 (#517): real internal/ packages (internal/spec, internal/rulesynth,
# internal/wirescreen) whose `(fak <leaf>)` ship-stamps the commit_stamp_doctor coverage
# meter flagged as off-lane -- declared here so the referee can bind them (the "declare
# per-leaf lanes" resolution of #517's drift #4; non-package scopes like the `guard` verb
# stay advisory). internal/wirescreen shipped (b63264c, `feat(wirescreen): ... (fak
# wirescreen)`) after the first #517 pass, so it surfaced as the one remaining real
# off-lane package; declaring its lane lets the referee bind its ship-stamp and lets
# the arbiter detect a same-tree collision on it. (Its architest tier-map entry is a
# separate internal/architest concern, tracked outside this dos.toml lane.)
spec = ["internal/spec/**"]
rulesynth = ["internal/rulesynth/**"]
wirescreen = ["internal/wirescreen/**"]
# reconciled 2026-06-22: real internal/ leaves that previously had NO lane (so a
# `(fak <leaf>)` ship-stamp bound to a phantom unit and edits were unprotected).
appversion = ["internal/appversion/**"]
boundarylint = ["internal/boundarylint/**"]
deletioncert = ["internal/deletioncert/**"]
demoui = ["internal/demoui/**"]
enginecache = ["internal/enginecache/**"]
gpulease = ["internal/gpulease/**"]
leakcheck = ["internal/leakcheck/**"]
metalgemm = ["internal/metalgemm/**"]
modelengine = ["internal/modelengine/**"]
pathlint = ["internal/pathlint/**"]
pathutil = ["internal/pathutil/**"]
radixkv = ["internal/radixkv/**"]
ratelimit = ["internal/ratelimit/**"]
rsiloop = ["internal/rsiloop/**"]
swebench = ["internal/swebench/**"]
toollint = ["internal/toollint/**"]
tracesink = ["internal/tracesink/**"]
urllint = ["internal/urllint/**"]
webbench = ["internal/webbench/**"]
ailuminate = ["internal/ailuminate/**"]
codelint = ["internal/codelint/**"]
headroom = ["internal/headroom/**"]
residency = ["internal/residency/**"]
ctxresidency = ["internal/ctxresidency/**"]
# reconciled 2026-06-23: nine real committed internal/ leaves that had no lane (see the
# matching note in [lanes].concurrent). Tiers per internal/architest: blobfs/blobhttp/
# dgxbridge/hfhub/polymodel/ctxplan = tier 1, gitgate/storedrv = tier 2, memq = tier 3.
blobfs = ["internal/blobfs/**"]
blobhttp = ["internal/blobhttp/**"]
ctxplan = ["internal/ctxplan/**"]
dgxbridge = ["internal/dgxbridge/**"]
gitgate = ["internal/gitgate/**"]
hfhub = ["internal/hfhub/**"]
memq = ["internal/memq/**"]
polymodel = ["internal/polymodel/**"]
storedrv = ["internal/storedrv/**"]
advmodel = ["internal/advmodel/**"]
modelroute = ["internal/modelroute/**"]
# reconciled 2026-06-28 (dos lint --strict LANE_WITHOUT_TREE): #1029 added the modelreg lane to
# [lanes].concurrent/autopick but never declared its tree here, so the lane could NOT be arbitrated
# (the disjointness algebra had no region to compare) -- the very same-tree collision protection
# #1029 set out to add was inert. Tree = internal/modelreg/** only; its cmd/fak shim stays in `cmd`.
modelreg = ["internal/modelreg/**"]
maturity = ["internal/maturity/**"]
sessionimage = ["internal/sessionimage/**"]
a2achan = ["internal/a2achan/**"]
snapshot = ["internal/snapshot/**"]
rungobs = ["internal/rungobs/**"]
sharedtask = ["internal/sharedtask/**"]
vcachegov = ["internal/vcachegov/**"]
vcachechain = ["internal/vcachechain/**"]
vcachewarm = ["internal/vcachewarm/**"]
taskmgr = ["internal/taskmgr/**"]
dropin = ["internal/dropin/**"]
comm = ["internal/comm/**"]
cohort = ["internal/cohort/**"]
agenttopo = ["internal/agenttopo/**"]
promptmmu = ["internal/promptmmu/**"]
session = ["internal/session/**"] # #756 (Pillar 2 / #748): per-session DRIVE state + cost ring
loopmgr = ["internal/loopmgr/**"] # #763 (#749 `fak cron` / #748): loop ledger + governor + recurring-job schedule
fakrpc = ["internal/fakrpc/**"]
vcacheobserve = ["internal/vcacheobserve/**"]
leaseref = ["internal/leaseref/**"] # #825: cross-machine lease VISIBILITY substrate (refs/fak/locks/*)
dojo = ["internal/dojo/**"] # #951: prediction-vs-reality gym (predict -> run -> measure -> eval -> calibrate)
scoreboard = ["internal/scoreboard/**"] # #1000: fak scoreboard Slack scorecard publisher (cmd/fak/scoreboard.go shim stays in the `cmd` lane to keep leaf trees pairwise-disjoint)
marketing = ["internal/marketing/**"] # completion-driven marketing subsystem (any cmd/fak shim lives in the `cmd` lane to keep leaf trees pairwise-disjoint)
loopdrive = ["internal/loopdrive/**"]
loopgate = ["internal/loopgate/**"]
dispatchorder = ["internal/dispatchorder/**"]
devindex = ["internal/devindex/**"]
learningdebt = ["internal/learningdebt/**"]
opttarget = ["internal/opttarget/**"]
guardcomplaint = ["internal/guardcomplaint/**"] # agent APPEAL channel (`fak complain`); cmd/fak/complain.go shim stays in the `cmd` lane to keep leaf trees pairwise-disjoint
loopindex = ["internal/loopindex/**"] # #1152: agentic-coding loop-index spine (cmd/fak/loopindex.go shim stays in the `cmd` lane to keep leaf trees pairwise-disjoint)
codexmcphealth = ["internal/codexmcphealth/**"]
operatorbrief = ["internal/operatorbrief/**"]
# new-leaf:tree -- generated leaf trees inserted above this line
cmd         = ["cmd/**"]
experiments = ["experiments/**"]
examples    = ["examples/**"]
# Root docs/index files are part of the docs lane too. Without these exact
# entries, `fak sweep` can only surface README/llms churn as no-lane residuals,
# so the shared tree never fully drains through `fak sweep --apply --lane docs`.
docs        = ["docs/**", "README.md", "INDEX.md", "llms.txt", "llms-full.txt", "llms-updates.txt"]
tools       = ["tools/**", "scripts/**"]
visuals     = ["visuals/**"]
claude      = [".claude/**"]
ci          = [".github/**"]
abi         = ["internal/abi/**"]
release     = ["VERSION", "docs/releases/**"]
dos         = ["dos.toml", ".gitignore"]
global      = ["**/*"]

# Where fleet keeps the state DOS reads. fak/ carries no version constant; the bare
# `VERSION` file at the root is the single marker (.claude/project.yaml).
#
# Plan surface (2026-06-22, #517): the old `plans_glob = "PLAN-*.md"` matched ZERO
# files -- there is no `PLAN-*.md` (nor `BUILD-*.md`) at the repo root -- so every
# plan-facing surface (`dos next-up`, `dos replan`, the class-cycle/lifecycle skills)
# resolved a phantom empty set while the config advertised a plan surface that does
# not exist. The real plan-shaped docs live under `docs/**/*-plan.md` (e.g.
# docs/serving/dual-track-serving-plan.md, docs/explainers/video-content-plan.md),
# which is also dos 0.28.0's built-in default glob -- so this points the key at the
# surface that actually exists, and `dos next-up` / `dos replan` now resolve a
# NON-EMPTY set. There is still no `[lifecycle]` table (no in-tree lifecycle phase
# machinery runs), so the lifecycle path stays intentionally inert; add one only when
# a real lifecycle surface lands.
[paths]
plans_glob = "docs/**/*-plan.md"

# How fleet stamps a shipped phase in commit subjects. Releases use the UNIVERSAL
# "vX.Y.Z:" anchor which dos correctly classifies as a release-bump (not a phase
# ship); per-leaf ships read "fak/<leaf>: ...". subject_dirs = [] keeps the generic
# dir-free shape so `verify` never MISSES a fleet ship by over-requiring a prefix --
# confirmed: `dos verify fak <leaf>` -> SHIPPED (source=grep-subject) for any leaf
# shipped within dos's 50-commit grep window -- e.g. `dos verify fak tools` -> SHIPPED
# ce2c2f3 (2026-06-21). A witness sha frozen in this comment necessarily goes stale once
# its ship scrolls out of the window (which is why an older pinned sha here drifts dead);
# re-run the command for the live witness rather than trusting a sha pinned in prose.
#
# trailer_stamp = true (docs/289): ALSO bind the Conventional-Commits subject the
# fleet actually writes -- `feat(scope): ... (fak <leaf>)` / `fix(scope): ...
# (fak <leaf>)`. Fleet has standardized on `type(scope):` subjects (32/50 of recent
# commits; 0 use the legacy `fak/<leaf>:` start-prefix), so without this `dos verify`
# / the goal-gate referee bind NOTHING in the fresh window -- `dos doctor` reported
# "none of your last 50 commits name a unit of work" (DOS-INSTALL-AUDIT-2026-06-19).
# This recognizes a `(fak <leaf>)` trailer at the END of the subject as a direct ship
# of leaf <leaf>; it is purely ADDITIVE (the legacy `fak/<leaf>:` start-form still
# binds via the direct rung) and introduces NO spontaneous refusal. The trailer is
# exactly as forgeable as the start-prefix, so the rung grades it `grep-subject` the
# same way. Verified in-process against the real oracle: `fix(gateway): ... (fak
# gateway)` -> SHIPPED via trailer (47c010c); a bare `fix(gateway): ...` with no
# trailer stays NOT_SHIPPED (an un-stamped commit is not a forgeable ship). The
# commit-side half of this convention lives in CLAUDE.md / AGENTS.md.
[stamp]
style         = "grep"
subject_dirs  = []
trailer_stamp = true

# Standing supervisor policy for the generic DOS population loop. Fleet has a
# static, disjoint lane roster, so a target of three is reachable without the
# dynamic-claim `max_concurrency` budget the job repo uses. The worker is
# launched through a backend-selector shim (tools/dispatch_worker.py) so the
# fleet can run a MIX of Claude and opencode workers behind one template: the
# shim defaults to Claude (the dos-kernel plugin skill `/dos-kernel:dos-*`) and
# switches to opencode (the `.opencode/agent/dos-dispatch.md` worker) when
# FLEET_WORKER_BACKEND=opencode is set on a node. The Claude launch shape lives
# in the shim and is kept byte-identical to the historical direct template.
[supervise]
# target = 0 (2026-06-22, #517): NOTHING in-repo keeps a standing loop alive on the
# live path, so a target of 4 was aspirational, not enforced -- zero loops are actually
# kept alive. `dos loop` WITHOUT `--enact` is emit-only (it only PRINTS the
# worker_launch_template below); `dos loop --enact` DOES Popen it, but nothing in-repo
# arms a standing `--enact` loop, and the in-tree watchdog hardcodes its own argv (see
# the NOTE under the template). Lowered to 0 so the declared count matches what is
# actually kept alive. The keep-alive ARMING that would make a non-zero target real is
# tracked in #20; raise this back to the desired standing-loop count when that lands.
target = 0
# NOTE (cutover 2026-06-24): this template now names the interpreter-free Go launcher
# cmd/dispatchworker (`make build` drops it at tools/.bin/dispatchworker), retiring the
# bare-`python` token that ENOENTed on python3-only nodes (#22). `dos loop` WITHOUT
# `--enact` only PRINTS this; `dos loop --enact` Popens it (`dos loop --help`: "Popen
# each SPAWN via [supervise].worker_launch_template"), so the launcher IS on the --enact
# exec path -- hence the Go binary. The other live spawner,
# tools/dos_supervisor_watchdog.py:enact_command, likewise prefers the Go binary and
# falls back to dispatch_worker.py via sys.executable when it is not built, so a node
# without the binary still dispatches. The cutover is launcher-AGNOSTIC for worker
# detection: dispatch_preflight.py counts the `dos-dispatch-loop` marker on the spawned
# `claude -p` worker (identical whichever launcher started it), and dispatch_status /
# fleet_dos_dispatch_watchdog key on run-dir PIDs and the `loop`/`--enact` supervisor
# markers -- none key on the launcher's own name, so the double-spawn guard is unchanged.
# A node that runs `--enact` must `make build` first (relative path, resolved from the
# workspace). See docs/MULTINODE-DOS-FAK-CROSSOVER-2026-06-20.md.
worker_launch_template = 'tools/.bin/dispatchworker --lane {lane}'

# Dogfood: fleet's OWN architectural invariants, declared as DOS refusal vocabulary
# (DATA -- no kernel edit), each with a deterministic floor that already runs in CI.
# This is the whole repo turned on itself: a change that breaks the layering or the
# typed-core direction is refusable with a structured, citable reason from a closed
# set, not free-text -- the same discipline fak imposes on tool results. The floor
# under each is the architest gate (`go test ./internal/architest`).
[reasons.LOOP_DONE_UNWITNESSED]
category = "STALE_CLAIM"
refusal  = true
summary  = "A loop turn claimed done, but the configured external witness did not corroborate the done effect from non-agent-authored evidence."
fix      = "Re-arm the loop with this reason as feedback, make the next turn satisfy the goal's witness criterion (commit-audit, verify, test-witness, or a registered witness), then re-check the gate."
see_also = ["internal/loopgate", "dos commit-audit", "dos verify", "dos test-witness", "dos witness"]

[reasons.STALE_RECALL]
category = "STALE_CLAIM"
refusal  = true
summary  = "A dev-ex loop is about to act on recalled status or plan memory whose backing witness is stale relative to the current git/ledger source of truth."
fix      = "Refresh from the source witness (`dos status`, `dos verify`, `dos commit-audit`, or the current git ref), discard the stale recall, then retry with the refreshed digest."
see_also = ["dos status", "dos verify", "dos commit-audit", "docs/goal-spec.md"]

[reasons.COLLISION_RISK]
category = "OPERATOR_GATE"
refusal  = true
summary  = "A dispatch or worker launch would overlap a live lease/region and risks two agents mutating the same file tree concurrently."
fix      = "Run the arbiter and wait, repartition, or pick a disjoint lane/region before launching. Floor: `dos arbitrate` / lane lease checks."
see_also = ["dos arbitrate", "dos top", "tools/dos_fleet_lease.py"]

[reasons.GATE_LATENCY_REGRESSION]
category = "OPERATOR_GATE"
refusal  = true
summary  = "A guard or DOS gate grew slow enough to threaten the loop's dev-ex latency budget, so the loop should not normalize the regression as healthy."
fix      = "Measure the gate with the relevant hook/status benchmark, fix or budget the added latency, and rerun before claiming the loop is healthy."
see_also = ["tools/dos_hook_bench.py", "docs/perf-dos-hook-cost.md", "dos status"]

[reasons.RUN_STATUS_CLAIMED_FIELD]
category = "STALE_CLAIM"
refusal  = true
summary  = "`dos status` returned a run digest containing a `claimed` field, violating the witnessed-status contract that peers read liveness/progress from evidence only."
fix      = "Fix the status producer so the digest contains only liveness, ledger-verified progress, lease region, and resume evidence; do not consume claimed status."
see_also = ["dos status", "tools/dispatch_status.py"]

[reasons.ARCH_LAYER_VIOLATION]
category = "OPERATOR_GATE"
refusal  = true
summary  = "change breaks the layered-DAG import contract (an upward/cross-tier import, or a new leaf with no declared tier)"
fix      = "invert the dependency through a registration seam or push the shared type down a layer; declare a new leaf's tier in internal/architest (or run `python tools/new_leaf.py`). Floor: `.\\fak\\test.ps1 ./internal/architest/`"
see_also = ["fak/internal/architest/architest_test.go", "fak/ARCHITECTURE.md", "fak/GROWTH.md"]

[reasons.OUT_OF_DIRECTION]
category = "OPERATOR_GATE"
refusal  = true
summary  = "request-path logic in an untyped language, or a non-Go package blank-imported into the kernel (DIRECTION.md)"
fix      = "keep the request path Go-only; a non-Go seam must be off-path and cross a typed, re-validated boundary. Floor: architest TestHotPathHasNoExec"
see_also = ["fak/DIRECTION.md", "fak/internal/architest/architest_test.go"]

# Trunk discipline: work lands directly on `main` as a SINGLE working tree, not on
# stray feature branches / per-issue worktrees. This is the cross-agent "stay on the trunk"
# policy turned into DOS vocabulary -- the same structured-reason discipline fak imposes
# on tool results, aimed at the fleet's own git workflow so Claude, Codex, and humans
# share ONE refusal instead of three doc sentences that only one of them reads. The named
# floor is the git hook (tools/githooks/reference-transaction), which fires below the
# agent layer; `python tools/install_trunk_guard.py` activates it per clone.
[reasons.OFF_TRUNK]
category = "OPERATOR_GATE"
refusal  = true
summary  = "work landed off the trunk -- a new branch or worktree was created instead of committing directly to main (the single-tree-on-trunk operator law). Every worker MUST stay on main in the main worktree; creating a side branch/worktree to route around a dirty or diverged tree is the specific anti-pattern this reason names."
fix      = "commit directly to main with small, path-scoped commits; pull/merge frequently, preferring merge or pull --no-rebase when unrelated dirty files are present. `git commit -- <paths>` and merge never need a clean tree, so a worktree is never required to ship -- a dirty/diverged tree means WAIT or merge in place, never escape into a side tree; if you truly cannot proceed on main, STOP and surface the blocker. Floor: tools/githooks/reference-transaction blocks off-trunk branch creation by default; set FLEET_TRUNK_GUARD=warn for advisory mode, or FLEET_ALLOW_BRANCH=1 to override once (release tooling only)."
see_also = ["CLAUDE.md", "AGENTS.md", "tools/githooks/reference-transaction", "tools/install_trunk_guard.py"]

# Stale-base deletion: a `git commit -- <path>` (or `fak commit`) stages the
# WORKING-TREE copy of exactly the named pathspec, so a stale whole-file base
# SILENTLY drops a peer's pushed block even on a pure-add change. The content-level
# merge-base guard in internal/safecommit (FAK_STALE_BASE_GUARD=block|warn|off)
# refuses with this reason when the committed blob would delete a contiguous
# peer-added run present on origin/<trunk> but absent from the working tree.
[reasons.STALE_BASE_DELETION]
category = "OPERATOR_GATE"
refusal  = true
summary  = "a pathspec commit would silently drop a peer's pushed block -- the working-tree copy of the named path is stale relative to origin/<trunk>, so committing it deletes a contiguous peer-added run the local change never touched"
fix      = "fetch + rebase/merge origin/<trunk> so the working-tree base includes the peer's block, then re-commit; never escape into a side tree. FAK_STALE_BASE_GUARD=warn for advisory mode, =off to disable. Floor: internal/safecommit checkStaleBaseDeletion (fail-open on a fresh base / absent ref)."
see_also = ["fak/internal/safecommit/stalebase.go", "fak/CLAUDE.md", "fak/AGENTS.md"]

# Public-copy leak scan: the private canonical repo is the source for every
# future public export, so the redact-needles listed in PUBLIC-SCRUB-POLICY.md
# (operator Tailscale IPs, SSH user, machine paths, the .claude-agent-*
# config dir, the Go-module codename, operator hostnames, sibling-repo
# working-copy paths) must never enter THIS history in the first place. The
# named floor is the git hook (tools/githooks/pre-commit), which fires below
# the agent layer; `python tools/install_trunk_guard.py` activates it alongside
# the trunk guard. The export-time scrubber (tools/scrub_public_copy.py) is
# defense-in-depth; this hook is the primary gate.
[reasons.PUBLIC_LEAK]
category = "OPERATOR_GATE"
refusal  = true
summary  = "staged content matches a redact-needle -- operator-private info would enter the repo's history (the source for every public export)"
fix      = "remove the needle from the staged change, or redact it per PUBLIC-SCRUB-POLICY.md. Floor: tools/githooks/pre-commit blocks the commit by default; set FLEET_SCRUB_GUARD=warn for advisory mode, or FLEET_ALLOW_LEAK=1 to override once (only for new fake adversarial fixtures that intentionally trip a needle)."
see_also = ["PUBLIC-SCRUB-POLICY.md", "tools/githooks/pre-commit", "tools/scrub_public_copy.py", "tools/install_trunk_guard.py", "tools/check_committed_files.py"]

# File-admission gate: a staged path is PRIVATE-ONLY content that must never enter the
# PUBLIC tree -- the operator's lab GPU-server connection subsystem (cmd//internal/ *dgx*
# packages, cmd/slackgc) lives ONLY in the fak-private companion repo -- or it is
# regenerable junk (caches, compiled binaries, logs, demo output) or an oversized blob a
# forever-history public tree should not carry. This is the COMMIT-TIME, content-placement
# sibling of PUBLIC_LEAK (and of the write-time OUT_OF_TREE_WRITE below): the SAME
# public/private boundary, enforced on WHAT a path is rather than WHERE it resolves. Named
# floor: the git hook tools/githooks/pre-commit -> tools/check_committed_files.py, which also
# runs tree-wide in CI (--audit-tree).
[reasons.FILE_ADMISSION]
category = "OPERATOR_GATE"
refusal  = true
summary  = "a staged path must not enter the PUBLIC tree -- it is private-only content (the lab GPU-connection subsystem cmd//internal/ *dgx* / cmd/slackgc, which lives only in fak-private), regenerable junk (cache/compiled/log/demo-output), or an oversized blob"
fix      = "move private-only code to the fak-private companion repo; drop regenerable junk (it rebuilds) or gitignore it; put real data under experiments/ or testdata/; trim an oversized blob or use Git LFS. Floor: tools/githooks/pre-commit blocks the commit by default; set FLEET_FILE_GUARD=warn for advisory mode, or ALLOW_STRAY_FILE=1 to override once."
see_also = ["tools/check_committed_files.py", "tools/githooks/pre-commit", "PUBLIC-SCRUB-POLICY.md", "tools/repo_guard.py"]

# Out-of-tree write guard: a destructive/write op that escapes the repo into a
# SIBLING tree. work/ holds many sibling repos (fak/, fak-private/, fleet/, dos/,
# tools/, ...); a tool resolving a path one level too high writes into ANOTHER
# project. Born 2026-06-21: a build script wrote fak.exe into the sibling
# anthony-chaudhary/tools repo, which was then rm -rf'd. The named floor is a
# PreToolUse hook (not a commit-time hook like the reasons above) -- it fires
# BELOW the agent, before the tool runs. tools/repo_guard.py resolves every
# destructive/write target against the repo root and denies the out-of-tree ones
# (allow-listing the OS temp dir + ~/.cache); the FAK regex twin is
# examples/repo-guard-policy.json. Like the others this is purely ADDITIVE data:
# the hook fires only on a target that lands outside the workspace, so no in-repo
# work is ever refused; soften with FAK_REPO_GUARD=warn or off.
[reasons.OUT_OF_TREE_WRITE]
category = "OPERATOR_GATE"
refusal  = true
summary  = "a destructive or write op targets a path OUTSIDE this repo -- it would create / delete / overwrite inside a SIBLING tree (another project or git repo) or the workspace's parent, not the workspace itself"
fix      = "operate inside the workspace; send scratch to a temp dir (/tmp, ~/.cache), never a sibling. Floor: tools/repo_guard.py (a Claude Code PreToolUse hook in .claude/settings.json) resolves each target against the repo root and denies out-of-tree ones; set FAK_REPO_GUARD=warn for advisory, off to disable. FAK regex twin: examples/repo-guard-policy.json."
see_also = ["tools/repo_guard.py", "examples/repo-guard-policy.json", ".claude/settings.json", "DOGFOOD-CLAUDE.md", "tools/check_committed_files.py"]

# L3 cross-tenant prefix-share gate (#57): the two typed REFUSE reasons the read-path
# admission gate (internal/gateway/l3share.go -> AdmitL3SharedPage) cites when it refuses
# a cross-tenant L3 page. fak rides the control/admission path in FRONT of the external L3
# store's `mget`: that store is semantics-free (trusts the client hash, no content verify,
# no tenancy notion), so fak supplies the two rungs it structurally cannot -- G1 full
# content-verify and G4 scope/capability. Declared here so the tokens are CLOSED
# (validateable via `dos_check_reason`), DISTINCT (a collision is distinguishable from an
# access denial), and refusable -- the issue's "two REFUSE reasons are distinct, closed
# tokens" acceptance. Floor: `go test ./internal/gateway -run TestL3Share`.
[reasons.L3_CROSS_TENANT_SCOPE_DENIED]
category = "OPERATOR_GATE"
refusal  = true
summary  = "G4 scope rung: a cross-tenant reader asked for an L3 page whose ShareScope does not reach across the trust boundary -- a page private to one agent (ScopeAgent) or bound to the owner's tenant (ScopeTenant), fetched by a DIFFERENT tenant. A capability check, not a namespace-prefix match."
fix      = "the page is not shareable across tenants; only an owner-marked fleet/public (ScopeFleet) page crosses the boundary. Mark the prefix ScopeFleet at the producer if it is genuinely public, or serve the reader its own tenant-scoped page. Floor: go test ./internal/gateway -run TestL3Share_CrossTenant."
see_also = ["internal/gateway/l3share.go", "internal/abi/types.go", "L3-DISAGGREGATED-CACHE-REIMAGINED.md"]

[reasons.L3_PAGE_DIGEST_MISMATCH]
category = "OPERATOR_GATE"
refusal  = true
summary  = "G1 verify rung: the bytes the L3 get returned do not hash to the digest the page CLAIMS (Ref.Digest) -- a hash collision or a mis-tag the semantics-free store cannot detect. Refused for EVERY reader, same-tenant included: a corrupt/ambiguous page is served to no one. A full-content digest recompute, not a truncated locality hash."
fix      = "do not admit the page -- the content address does not match its bytes. Re-fetch / re-key at the store, or re-mint the page so its Ref.Digest is the true hex(sha256) of its content. Floor: go test ./internal/gateway -run TestL3Share_DigestMismatch."
see_also = ["internal/gateway/l3share.go", "internal/abi/types.go", "internal/model/evict_test.go"]

# Self-tax overhead budget breach (#1150, epic #1147 T2): the closed-vocabulary token a
# per-rung/per-method overhead breach names. The plane declares an EXPECTED overhead
# envelope per lifecycle rung (internal/turntaxmeter overheadbudget.go -- a generous,
# scope-stated ceiling, not a measured p99); a span over its declared ns/token envelope
# reads back as THIS token rather than free-text prose, reusing the same closed-set
# discipline as the refusal vocabulary so a breach is emittable, verifiable, and
# refusable. A budget is an envelope with a stated scope, NOT a promise of zero cost: a
# rung that costs more but SAVES more is a net win, so the gate's red/green is a
# net-of-savings decision (epic T6), not this single-rung breach alone.
# Floor: `go test ./internal/turntaxmeter -run TestOverBudgetSpanReadsBackAsBreach`.
[reasons.OVERHEAD_BUDGET_EXCEEDED]
category = "OPERATOR_GATE"
refusal  = true
summary  = "A lifecycle rung's measured overhead (elapsed ns or tokens added) exceeded its declared per-rung/per-method budget envelope -- the 'expected' baseline a breach is defined against. The witness gate is subprocess-bound (it spawns git) and carries a deliberately wide envelope so a slow disk does not read as a kernel regression."
fix      = "Confirm the breach against the rung's declared envelope (internal/turntaxmeter.DefaultBudget); if it is a real regression, fix or re-budget the added overhead; if the rung also SAVES more than it costs, the net line (epic T6) decides red/green, not this single breach. Floor: go test ./internal/turntaxmeter."
see_also = ["internal/turntaxmeter/overheadbudget.go", "docs/notes/self-tax-performance-assurance-tracking-1147.md", "GATE_LATENCY_REGRESSION"]

# Verification-ladder INDETERMINATE (#1210, C2/G2 of the agent-programming-grammar
# epic #1208): the first-class verdict the declarable verification ladder
# (docs/standards/verification-ladder-spec.md) folds to when a cheap rung could NOT
# conclusively decide and the ladder exhausted without a costlier rung resolving it.
# It is the kernel's shipped VerdictIndeterminate (internal/abi/types.go,
# FoldRank 15 between Defer and Transform in internal/abi/registry.go; the lazy fold
# in internal/kernel/kernel.go makes it non-committable -- a residual Indeterminate
# yields Deny) lifted into the closed REFUSAL vocabulary so a ladder's fail-closed
# deny carries a dos_check_reason-validatable token instead of free text. Distinct
# from a fail-open allow and from a bare DEFAULT_DENY abstention: it names "I could
# not decide cheaply; the costlier rung was unavailable, so fail closed -- escalate,
# never silently allow." Pure DATA: it introduces no spontaneous refusal, it only
# gives the ladder's existing fail-closed tail a checkable name.
[reasons.INDETERMINATE]
category = "OPERATOR_GATE"
refusal  = true
summary  = "Verification-ladder rung could not CONCLUSIVELY decide the claim cheaply and the ladder exhausted with no costlier rung available -- the residual indeterminate folds to a fail-closed deny (escalate, never silently allow). The kernel-side VerdictIndeterminate (internal/abi/types.go) is non-committable by construction; this token names the ladder's fail-closed tail."
fix      = "Consult a costlier rung that can conclusively decide (require-witness / CI / git-evidence / isolated-worktree keep-bit / human ESCALATE), or accept the fail-closed deny. The ladder is DATA: declare a sufficient rung whose max_risk covers the claim's risk class in the dos.toml [ladder] (docs/standards/verification-ladder-spec.md). Floor: go test ./internal/kernel (the shipped lazy fold + residual-Indeterminate-fails-closed)."
see_also = ["docs/standards/verification-ladder-spec.md", "docs/notes/verification-ladder-doctrine.md", "internal/abi/types.go", "internal/kernel/kernel.go"]

# Bound the per-project `.dos/` scratch so a long-lived repo doesn't accrete audit
# reports without limit (the "never reap a live lease" floor is the collector's,
# independent of this number).
[retention]
audits_keep_last = 30
