Error Code Reference
testing-os’ CLIs surface structured errors at the top-level seam via renderTopLevelError (packages/dogfood-swarm/lib/error-render.js). Every typed error carries:
code— stable identifier (e.g.ISOLATION_FAILED)message— operator-facing prosehint— explicit next step (or a per-code derived hint when the error class did not set one)- optional
cause(Caused by: …),runId,waveId,agentRunId,findingsAttempted
CLI output shape:
ERROR [<CODE>]: <message> Next: <hint> Caused by: <inner error message> Wave: <waveId>Untyped errors keep the original ERROR: <message> single-line shape. A leading ERROR [<CODE>]: is the signal that one of the codes below is in play.
Severity tiers — fix order at a glance
Section titled “Severity tiers — fix order at a glance”| Severity | Visual cue | Meaning | Operator response |
|---|---|---|---|
| CRITICAL | :::danger callout (red ⊘) |
Persistent state corrupted or contract broken; a record / index is wrong, not just absent | Stop ingesting, repair the underlying state, then resume |
| HIGH | :::caution callout (orange ⚠) |
Operator action required before the system can make progress; one run lost | Diagnose using the hint, fix the upstream cause, re-dispatch |
| MEDIUM | :::note callout (blue ℹ) |
Informational — a race or transient issue handled gracefully | Inspect the persisted state with the suggested CLI, then continue |
| LOW | :::tip callout (green ✓) |
Caller bug surfaced as a state-machine reject; system state is consistent | Fix the caller; no recovery needed on the testing-os side |
Severity is encoded by the Starlight callout type at the top of each code below — color is paired with the icon and the bolded Severity: title, so a color-blind operator gets the same fix-order signal from the icon + word as a sighted operator gets from the hue. WCAG AA contrast ratios for each callout variant are asserted by scripts/check-severity-contrast.test.mjs.
RECORD_SCHEMA_INVALID
Section titled “RECORD_SCHEMA_INVALID”- Class:
RecordValidationError(packages/ingest/validate-record.js) - Trigger: A persisted record fails AJV validation against
dogfood-record.schema.json. Surfaced fromvalidateRecord()during ingest. - Message shape:
persisted record failed schema validation: <path> <ajv message>; <path> <ajv message>; … - Hint:
inspect the failing record against packages/schemas/src/json/dogfood-record.schema.json and fix the invalid fields before re-ingesting - Operator action:
- Open
packages/schemas/src/json/dogfood-record.schema.jsonand locate each path from the message. - The error object also carries
errors[]with{ path, keyword, message }for programmatic inspection. - Fix the upstream emitter (the source repo’s submission builder), not the schema. Schema is a contract.
- Re-dispatch the source workflow to produce a clean record.
- Open
DUPLICATE_RUN_ID
Section titled “DUPLICATE_RUN_ID”- Class:
DuplicateRunIdError(packages/ingest/persist.js) - Trigger:
writeRecordlost a TOCTOU race for the same canonical record path. Two concurrent writers tried to persist the samerun_id; the first won. - Message shape:
duplicate run_id: <run_id> — another writer won the race for <path> - Hint:
a run with this id already exists — use a fresh run id or \swarm runs` to inspect the existing one` - Carries:
runId,path - Operator action:
- In ingest: this is informational — the first writer succeeded, the system is consistent. Re-running the source workflow with a fresh
run_idproduces a new record. - In swarm:
swarm runslists existing runs by id. Either re-dispatch with a fresh id or accept the existing record.
- In ingest: this is informational — the first writer succeeded, the system is consistent. Re-running the source workflow with a fresh
ISOLATION_FAILED
Section titled “ISOLATION_FAILED”- Class:
IsolationError(packages/dogfood-swarm/lib/errors.js) - Trigger:
--isolatewas requested on aswarm dispatchbutcreateWorktree()failed. Pre-fix, dispatch silently fell back to running the agent in the main repo; isolation is now a contract — only valid responses are “isolated” or “loud failure”. - Message shape: the underlying worktree error wrapped with the explicit isolation context. Inspect
e.cause.messagefor the git-level reason. - Hint:
run \git worktree list` to inspect existing worktrees, or re-dispatch without –isolate` - Operator action:
git worktree listfrom the repo root to see what’s already attached.git worktree pruneto clean stale references;git worktree remove <path>to clear specific entries.- Re-dispatch with
--isolate, or drop--isolateif isolation is not required for this run (accepting the shared-workspace risk).
COLLECT_UPSERT_FAILED
Section titled “COLLECT_UPSERT_FAILED”- Class:
CollectUpsertError(packages/dogfood-swarm/lib/errors.js) - Trigger:
swarm collect’s findings upsert transaction threw. Common underlying causes: SQLitebusy_timeoutexhaustion, fingerprint UNIQUE collision, prepared-statement crash. The artifact rows + file_claims + agent state transitions had already committed; the wave-status UPDATE had not. - Message shape: structured wrapper with
e.cause.messagecarrying the SQLite-level reason. - Hint:
wave <id> has artifacts persisted but findings missing — inspect with \swarm status`, then re-run `swarm collect` once the underlying SQLite issue is resolved (busy_timeout or fingerprint UNIQUE collision)` - Carries:
waveId,findingsAttempted,cause - Operator action:
swarm statusto confirm the wave is in a half-written state (artifacts present, findings missing).- Diagnose the underlying SQLite issue from
Caused by:.busy_timeoutusually means another process holds the DB; check for stuckswarmprocesses. UNIQUE collision usually means the fingerprint algorithm matched an existing row — checkswarms/control-plane.dbfor the colliding finding. - Re-run
swarm collectfor the same wave once resolved. The outer wrapper is idempotent at the upsert level.
CONTROL_PLANE_SCHEMA_CORRUPT
Section titled “CONTROL_PLANE_SCHEMA_CORRUPT”- Class:
ControlPlaneSchemaCorruptError(packages/dogfood-swarm/lib/errors.js), thrown bygetSchemaVersionand surfaced throughopenDb—packages/dogfood-swarm/db/connection.js. Carriescode: 'CONTROL_PLANE_SCHEMA_CORRUPT', so it renders throughrenderTopLevelErroras the structuredERROR [CONTROL_PLANE_SCHEMA_CORRUPT]:envelope like the rest of the family. - Trigger:
getSchemaVersion()read aschema_versionrow from thekvtable whose value failsNumber.isFinite— e.g."abc", or any textNumber()turns intoNaN. Distinct fromCONTROL_PLANE_SCHEMA_TOO_NEWon purpose: the two conditions get separate codes so an operator is never told to “upgrade” a DB that is actually damaged, or to restore-from-backup a DB that is merely newer. - Not every junk value reaches this code.
Number('')is0, notNaN— finite — so an emptyschema_versiondoes not throw here; it reads as version 0 and takes the bootstrap path, and a whitespace-only" "coerces to0the same way. Do not generalize that to every coercible string: a value that parses to a nonzero finite number never reaches this code either, but it does not take the bootstrap path — it is treated as a real on-disk version and routed by magnitude."0x10"parses to16, which sits above today’s schema ceiling and throwsCONTROL_PLANE_SCHEMA_TOO_NEWinstead: a damaged value masquerading as version skew, where that code’s “upgrade your build” hint is the wrong remediation — run the inspect query below before believing it. This is theNumber('') === 0trap thatF-b5fd9887fixed one module over incross-run-analytics.js. An earlier revision of this bullet listed""as a trigger — wrong; the revision that corrected it then filed"0x10"under the bootstrap path — also wrong. Both were caught by audits that ran the code instead of reading the prose. - Message shape:
control-plane.db at <dbPath> has a corrupted kv.schema_version value: <rawValue> — expected a finite number.— where<rawValue>isJSON.stringify’d, so a string value renders quoted ("abc", notabc). That is deliberate: it makes a whitespace-only or empty-looking value visible instead of vanishing into the sentence. - Carries:
rawValue(the unparseable value as read from disk),dbPath,hint. - Hint:
kv.schema_version is not a finite number — the control-plane.db is corrupted or was hand-edited, not merely older/newer. Restore control-plane.db from a known-good backup, or remove it to bootstrap a fresh one (only if this run's history is not needed) — do not hand-write schema_version without reading db/migrate.js's ledger first. - Why it fails loud instead of coping (F-9587adda):
Number('abc')isNaN, and every comparison againstNaNis false. A silentNaNtherefore defeated both ofopenDb’s guards at once — the too-new refusal (onDisk > SCHEMA_VERSION) and the bootstrap gate (onDisk === 0) — so a corrupted DB sailed past the exact two checks written to stop it and got operated on as though its schema were understood. This is the ordinary NaN-poisoning shape with an unusually bad blast radius: not one guard bypassed, but a matched pair. - Recovery:
- Inspect the value the error names:
sqlite3 swarms/control-plane.db "SELECT * FROM kv WHERE key='schema_version'". - Restore
control-plane.dbfrom a known-good backup if this run’s history matters. - Only if it does not, delete the DB and let
openDbbootstrap a fresh one. - Do not hand-write
schema_versionback to a plausible-looking integer — readdb/migrate.js’s migration ledger first, or you will claim a shape the DB does not have.
- Inspect the value the error names:
CONTROL_PLANE_SCHEMA_TOO_NEW
Section titled “CONTROL_PLANE_SCHEMA_TOO_NEW”- Class:
ControlPlaneSchemaTooNewError(packages/dogfood-swarm/lib/errors.js), thrown byopenDb—packages/dogfood-swarm/db/connection.js. Carriescode: 'CONTROL_PLANE_SCHEMA_TOO_NEW', the on-disk + build versions, and ahint, so it renders throughrenderTopLevelErroras the structuredERROR [CONTROL_PLANE_SCHEMA_TOO_NEW]:envelope like the rest of the family (F4-CP-03 / F5-07 promoted it from the earlier untypedthrow new Error(...)). - Trigger:
openDb()readschema_versionfrom the DB’skvtable and found it greater than theSCHEMA_VERSIONthis build understands. The sharedswarms/control-plane.dbis committed back tomainbyingest.yml; an operator on an older checkout (or a stale CI cache) can open a DB that a newermainalready migrated. The refusal fires before the create/upgrade/bootstrap path, fail-closed:openDbcloses the handle and drops it from the pool before throwing, so no write happens against the unknown-newer shape. - Message shape:
control-plane.db at <dbPath> is schema v<version> but this @dogfood-lab/dogfood-swarm build only understands v<SCHEMA_VERSION>. Pull the latest @dogfood-lab/dogfood-swarm before opening this DB. - Carries:
onDiskVersion,buildVersion,dbPath,hint. - Hint:
Pull the latest @dogfood-lab/dogfood-swarm so your build SCHEMA_VERSION >= the on-disk control-plane version. Do NOT hand-edit or delete the DB — its state is the newer build's correctly migrated state, not corruption. - Recovery (the message + hint say it too): this is not DB corruption and needs no manual DB surgery — the remedy is to upgrade the tool to match the DB:
- Pull the latest
main/ re-install@dogfood-lab/dogfood-swarmso your build’sSCHEMA_VERSIONis>=the on-disk version. - Re-run the command.
openDbwill then take the normal create/upgrade path (and the migration ledger will reconcile). - Do not hand-edit
swarms/control-plane.dbor delete it to “fix” the version — that discards the newer migrated state the newer build wrote.
- Pull the latest
DISPATCH_RUN_NOT_FOUND
Section titled “DISPATCH_RUN_NOT_FOUND”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js) - Trigger:
dispatch()looked upruns.idand got no row. Either the run id is mistyped, or noswarm inithas been run for this repo. - Message shape:
Run not found: <run-id> - Hint:
check \swarm runs` for the correct run id, or `swarm init` to create a fresh run` - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_RUN_NOT_FOUND,runId,phase,correlation_id. - Operator action:
swarm runsto list all known runs.- If the run doesn’t exist,
swarm init <repo-path>to create it.
DISPATCH_DOMAINS_NOT_FROZEN
Section titled “DISPATCH_DOMAINS_NOT_FROZEN”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js) - Trigger:
aredomainsFrozen(runId)returned false and--auto-freezewas not passed. - Message shape:
Domains are not frozen. Review and freeze before dispatching, or pass --auto-freeze. - Hint:
run \swarm domains–freeze` after reviewing, or re-run dispatch with –auto-freeze` - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_DOMAINS_NOT_FROZEN. - Operator action:
swarm domains <run-id>to inspect the current draft.swarm domains <run-id> --freezeto lock the map, OR re-run with--auto-freeze.
DISPATCH_NO_DOMAINS
Section titled “DISPATCH_NO_DOMAINS”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js) - Trigger:
getDomains(runId).length === 0. Usually meansswarm initproduced no auto-detected domains and the operator hasn’t added any manually. - Message shape:
No domains defined for this run - Hint:
run \swarm domains–add –globs “[…]”` then –freeze` - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_NO_DOMAINS. - Operator action:
swarm domains <run-id> --add <name> --globs '["packages/foo/**"]'to define at least one domain.swarm domains <run-id> --freeze.
DISPATCH_INVALID_PHASE
Section titled “DISPATCH_INVALID_PHASE”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js) — same class as the otherDISPATCH_*preconditions;codeis part of the JSDoc union contract. - Trigger:
dispatch()checkedopts.phaseagainstAUDIT_PHASESandAMEND_PHASES(inpackages/dogfood-swarm/commands/dispatch.js) before any DB mutation and found neither matched — i.e. a mistyped phase such ashelth-audit-a. - Message shape:
Unknown phase: <phase> - Hint:
valid phases: <AUDIT_PHASES ∪ AMEND_PHASES>— currentlyhealth-audit-a, health-audit-b, health-audit-c, stage-d-audit, feature-audit, health-amend-a, health-amend-b, health-amend-c, stage-d-amend, feature-execute. When the thrown error carries no.hint,renderTopLevelErrorderives the same enumeration. - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_INVALID_PHASE,runId,phase. - Carries:
runId,phase. - Operator action:
- Re-invoke with a phase from the list above, e.g.
swarm dispatch <run-id> health-audit-a. - The control plane is untouched — no cleanup is needed before retrying.
- Re-invoke with a phase from the list above, e.g.
DISPATCH_NO_AGENT_DOMAINS
Section titled “DISPATCH_NO_AGENT_DOMAINS”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js). - Trigger:
dispatch()’s pre-transaction sweep of the frozen domain map (packages/dogfood-swarm/commands/dispatch.js) finds noownedorbridgedomain to become an agent. - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_NO_AGENT_DOMAINS. - Operator action: edit the domain map (
swarm domains <run-id> --edit/--add) so at least one domain isowned(orbridge), re-freeze, then dispatch.
DISPATCH_WAVE_IN_FLIGHT
Section titled “DISPATCH_WAVE_IN_FLIGHT”- Class:
DispatchPreconditionError(packages/dogfood-swarm/lib/errors.js). - Trigger:
dispatch()’s pre-transaction wave-status check (packages/dogfood-swarm/commands/dispatch.js) finds the newestwavesrow in a non-terminal collecting state. - NDJSON event emitted before throw:
dispatch_precondition_failedwithcode=DISPATCH_WAVE_IN_FLIGHT. - Operator action: finish the in-flight wave first —
swarm collect <run-id> --all(orswarm resume <run-id>if agents died) — then dispatch the next one.swarm cleanmirrors this guard on--apply(CLEAN_WAVE_IN_FLIGHT).
CLI_INVALID_GLOBS_JSON
Section titled “CLI_INVALID_GLOBS_JSON”- Class:
CliInvalidGlobsError(packages/dogfood-swarm/lib/errors.js) - Trigger:
swarm domains --add/--edit --globs <raw>invoked with arawvalue that:- is empty
- fails
JSON.parse - parses to a non-array
- parses to an empty array
- contains a non-string element
- Message shape:
--globs requires a JSON array of glob strings; <specific reason> - Hint:
pass --globs '["packages/foo/**"]' — wrap the JSON in single quotes so the shell preserves it, and use double quotes for each glob string - Carries:
received(the raw input, possibly truncated),cause(the inner JSON.parse error message). - Operator action:
- Re-invoke with shell-safe quoting:
--globs '["packages/foo/**", "packages/bar/**"]'. - On Windows PowerShell, escape inner double quotes or use the single-quote outer form per shell rules.
- Re-invoke with shell-safe quoting:
DOMAINS_INVALID_OWNERSHIP_CLASS
Section titled “DOMAINS_INVALID_OWNERSHIP_CLASS”- Class:
DomainsInvalidOwnershipError(packages/dogfood-swarm/lib/errors.js). - Trigger:
swarm domains --add/--edit --ownership <value>with a value outsideSTATUS.ownership_class(live:owned|shared|bridge|coordinator). - Message shape:
Invalid ownership class: <received> (valid: owned|shared|bridge|coordinator) - Hint:
pass one of owned|shared|bridge|coordinator — e.g. \–ownership owned`` - Carries:
received,valid. - Operator action:
- Re-invoke with one of the four live classes.
coordinatoris exclusive and skipped at dispatch (GitHub #67). - Do not reclassify
docstosharedto skip dispatch — that makes public surfaces world-writable.
- Re-invoke with one of the four live classes.
CLI_INVALID_THRESHOLD
Section titled “CLI_INVALID_THRESHOLD”- Class: plain
Errorwithe.code = 'CLI_INVALID_THRESHOLD'set inparseVerifyFlags—packages/dogfood-swarm/cli.js. Surfaced through the same top-level seam (renderTopLevelError) asCLI_INVALID_GLOBS_JSON. - Trigger:
swarm verify --threshold <raw>(space-form--threshold Nor equals-form--threshold=N) invoked with arawvalue that is not a non-negative integer — e.g.foo,-1, or a partially-numeric3abc. Both flag forms route through the same validator, so a typo like--threshold=1O(letter O) is rejected rather than silently becoming the strictest gate (0). - Message shape:
--threshold expects a non-negative integer; got '<raw>' - Hint:
pass an integer >= 0, e.g. \–threshold 0` or `–threshold=3`` - Carries:
received(the raw input). - Operator action:
- Re-invoke with an integer
>= 0:swarm verify <run-id> --threshold 0. - A typo’d threshold exits non-zero by design — a CI gate keyed on
$?will not mistake a malformed threshold for a passing run.
- Re-invoke with an integer
FINDING_ID_COLLISION
Section titled “FINDING_ID_COLLISION”- Class: object-literal
{ code: 'FINDING_ID_COLLISION', findingId, error }(inwriteFindingserrors array) ANDFindingIdCollisionErrorclass (inwriteFindingsingleton) —packages/findings/derive/write-findings.js - Trigger: Two derivation rules generate the same
dfind-<repoSlug>-<lessonSlug>for the same submission (the id generator does NOT yet discriminate byrule_id), and the resulting batch — OR two same-process singleton calls — try to write to the same path. The batch helperwriteFindingscollects collisions intoerrors[]; the singletonwriteFindingthrows. - Message shape:
intra-batch finding_id collision: '<id>' already claimed by index <N>; refused write at index <M> to avoid silent clobber (D2B-008)(batch) orfinding_id collision: '<id>' already written in this process; refused to silently clobber (D2B-008 / L3-001 family-seal)(singleton). - Hint: rename or skip the colliding finding before re-running
dogfood findings derive --write. If two rules legitimately share a lesson slug, the structural fix is to differentiate them ingenerateFindingId(rule_id in the slug) — deferred to a follow-on wave. - Operator action:
- Run
dogfood findings derive(without--write) to see which rule pairs are colliding. - Either skip the duplicate at the source rule, or extend the id generator to include
rule_idin the slug. - If a re-write is legitimate (e.g. after an intentional disk wipe in a test), call
resetSeenWrites(rootDir)between the two calls.
- Run
PATTERN_ID_COLLISION
Section titled “PATTERN_ID_COLLISION”- Class: object-literal
{ code: 'PATTERN_ID_COLLISION', patternId, error }(inwritePatternserrors array) ANDPatternIdCollisionErrorclass (inwritePatternsingleton) —packages/findings/synthesis/write-artifacts.js - Trigger: Two synthesis rules emit the same
dpat-<slug>(cluster-key collision) and the resulting batch tries to write both, or two same-process singleton calls collide. - Message shape:
intra-batch pattern_id collision: '<id>' already claimed by index <N>; refused write at index <M> to avoid silent clobber (D2B-008)(batch) or singleton variant. - Hint: same as
FINDING_ID_COLLISION— fix the duplicating rule or wipe and re-run. - Operator action: as above, for patterns.
RECOMMENDATION_ID_COLLISION
Section titled “RECOMMENDATION_ID_COLLISION”- Class: object-literal
{ code: 'RECOMMENDATION_ID_COLLISION', recommendationId, error }(batch) ANDRecommendationIdCollisionError(singleton) —packages/findings/synthesis/write-artifacts.js - Trigger: Two recommendation derivations emit the same
drec-<slug>. - Message shape: as above, with
recommendation_idin the message. - Hint: as above.
- Operator action: as above, for recommendations.
DOCTRINE_ID_COLLISION
Section titled “DOCTRINE_ID_COLLISION”- Class: object-literal
{ code: 'DOCTRINE_ID_COLLISION', doctrineId, error }(batch) ANDDoctrineIdCollisionError(singleton) —packages/findings/synthesis/write-artifacts.js - Trigger: Two doctrine derivations emit the same
ddoc-<slug>. - Message shape: as above, with
doctrine_idin the message. - Hint: as above.
- Operator action: as above, for doctrine.
FINDING_SCHEMA_INVALID
Section titled “FINDING_SCHEMA_INVALID”- Class:
FindingValidationError(packages/findings/derive/write-findings.js) - Trigger:
writeFinding/writeFindingsinvoked with a finding object that fails AJV validation. Pre-fix, library-path writers had no schema gate (the CLI gated, but programmatic callers did not). - Message shape:
finding failed schema validation (<finding_id>): <path> <message>; <path> <message>; … - Hint: inspect each path against
packages/schemas/src/json/dogfood-finding.schema.jsonand fix the upstream emitter. Schema is a contract. - Carries:
findingId,errors[](AJV-shaped{ path, message }). - Operator action: same as
RECORD_SCHEMA_INVALID— fix the emitter, not the schema.
PATTERN_SCHEMA_INVALID
Section titled “PATTERN_SCHEMA_INVALID”- Class:
PatternValidationError(packages/findings/synthesis/write-artifacts.js) - Trigger:
writePattern/writePatternsinvoked with a malformed pattern. Pre-fix, the synthesis writers had ZERO validation (not even CLI-side) — this was the worst gap of the family. - Message shape:
pattern failed schema validation (<pattern_id>): <path> <message>; … - Hint: inspect against
packages/schemas/src/json/dogfood-pattern.schema.jsonand fix the derivation rule. - Carries:
patternId,errors[]. - Operator action: fix the derivation rule.
RECOMMENDATION_SCHEMA_INVALID
Section titled “RECOMMENDATION_SCHEMA_INVALID”- Class:
RecommendationValidationError(packages/findings/synthesis/write-artifacts.js) - Trigger:
writeRecommendation/writeRecommendationsinvoked with a malformed recommendation. - Message shape:
recommendation failed schema validation (<recommendation_id>): <path> <message>; … - Hint: inspect against the recommendation schema and fix the rule.
- Carries:
recommendationId,errors[]. - Operator action: fix the derivation rule.
DOCTRINE_SCHEMA_INVALID
Section titled “DOCTRINE_SCHEMA_INVALID”- Class:
DoctrineValidationError(packages/findings/synthesis/write-artifacts.js) - Trigger:
writeDoctrine/writeDoctrinesinvoked with a malformed doctrine. - Message shape:
doctrine failed schema validation (<doctrine_id>): <path> <message>; … - Hint: inspect against the doctrine schema and fix the rule.
- Carries:
doctrineId,errors[]. - Operator action: fix the derivation rule.
FINDING_UNSAFE_REPO
Section titled “FINDING_UNSAFE_REPO”- Class:
FindingUnsafeRepoError(packages/findings/derive/write-findings.js) - Trigger:
writeFinding/writeFindingsinvoked with a finding whosereposplits into anorg/reposegment containing..or a separator. Thedogfood-findingschema’srepopattern admits./.., so a schema-validrepo: '../policies'previously resolved one directory level underrootDirand wrote outsidefindings/(into sibling runtime dirs likepolicies/,indexes/,reports/). The read path (loadRecordsForRepoWithSkips) already guarded this viaisUnsafeSegment; this closes the write side (findings-A-001). - Message shape:
unsafe repo path segment in finding (<finding_id>): '<repo>' contains a path-traversal or separator and was refused before any write (findings-A-001). - Carries:
repo,findingId. - Operator action:
- Inspect the offending record/finding — the
repofield is malformed (contains..or/inside the org or repo name). - Fix the upstream emitter (the source repo’s submission builder), not the guard. A legitimate
repois exactly<org>/<repo>with no traversal segments. - Re-run
dogfood findings derive --writeonce the emitter is corrected.
- Inspect the offending record/finding — the
RECOMMENDATION_UNSAFE_POLICY
Section titled “RECOMMENDATION_UNSAFE_POLICY”- Class: structured error
{ code: 'RECOMMENDATION_UNSAFE_POLICY', … }(packages/findings/synthesis/apply-recommendation.js) - Trigger:
dogfood findings advise --policy <org/repo>(apply-recommendation) invoked with anorg/repothat is empty or contains../ a separator.policyPathForresolvespolicies/repos/<org>/<repo>.yaml; an unsafe segment would escape thepolicies/tree. - Message shape:
policy repo "<repo>" is not a safe org/repo path segment - Hint:
Pass --policy <org/repo> with no ".." or path separators inside the org or repo name. - Operator action:
- Re-invoke
--policywith a clean<org>/<repo>value — no.., no extra separators. - The control plane and filesystem are untouched; no cleanup is needed before retrying.
- Re-invoke
VALIDATOR_FAULT_SCHEMA
Section titled “VALIDATOR_FAULT_SCHEMA”- Class: template-literal
\VALIDATOR_FAULT_${cls}`emitted byrunValidator(‘schema’, fn)catch —packages/verify/index.js` - Trigger: the
validateSchemacall threw (e.g. AJV crash on a pathological regex, an unexpected reference resolution failure, or an internal assertion). The error string-prefix discriminatesVALIDATOR_FAULT_SCHEMA:from the submission-badschema:prefix. - Message shape: appears as a string entry in
verification.rejection_reasons:VALIDATOR_FAULT_SCHEMA: <thrown message>. - Hint: the verifier itself crashed mid-validation — escalate to ops; do not page the submitter. Inspect the validator stack and patch the verifier.
- Operator action:
- Pull the
VALIDATOR_FAULT_SCHEMA:reasons out ofverification.rejection_reasonsand triage them as a system incident. - Re-run with verbose logging on the schema validator to capture the throw site.
- Patch the validator; the submission is a useful repro fixture, NOT the bug.
- Pull the
VALIDATOR_FAULT_POLICY
Section titled “VALIDATOR_FAULT_POLICY”- Class: template-literal
\VALIDATOR_FAULT_${cls}`emitted byrunValidator(‘policy’, fn)catch —packages/verify/index.js` - Trigger: the policy-validator call threw (e.g. deep-merge corrupted by a prototype-pollution probe, an unexpected policy shape from
loadRepoPolicy). - Message shape:
VALIDATOR_FAULT_POLICY: <thrown message>inrejection_reasons[]. - Hint: as above — verifier-side incident, not submission-bad.
- Operator action: triage as a system incident, patch the policy validator.
VALIDATOR_FAULT_STEPS
Section titled “VALIDATOR_FAULT_STEPS”- Class: template-literal
\VALIDATOR_FAULT_${cls}`emitted byrunValidator(‘steps’, fn)catch —packages/verify/index.js` - Trigger: the step-contract checker threw (e.g. an evidence-shape walk hit an unexpected nesting, a gate-accumulation arithmetic edge).
- Message shape:
VALIDATOR_FAULT_STEPS: <thrown message>inrejection_reasons[]. - Hint: as above — verifier-side incident.
- Operator action: triage as a system incident, patch the steps validator.
VALIDATOR_FAULT_CONTRACT_SCHEMA_VERSION
Section titled “VALIDATOR_FAULT_CONTRACT_SCHEMA_VERSION”- Class: template-literal
\VALIDATOR_FAULT_${cls}`emitted by therunValidator(‘contract_schema_version’, fn)catch —packages/verify/index.js` - Trigger:
validateSchemaVersion(submission, 'recordSubmission')threw — reached when the gate is invoked with an unknown contract key (a programmer error at the call site, not a submission fault). The version mismatch cases are the submission-badCONTRACT_SCHEMA_TOO_NEW:/CONTRACT_SCHEMA_TOO_OLD:reasons below; only a genuine throw from the gate surfaces here. - Message shape:
VALIDATOR_FAULT_CONTRACT_SCHEMA_VERSION: <thrown message>inrejection_reasons[]. - Hint: as above — verifier-side incident; matched by the same
VALIDATOR_FAULT_*prefix family (F-82429f90: operational, thrown-not-persisted). - Operator action: triage as a system incident, patch the version gate / its contract-key wiring.
Consuming rejection_reasons[] — parseRejectionReason
Section titled “Consuming rejection_reasons[] — parseRejectionReason”- Class:
parseRejectionReason(reason)—packages/verify/parse-rejection.js(re-exported from the package rootindex.js). - Returns:
{ class, prefix, detail }whereclassis one of:submission-bad— the submitter fixes the payload (schema:,policy:,policy-config:,steps[<id>]:,provenance:,repo:,submission-contains-verifier-field:,CONTRACT_SCHEMA_TOO_NEW:,CONTRACT_SCHEMA_TOO_OLD:). Hereprovenance:is the genuine-absence case only — a 404 / not-confirmable run, i.e. the submitted run does not exist or does not bind.policy-config:(VERIFY-F1, v1.7.0) is a repo custom-rule predicate that hit an eval-time semantic fault the schema could not catch — an unknown leading field, a numeric operator over a non-number, or a depth/width budget overrun; the repo authored the bad rule YAML, so the submitter fixes it. Its global counterpart is operational — a malformed global predicate surfaces asVALIDATOR_FAULT_POLICYabove.operational— the verifier/tooling faulted; page ops, do NOT bounce to the submitter (theVALIDATOR_FAULT_*family above, matched by prefix family so a future fault class needs no parser edit;provenance-fault:for a provider 429/5xx/401/403 fault confirming the run;scenario-fetch-fault:for the same fault classes — including exhausted timeouts — while fetching a scenario definition; andsubmission-malformed:for a null/non-object payload from a malfunctioning dispatcher). As of the wave-4 hardening these faults are thrown, not persisted: production ingest exits 2 with no_rejectedrecord, so an outage window never poisons arun_idagainst clean resubmission — you will only see theVALIDATOR_FAULT_*/provenance-fault:string forms in records persisted before that change.ingest— an ingest-side load fault (scenario-load:), with typed reasonsparse_error | invalid_id | too_large | schema_invalid | malformed_entry | fetch_cap. A missing definition file (not_found) is NOT in this set — it is not a rejection at all; the submission is accepted and the record carries averification.warningsentry (scenario definition not found for "<id>" — required_steps unenforced). More than 20 scenario-load reasons collapse into a count summary in the persisted record.unknown— unrecognized prefix (including the prefix-less null-submission reason); log + surface raw.
- Usage:
import { parseRejectionReason } from '@dogfood-lab/verify';
for (const r of record.verification.rejection_reasons) { const { class: cls, prefix, detail } = parseRejectionReason(r); if (cls === 'operational') notifyOps(prefix, detail); else if (cls === 'submission-bad') reject(prefix, detail); else if (cls === 'ingest') triageLoad(detail); else log.warn('unknown rejection_reason', r);}- Source of truth: the full prefix taxonomy table lives in
packages/verify/README.md→ “Prefix taxonomy”; the parser enumerates the same set from the actual emitters (verify/index.js,validators/schema-version.js,packages/ingest/run.js).
STATE_MACHINE_<KIND> — BLOCKED, TERMINAL, INVALID
Section titled “STATE_MACHINE_<KIND> — BLOCKED, TERMINAL, INVALID”- Class:
StateMachineRejectionError(packages/dogfood-swarm/lib/errors.js) - Trigger:
transitionAgent()rejected a state-machine transition. Thekindfield discriminates why:STATE_MACHINE_BLOCKED— the transition is legal in the abstract but blocked by a guard (e.g. dependencies not met, override required). Operator’s problem.STATE_MACHINE_TERMINAL— the agent is in a terminal state (complete,rejected, etc.) — no transitions allowed. Caller bug — something tried to advance an already-finished agent.STATE_MACHINE_INVALID— the transition is missing from theTRANSITIONStable. Legitimate disallowed transition (e.g.idle → completeskippingrunning).
- Message shape:
Illegal transition <from> → <to>: <reason>with explicit kind ine.code. - Hint:
e.hintis set per-kind by the throwing site (e.g. “useswarm revalidateto lawfully recover from blocked states” for BLOCKED, “this agent is already complete; check why the caller tried to re-advance it” for TERMINAL). - Carries:
kind,from,to,agentRunId,allowedTransitions[](legaltoset from the currentfrom). - Operator action:
- BLOCKED: look at the
Next:hint — usually points at an override flag or a missing prerequisite. - TERMINAL: the agent is done; the bug is upstream. Inspect the caller for a re-advance loop.
- INVALID: check
allowedTransitions[]for what the state machine will accept from thisfrom. Either reroute the call or, if the transition should be legal, file a finding to add the edge toTRANSITIONS.
- BLOCKED: look at the
INGEST_FAILED
Section titled “INGEST_FAILED”- Class: structured stderr envelope (not a thrown typed error) —
console.error('ERROR [INGEST_FAILED]: …')emitted at the swarm CLI ingest seam (packages/dogfood-swarm/cli.js) and frompackages/dogfood-swarm/persist-results.js. Mirrors the documentedERROR [<CODE>]:shape even though it is printed rather than rendered throughrenderTopLevelError. - Trigger: the
--ingestpath attempted to record the run’s own dogfood submission and the downstream ingest either returnedingested !== true(CLI seam, with the verifier’sreason) or exited non-zero (persist-results.jsseam). Common underlying cause: the swarm-emitted submission failed schema validation inpackages/ingest/run.js. - Message shape:
- CLI seam:
ERROR [INGEST_FAILED]: dogfood ingest did not complete — <reason> - persist-results seam:
ERROR [INGEST_FAILED]: dogfood ingest exited non-zero - Both follow the failure line with
Submission: <path>and a copy-pasteableReproduce: node "<repo>/packages/ingest/run.js" --provenance=stub --file "<submission>"line; the persist-results seam also printsExit code: <n>when available.
- CLI seam:
- Operator action:
- Run the printed
Reproduce:command to replay the ingest in isolation with full output. - The most common cause is a schema-invalid submission — inspect the AJV failure against
packages/schemas/src/json/dogfood-record.schema.jsonand fix the swarm’s submission emitter, not the schema. - Re-run
swarm verify --ingestonce the emitter is corrected. The human-readable summary still printsIngested: NOto stdout so the failure is visible in both streams.
- Run the printed
CRITERION_INTENT_OVERFLOW
Section titled “CRITERION_INTENT_OVERFLOW”- Class:
CriterionIntentOverflowError(packages/dogfood-swarm/lib/errors.js). - Trigger: Assembling a
--jury=prismseat call, when the mandatory section —rubric.objective+ one criterion’scheck+ the wholeout_of_scopeblock — already exceeds prism’s 4000-charintentcap, before any evidence is added. Raised bybuildCriterionIntentinpackages/dogfood-swarm/lib/case-file/prism-jury.js. - Message shape:
rubric.objective + criterion '<id>' + out_of_scope exceed prism's 4000-char intent cap by <n> chars — shorten the objective, split the criterion, or trim the out-of-scope list - Hint:
Next: criterion <id> intent is <n> chars over the <max>-char cap — shorten rubric.objective, split the criterion, or trim out_of_scope.CriterionIntentOverflowErrorsets.hintin the constructor;deriveHintForCode()also has aCRITERION_INTENT_OVERFLOWfallback for round-tripped objects that kept.codebut not.hint(F-4b72faf9).renderTopLevelErrortherefore printsERROR [CRITERION_INTENT_OVERFLOW]:plus thatNext:line. Structured fields (criterionId,headLength,maxChars) remain on the error. - Operator action:
- Shorten
objective, split the named criterion, or trimout_of_scope— the three levers the message names. All three sections are mandatory on this tier: only the evidence pack yields to the cap, so an over-cap mandatory section cannot be resolved by dropping evidence. - Re-run
swarm adjudicate <run-id> --case-file <path> --jury=prism.
- Shorten
- Why this fails fast rather than trimming: everything droppable is already reported per-criterion on the receipt as
criteria[].brief_omitted. An over-cap mandatory section, though, was neither reported nor droppable — it was a fixed cost measured against the budget but never checked. prism’s own pydanticmax_length=4000then rejected the request on every seat uniformly, so the panel returnedinsufficient_contextand the operator was told the jury could not reach the artifact — when the real cause was a deterministic input error knowable without spending a single ~27s seat call. This is the case-file contract’s “anything that does not fit is REPORTED, not silently dropped” applied to the one section that had escaped it.
UNSAFE_RECORD_PATH
Section titled “UNSAFE_RECORD_PATH”- Class:
UnsafeRecordPathError(packages/ingest/persist.js), cause-chained to the underlyingcomputeRecordPathfailure. - Trigger:
writeRecord(), whencomputeRecordPath()throws aftervalidateRecord()has already passed. The gap it closes: the record schema’srepopattern (^[a-zA-Z0-9_.-]+/[a-zA-Z0-9_.-]+$) permits embedded..—../etcmatches — whileisUnsafeSegmentcorrectly refuses it. So a schema-valid-but-unsafe repo reached path computation and threw a bare, unclassifiedError. - Message shape:
record passed schema validation but its path could not be safely computed (repo: <repo>, run_id: <run_id>): <cause message> - Fields:
repo,runId, plus the chainedcause(rendered asCaused by: …). - Operator action:
- Read the
Caused by:line — it names the specific guard that refused (invalid repo format/unsafe repo segment/unsafe run_id). - Fix the submission’s
repoorrun_idand resubmit. The run_id is not consumed — nothing was written, so a corrected resubmission is not a duplicate.
- Read the
- Why a distinct code rather than reusing
RECORD_SCHEMA_INVALID: the record is not schema-invalid — it passed. Reporting it as a schema failure would send the operator to the schema, which is exactly the confusion this code exists to end. The two checks disagree by design: the schema is a permissive contract shared with consumers, andisUnsafeSegmentis the stricter filesystem-safety gate. A submission can satisfy the first and fail the second, and that state now has a name.
CLI_INVALID_VERIFIED_HOW
Section titled “CLI_INVALID_VERIFIED_HOW”- Class: plain
Errorwithe.code = 'CLI_INVALID_VERIFIED_HOW', thrown byverifiedHowError()—packages/dogfood-swarm/cli.js. It throws (unlike the guard clauses below), so it reachesrenderTopLevelErrorand renders the fullERROR [CLI_INVALID_VERIFIED_HOW]:envelope, matchingCLI_INVALID_GLOBS_JSON/CLI_INVALID_THRESHOLD’s format. - Trigger:
swarm close --verified-how <raw>invoked with arawvalue outsideindependent | self_attested | operator_evidence. A missing--verified-howis a separate, untyped guard-clause refusal (see the Usage-errors bullet below) — only an out-of-enum value reaches this typed code. - Message shape:
--verified-how expects one of independent|self_attested|operator_evidence; got '<raw>' - Hint:
pass one of: independent, self_attested, operator_evidence — e.g. \–verified-how independent`` - Carries:
received(the raw input). - Operator action:
- Re-invoke with one of the three accepted values.
- This field is load-bearing, not decoration — review-verified fixes demonstrably reopen less than self-attested ones (Zimmermann et al., ICSE-SEIP 2012).
swarm reopen / swarm close / swarm roadmap — failure modes
Section titled “swarm reopen / swarm close / swarm roadmap — failure modes”- Usage errors (missing
--ids, empty--reasonor--evidence, missing--verified-howonclose, an--asvalue other thanfixed): each guard clause callsconsole.errordirectly andprocess.exit(1)— it never throws, sorenderTopLevelErrornever sees it: there is no typedERROR [<CODE>]:envelope and no untypedERROR: <message>line either. The printed text is a plain, mostly verb-prefixed sentence (e.g.reopen: --evidence "<text>" is required (non-empty) — …, or the bareSpecify --ids F-001,F-002 (reopen is targeted — there is no --all)). No usage synopsis prints alongside any of these — theUsage: swarm reopen .../Usage: swarm close ...synopsis is reserved for the one case where the run-id itself is missing. Unrecognized flags (e.g. a typo’d--this-flag-does-not-exist) are silently ignored, not rejected — verified live against both verbs. Both mutation verbs are dry-run by default — forgetting--applyis not an error; it prints the would-do report and changes nothing. - The narrow
--formatenum:swarm reopen/swarm closeaccept--format=text|jsononly — deliberately excludingmarkdown— and an out-of-enum value throws the typedCLI_INVALID_FORMATfrom their ownclosureFormatError()(cli.js), a distinct call site from the sharedtext|markdown|jsonparser other verbs use. Same code string, narrower contract; the message names the two accepted values. - Ineligible ids — and the reopen/close asymmetry.
swarm closeis idempotent over ineligible rows the waydefer/rejectare: closing an id that is not open, or an id that names no finding in the run, is listed per-id in the dry-run/apply report rather than thrown, so a typo’d or hallucinated id can never vacuously transition anything.swarm reopenis stricter: reopening an id that is not in a closed state hard-refuses — non-zero exit, no report line — rather than reporting it as a skipped row. The two verbs are deliberately not symmetric here (an earlier revision of this page claimed they were): closing is a batch disposition where a no-op member is unremarkable, while a reopen names a specific closed finding to revive and an ineligible target is more likely an operator mistake worth failing on. Either way, no ineligible id is ever silently transitioned. swarm roadmap compilevalidates operator notes at compile time, before any core compile work, via the seven typedROADMAP_NOTES_*/ROADMAP_NOTE_*/ROADMAP_TOO_MANY_NOTES/ROADMAP_INVARIANT_NO_ENFORCER/ROADMAP_ENFORCER_NOT_FOUNDcodes (grouped entry below); nothing is written on refusal — compile is atomic.expiresis never a validation trigger —validateNote()does not inspect it, so a malformedexpirescannot refuse the compile; an unparseable value (e.g. the<N runs>shorthand — only ISO-dateexpiresis implemented today, a disclosed scope gap) is treated as non-expiring, never a refusal. Both buckets land in the artifact —operator_notes(active) andexpired_notes(required, empty-allowed; the cross-run carrier for loud expiry) — androadmap showrenders expired notes as EXPIRED rather than silently dropping them.- Provenance guarantees on every transition: each applied reopen/close writes an append-only
finding_eventsrow —event_type='reopened'forswarm reopen; forswarm close,event_typemirrors the--astarget status ('fixed'today, the only value--asaccepts) rather than a distinctoperator_closedevent type — carrying reason, evidence, and the acting authority. The original closure a reopen reverses is never rewritten — it remains in the event history.
Documented at contract level alongside the verbs’ first shipped wave; the next confirming audit re-verifies this section against the implementation, per this page’s standing discipline.
ROADMAP_RUN_NOT_FOUND
Section titled “ROADMAP_RUN_NOT_FOUND”- Class: plain
Errorwithe.codevia theroadmapError()factory (commands/lib/roadmap-notes.js), thrown byrequireRun()incommands/roadmap.js. - Trigger: the
<run-id>argument matches no row in the control-plane DB the CLI resolved. - Operator action:
swarm runslists real run ids; checkSWARM_DBif the id looks right but the DB is wrong.
ROADMAP_ARTIFACT_MISSING
Section titled “ROADMAP_ARTIFACT_MISSING”- Class:
roadmapError()incommands/roadmap.js— the raw fs error is caught and re-thrown as this named, recoverable state (the F-d875b3c1 compensator lineage). - Trigger:
swarm roadmap showresolving aroadmap_artifactsrow whosepathdoes not exist under the run’slocal_path. - Operator action: either re-run
swarm roadmap compile <run-id>(a fresh sequence supersedes; history is never rewritten) or remove the orphaned ledger row withswarm roadmap compile <run-id> --undo <sequence> --apply— the named compensator documented in the CLI reference.
ROADMAP_UNDO_INVALID_SEQUENCE / ROADMAP_UNDO_NOT_FOUND
Section titled “ROADMAP_UNDO_INVALID_SEQUENCE / ROADMAP_UNDO_NOT_FOUND”- Class:
roadmapError()incommands/roadmap.js(undoRoadmapCompile). - Operator action: the hint names the discovery path —
swarm roadmap show <run-id> --format=jsonlists the sequences that actually exist.
ROADMAP_NOTES_* — the operator-notes validation family
Section titled “ROADMAP_NOTES_* — the operator-notes validation family”| Code | Trigger |
|---|---|
ROADMAP_NOTES_UNPARSEABLE |
the notes seed file exists but is not valid JSON |
ROADMAP_NOTES_SHAPE_INVALID |
the parsed seed is neither a top-level array nor {"notes": [...]} |
ROADMAP_TOO_MANY_NOTES |
more than 7 notes (the T3 Reflexion bound) |
ROADMAP_NOTE_INVALID |
a note missing a non-empty text |
ROADMAP_NOTE_INVALID_KIND |
kind outside theme|open-question|invariant |
ROADMAP_INVARIANT_NO_ENFORCER |
an invariant note without enforced_by — a lesson without a mechanical verifier is not persisted as a lesson |
ROADMAP_ENFORCER_NOT_FOUND |
an enforced_by path that resolves to no file on disk |
- Operator action: fix the named note in the seed file and re-run compile; the per-note report names which note and which rule.
ROADMAP_SEED_NOT_FOUND
Section titled “ROADMAP_SEED_NOT_FOUND”- Class:
roadmapError()viacommands/lib/roadmap-seed.js(resolveRoadmapSeed). - Trigger: any of the four nothing-resolves shapes —
dogfood/roadmap/latest.jsonmissing, unparseable, missing a usablepathfield, or the resolved artifact file absent on disk (explicit=<run-id>resolvesdogfood/roadmap/<run-id>.jsondirectly). - Operator action: run
swarm roadmap compileon the source run first, or drop the flag to init cold.
ROADMAP_SEED_SCHEMA_INVALID
Section titled “ROADMAP_SEED_SCHEMA_INVALID”- Class:
roadmapError()viacommands/lib/roadmap-seed.js. - Operator action: recompile the source run’s roadmap (a conformant fresh sequence supersedes the malformed one), then retry.
DISPATCH_ROADMAP_DIGEST_NOT_FOUND
Section titled “DISPATCH_ROADMAP_DIGEST_NOT_FOUND”- Class:
DispatchPreconditionError(lib/errors.js), like the other sixDISPATCH_*codes above. - Operator action: compile the referenced run’s roadmap, or omit the flag (auto-injection only fires for runs initialized with
--seed-from-roadmap;--no-roadmap-digestsuppresses it entirely).
Cross-references
Section titled “Cross-references”- Hard Gate B (Errors): structured shape (code/message/hint), exit codes for CLI, no raw stacks. See README threat model.
- The state machine these errors come out of: State Machines.
- Where rejected records land when ingest throws
RECORD_SCHEMA_INVALIDorDUPLICATE_RUN_ID:records/_rejected/(Beginner’s Guide → Investigating a failure).
