Review Documentation Workflow in DocsFab
review-documentation is a read-only DocsFab runtime workflow, not a skill.
It binds one repository revision and keeps audit, deterministic, technical,
editorial, audience, and quality evidence separate. For more information,
refer to Workflow Contracts in DocsFab.
Contract at a Glance
The following table summarizes the contract.
Run the Workflow
Select the target repository and optional revision:
The fixed step sequence is:
audit-documentationin the declared scope (the Review Scope section below explains how the step prompt and a focused scope interact)derive-source-obligations(verify-technical-accuracyinventory-only)lint-documentationprovisional-technical-verification, usingverify-technical-accuracyverify-technical-accuracy, correcting the provisional evidencereview-documentationtest-audience-understandingevaluate-document-quality
Audience testing requires a frozen scenario corpus, oracle, representative audience, independence, and run authority. Without those inputs, the step returns insufficient evidence and names the missing prerequisites.
Check and Verification Evidence
Incomplete repository discovery does not invalidate the locked quality profile. The review records discovery gaps, such as unindexed binary files, in its run warnings and final report. Recognized documentation does not establish complete repository coverage.
The lint step runs applicable repository-native documentation checks through an isolated host executor. Its result records each command, exit status, and output. Checks that cannot run remain unavailable, with their reasons and untested scope. They do not count as passing checks.
Native command execution requires Linux with an available Bubblewrap sandbox. On macOS, the step reports unavailable because the host cannot guarantee cleanup of detached child processes. Run the repository checks separately and retain their results when this limitation applies.
Technical verification must return findings and coverage for the declared targets. Each verified target includes source evidence and the claims checked. Unverified targets remain visible and require human review. A promise to perform verification does not complete the step.
Coverage a judgment reply returns is degraded rather than refused in this
workflow. The host records exactly one entry per declared target: an entry for
a page outside the declared targets is removed, a declared target the reply
omitted is recorded unverified, an out-of-vocabulary status and a status that
claims verification without evidence become unverified, a declared target named
as a locator with line numbers or with a leading ./ is kept and rewritten to
the declared path, and an over-long reason or evidence list is truncated to the
stated answer budget. Each class of change adds one host limitation that names
what the reply said. The answer budget the
step states carries those coverage ceilings and a whole-answer character
budget, so the reply can be planned against them rather than refused after the
fact.
A review whose sealed source-obligation ledger admitted no source has no model answer to ask for: no obligation exists to judge, no issue can cite one, and no coverage claim can be compared with an admitted source. The host records every declared target unverified with one limitation that says so, without spending a model turn, for both the provisional step and the correction. The step record still names the model profile the run is bound to, because a run’s integrity evidence binds every inference step to the installed composition; it records no provider turn, token usage, or tool activity, because none was spent.
Source-obligation derivation first records proposed atomic source facts,
locations, and uncertainty. The ledger is untrusted evidence with
bounded-not-proven completeness. It cannot broaden the frozen review scope,
admit another source, or grant tool authority. The provisional technical step
then compares the documentation with the sources.
A fresh correction reads the sources again and checks the provisional findings and their consistency with the fact inventory. It accounts for each obligation as retained, refined, or rejected. Refinements and rejections retain the original proposal and a source-backed reason. Accounting establishes traceability, not factual correctness.
The current correction model returns a closed version 3 proposal containing the
verdict, coverage, complete fact inventory, obligation accounting, and bounded
issue judgments. Each judgment contains only its ID, severity, category, target
location, and fact links. The host rejects inconsistent links and out-of-target
locations. It derives the unchanged review issues from the exact linked fact
statements, dispositions, uncertainties, source locators, and bound repository
revision. The model does not provide a separate factual finding, evidence
narrative, action, or impact field. A sealed proposal that omits obligation
judgments fails invalid-shape. An applicable dimension with no admitted
facts fails invalid-fact-inventory.
Only the host-derived final technical result supplies current findings and evaluation evidence. This correction protocol adds no workflow step or model call. The private record retains provisional results for inspection. A failed correction does not promote provisional findings to final evidence.
The added read-only phases can increase run time and model cost. Existing provider, tool, and lease limits remain per step. Caller cancellation and any stronger overall deadline cover the complete invocation without resetting at either phase.
When technical verification fails its structured response contract, the failed
step records one fixed category after Technical review response rejected:
(Technical correction response rejected: when the judgment decoder raised
it). invalid-coverage no longer describes the outcome of a review-documentation
judgment reply, whose coverage is degraded as above; it still refuses a
create-documentation reply, whose coverage status the host cannot degrade.
The category is invalid-json, invalid-shape, unsafe-disclosure,
invalid-review, invalid-coverage, invalid-fact-inventory,
inconsistent-verdict, or unknown. These categories identify the rejected
contract boundary. They do not include the rejected response, field names,
values, repository paths, credentials, validator exceptions, or provider text.
The inference category remains malformed-response. Disclosure refusals and an
unavailable host review schema are not retried.
The terminal evaluator consumes these results with the other review evidence. A quality report that fails host validation leaves the evaluation step failed, even when the report is retained for inspection.
The final evaluation must trace each fact that is not documented. One finding
names the fact ID, every linked issue ID, and the exact uncertainty. The host
then adds the fact’s admitted source locators that the finding does not cite
exactly. It adds one evidence entry for each locator before it stages and
validates the evaluation. Each entry has a fixed description that names the
fact and a host provider_id. The evaluator does not copy locators by hand.
An evidence locator field cites a locator when the locator fills one whole
;-separated entry. A locator whose label contains ;, such as
docs/a.md: Setup; Install, counts as one locator. The check reads fact IDs,
issue IDs, and uncertainty only from evaluator-authored text, not from
host-added entries. A finding that lacks the fact ID, an issue ID, or the
uncertainty still fails the technical-trace check.
For the exact final evaluate-document-quality step in this read-only REVIEW
workflow, the host offers validate_quality_candidate when the run already has
a selected quality profile and uses the quality-artifact response contract.
Hosted runs without that profile retain their existing evaluator contract.
Under that contract, the evaluator’s final answer starts with one strict JSON
object that holds evaluation and qualityScore. The report follows that
object as plain Markdown that opens with its heading line, and the host stores
it as reportMarkdown. The host refuses a report that carries answer JSON
outside fenced or inline code, or that leaves a code fence open before more
report text. A lone fence line that ends the report hides nothing, so the host
drops it from the stored report. The host still reads a three-member
{evaluation, qualityScore, reportMarkdown} JSON envelope exactly as before.
Both forms pass the same checks. The evaluator sends its current draft to the
tool as a bounded string. The tool checks envelope content and technical trace
for that submitted candidate digest and returns bounded, content-free
feedback. The evaluator can correct the draft in the same inference session.
Tool calls and subsequent turns use the existing cancellation, time, token,
turn, and tool-call limits. This does not add a workflow step, inference retry, or separate model
call.
A valid tool result is advisory and applies only to the submitted draft. It does not establish schema or profile validity, semantic truth, overall document quality, or permission. The host still decodes and validates the evaluator’s actual final response through the normal schema, profile, technical-trace, and report-binding checks. A changed or invalid final response still fails those checks. The tool is not offered to CREATE, UPDATE, writer, technical, editorial, or audience steps, so their behavior remains unchanged.
The contract also restates, from the locked evaluation schema and profile that
the prompt already carries, the required keys of each evaluation object (for
example, the five keys every layer must contain), the keys or entries that a
single-value condition adds (such as a verified finding’s
verification_evidence, or the conditions entry a conditional pass needs),
the allowed values, pattern, or format of each constrained evaluation field,
and the dimension_id that each constraints key takes. The host fills the
quality evaluation’s run identity, timestamp, profile, evaluator metadata,
contract version (1.0), target scope and revisions, constraint IDs, finding
IDs and disposition defaults, and rule-derived confidence and decisions.
Dimension IDs remain in the evaluator’s response as correlation keys so the
host can preserve each judgment’s association while canonicalizing order.
Other host-written fields are omitted from the evaluator’s requested keys,
including in a focused review’s scope block. The evaluator still owns the
judgments attached to those dimension IDs, including each dimension’s
confidence. The host derives layer and overall confidence and decisions from
the judgments, counting required dimensions assessed not-assessed.
The host writes outcomes.human and outcomes.agent from an admitted audience
evaluation artifact only. When no such artifact is admitted, both arrays are
empty; model-authored outcome claims and placeholder attempt IDs are not
carried into the evaluation. The host records that evidence gap in the
limitations when the selected profile requires it.
The current EvaluateStepEvidence binding records evaluator, tool-policy, and
acceptance-policy digests, but no author-independence attestation. The host
therefore records independent_of_authors: false with that limitation; the
identity digest alone is not evidence of independence.
The current full-artifact schema has no separate reference-item array for technical or editorial issue IDs. The host preserves the evaluator’s evidence and carries locators from admitted technical verification; it does not invent issue-reference items. A future record assembly can resolve those references from sealed earlier artifacts.
When the host replaces derived fields or supplies metadata, the report records counts only. It does not repeat evaluator-provided values in the disclosure.
After a pattern or format refusal, the retry names the schema’s pattern or
format at the refused location. After a shape refusal of the leading object,
the retry names the answer’s two keys, which of them the object lacks, and
its undeclared keys. After a missing required key in an object that also has
an undeclared key, it names both. After a missing technical fact ID, it names
the not-documented facts of the admitted technical verification that no
finding names. A reply key is named only when it is a plain identifier, the
shared credential detector matches no part of it between separators, and no
segment of it is a long mix of letters and digits
(inference.echoedKeySegmentRandomChars); any other key is only counted.
When the evaluator’s answer is cut at the output limit, continued, and still refused, the step retries within its existing retry budget instead of declining the attempt. Other steps still decline such an answer, because a retry would pay the whole output budget again.
Answer Through a Tool Call
On a route whose installed composition recorded an answer submission, the
evaluator answers through a tool call instead. It calls
submit_quality_evaluation once, with evaluation and qualityScore as its
two arguments. When the turn ends, the host decodes the call from the raw
argument text the serving stack forwarded, never from the harness’s parsed object, so
the arguments meet the same strict JSON as the text answer: a duplicate key, a
raw line break inside a string, trailing text, and a third argument are each
refused with their own quality-submission-* code. Some serving stacks send
evaluation or qualityScore as one JSON string that holds the value. The
host decodes that string exactly once, with the same strict parser, and admits
only what the value itself would be admitted as: an object for evaluation,
an object or null for qualityScore. A string that holds another encoded
string is refused, and the attempt records how each member arrived. After it admits the submission, the host asks for
the report in a separate turn, with tool_choice: "none" when the route
recorded that it serves one. The report meets the same report rules as the text
answer, under quality-submission-report codes, and the host stores the same
candidate the text answer would store for the same evaluation, score, and
report. A reply with no submission call is refused as
quality-submission-missing, and a submission cut at the output limit is
refused as quality-submission-cut, never continued. On this path,
validate_quality_candidate takes the same two arguments and decodes them the
same way.
The route’s recorded capability comes from the qualification probe,
docsfab doctor --live --answer-submission. An unmatched endpoint that serves the same model at the
composition’s proven origin borrows it; the same model behind another origin
does not. DOCSFAB_ANSWER_SUBMISSION=text-final keeps the text answer on every
route. Every evaluator attempt records which path it took and why. Refer to
Runtime CLI and Configuration in DocsFab.
Review Scope
Two modes. The scope is frozen before any model turn and is recorded in the run record, so a focused run and a baseline run are never confused for each other. The following table describes the two modes.
The following table describes each scope mode.
For hosted procedures, follow Review Documentation From Slack or Ask a Documentation Question From Slack.
In live-agent, name repository-relative Markdown paths in your instruction:
For a one-off job, select a page with --context-path:
An explicit --context-path takes precedence over paths mentioned in the
instruction. Without it, named Markdown paths select the review targets.
Missing or unrecognized targets stop the run before inference. Instructions
that mix named pages with supporting sources, exclusions, or whole-set scope
require an explicit page selection or a clearer target-only instruction.
The same page set binds every step and final quality validation. Supporting
source reads do not expand the reviewed set.
The final quality evaluation’s target, evaluation.target.paths, belongs to the
host, focused or not. The host derives it once, before inference, from the
selected pages in the following ways:
- A focused run binds exactly the named pages.
- A run without named pages reviews every public tracked documentation page. It
binds the shortest exact list that covers them. A directory such as
docsstands for every page under it, but only when every documentation page under that directory is in scope and no tracked-entry gap could hide one there. The repository root is never an entry. A repository whose public pages sit under a few directories binds a few entries; pages beside out-of-scope documents stay individual entries. - A target with more than
reviewScope.maxTargetsentries stops the run before inference withreview-scope-max-targets-bound. Name pages in batches, or review one page with--context-path. - A run whose host-written technical coverage, one entry per target page,
alone exceeds
technicalCorrection.proposalMaxBytesstops before inference withtechnical-correction-proposal-max-bytes-bound. This can happen to a run without named pages in a large repository. The message names the observed bytes and the number of pages. Review one directory or a named set of pages at a time. Each page costs its path’s UTF-8 bytes plus 255 bytes, so a focused set ofreviewScope.maxTargetspages passes this check when its paths average at most 768 UTF-8 bytes. Every ASCII path withinreviewScope.pathMaxCharsdoes. Long non-ASCII paths can still be refused, becausereviewScope.pathMaxCharscounts characters, not bytes.
The evaluator prompt states this target, with the page count of each directory
entry, and asks the evaluator to leave evaluation.target.paths out of its
answer. Before validation, the host writes the target into the evaluation, so
the evaluator never chooses or copies its scope.
When the evaluator writes a different, non-empty set anyway, the report records one host limitation. It gives counts only: how many in-scope pages the evaluator’s set covered, and how many of its paths lay outside the scope.
The bound target labels what the evaluation covers. It does not show that the evaluator read every page. Partial evidence appears in the evaluation’s dimensions and limitations.
The following rules apply to every focused run:
- Every named path must be a repository-relative Markdown file tracked at the
frozen revision. The path is validated structurally, not as prose: a
leading
/or~, a backslash, an empty,.or..segment, a control character, whitespace or a comma anywhere in the path (thetarget=grammar splits on both), a URL-shaped or credential-shaped value, or a non-.mdsuffix is refused, while an ordinary tracked page whose path contains ahome/,root/orUsers/segment is accepted at every layer. A path that is not tracked fails the run before any model turn rather than quietly narrowing it, so a typo cannot produce a smaller review that still reports success; the reason is written as afrozen-inputdispatcher failure line. - Pages are reviewed in the order named.
- How many pages one run covers is decided by a budget derived from the
measured page sizes, not by a fixed count. Pages the budget cannot cover are
recorded as deferred in the run record and counted in the dispatcher
receipt. The model is instructed to name them in its output, to state that
the run reviewed N of M tracked Markdown pages, and to give the exact
continuation command: the same
/docsfab run review-documentationcommand with the registered canon digest, originalreviewRevision=, andtarget=set to the deferred pages. Nothing is silently dropped. The GitLab summary and the project-bound Slack terminal projector render the same bounded line; Slack reads and digest-checks the retained run record before posting once to the originating thread. - The scope is delivered to every workflow step as authoritative instruction, not as untrusted evidence, and the scope digest is bound into each step’s execution lease identity. The host binds the resolved scope — reviewed paths, deferred paths, digest, and corpus count — and refuses a terminal run record whose scope differs from it.
- A focused run with exactly one page uses
change-scoped/get-neighborhoodcontext for that page. The retained single-page fixture succeeded through all eight steps with 31,728 compiled context bytes, while repository discovery fell from six executions to one. - A multi-page focused run keeps the catalog’s
corpus/expandcontext level. The context index covers only the selected pages plus the other pages and navigation files in each directory on a selected page’s path, up to the repository root. Expansion starts from every selected page, and every selected page must appear in the compiled context, or the step’s context preflight recordsfailedwithtarget-not-applicable. A documentation-set run keeps the unscopedcorpus/expandcontext. - Focused indexing classifies a symlink by what the selected pages need
before it rejects anything. A symlink in a directory the pages do not need
is never read. A symlinked page in a directory on a selected page’s path is
skipped. A symlinked or executable navigation file in such a directory
records the step’s context as
degradedwithnavigation-coverage-incomplete, so the review continues but never claims complete navigation. A selected page, or a directory on its path, that is a symlink recordsdegradedwithnavigation-alias-refusedand acausethat names it. DocsFab does not follow the symlink, so the review steps run without a context packet, and the run’s LIMITATIONS name the symlink. A navigation entry that names a page outside the selected set, or a skipped symlinked page, is left out of the context and is not reported as broken. A navigation entry that names a missing page the index would have read is named as a navigation limitation, and the step’s context recordsdegradedwithnavigation-coverage-incomplete; the review continues. - The host still appends the authoritative scope block after the catalog prompt, and that block takes precedence over a contradictory instruction.
Merge-request runs: the blast radius
A review triggered by a merge request names no targets, so the host computes
them. The computation is deterministic, git-only, and runs at dispatch from the
project mirror, before any model turn: it recomputes the merge base locally
between the target branch tip held in the project mirror and the frozen head,
takes a bounded git diff --name-status, and scans the tracked Markdown at the
head tree for links, navigation membership, transclusions, and cited source
paths. No revision reported by the forge enters the computation, and the host
does not call the forge’s changes API.
The target tip is fetched into the mirror when the change is drained for
dispatch. If that fetch fails, the host uses the mirror’s own copy of the
target branch, last refreshed by activation or by an earlier sync. A stale tip
can only move the merge base backwards along the target branch, which widens
the diff and the radius; it never narrows them. When no local merge base
exists, the diff is taken against the target tip itself, which also only
widens it. If neither ref resolves, or the diff itself cannot be read, no
changed page is known and the run stays at documentation-set with
scan-unavailable.
A page is in the radius for exactly one reason, the highest-ranked one that applies. The following table lists the reasons.
Neighbour reasons are one hop from the changed set only, never transitive: a two-hop closure reaches most of the corpus and would be documentation-set scope wearing a focused-set label. Links inside code fences and code spans are documentation about a link, not a link, and are ignored.
The reference model head is the one exception to ordering by reason. Unless the
diff changed the head itself, it is reviewed last however the change reached
it, because its size would otherwise displace the pages the change touched, and its recorded reason still says how
the change reached it: a change that links to the head records outbound-link
and does not reorder the budget. Its slot at the 256-page ceiling is reserved
by path for the same reason, and the continuation command names it first.
Size never widens a run, and neither does a structural change or a scan failure. The whole diff is enumerated and every path kept; the whole radius is computed and ranked; the job’s budget decides how much of it this run covers, and the rest is deferred. The scope reason records what shaped the radius, as the following table shows.
When no page and no reference model head is in reach, and when no changed page
is known, the run stays at documentation-set with scan-unavailable.
A reason other than within-budget does not mean the whole radius fit. Every
focused merge-request run is bounded by the job’s budget, not guaranteed to
fit. Pages the budget could not cover are deferred and carry their own
over-budget reason, so within-budget alongside a non-empty deferred list is
the normal case.
Bounds that do exist, and what happens at each
The diff is never truncated and size never escalates, but the following three structural bounds are real and each one is reported rather than hidden.
Merge-request blast-radius selection is always on. The optional sealed
runtime.reviewScope project policy only overrides its defaults, together:
131,072 page bytes, 300 tool calls, three calls per target,
19,000 milliseconds per provider turn, 2,500 basis points of fixed overhead,
256 listed paths, and a 2,048-character continuation. The byte value is the
128 KiB corpus capsule ceiling at four bytes per estimated token; 300 preserves
25% of the 400-call hard ceiling; three calls per page and 19 seconds per turn
come from the 2026-09-11 pilot; the overhead reserve covers startup, planning,
and summary work. Each field has a bounded operator override through
amend-dispatcher-composition.sh; list and command overrides may narrow the
protocol maximum but cannot widen it. Credential, authority, exact-revision,
and safety checks are not tunable.
For the retained remainder, follow Continue a Hosted Documentation Review. Escalation remains terminal; an escalated run is never retried at a narrower scope.
What focused-set does not do
focused-set narrows what is reviewed and reported. It is not a filesystem
boundary, and the tools are not confined to the named pages. That is deliberate:
verify-technical-accuracy has to read the source a documentation claim depends
on, and audit-documentation has to read navigation and the reference model.
Confining reads to the named pages would break those steps rather than focus
them. The review reports findings only against the named pages, and the run
record carries the reviewed set, the deferred set, the scope digest, and the
corpus count so the coverage claim can be checked. The corpus count is every
tracked *.md file at the frozen revision — skill pages, fixtures, and
planning notes included — not only the pages under docs/, which is why the
coverage statement says “tracked Markdown pages” rather than “documentation
pages”.
Output and Authority Boundary
The terminal evaluator consumes the preceding evidence streams. It preserves the layered vector, human and agent outcomes, hard failures, gaps, and limitations separately. It produces the artifacts and locators required by the locked evaluation skill and validates the report summary against the canonical quality-report schema. The skill and schema own how the layered decision, hard failures, and rubric availability interact. Missing inputs and unavailable evidence remain visible in the report; do not infer the overall decision from rubric status alone.
The workflow does not edit source files or perform SCM, publication, merge, or release-approval actions.
JSON Result
Refer to Review JSON Result Reference.
Authoritative Sources
This page is based on the following sources:
- Workflow manifest
- Inventory-only verification skill
- Technical verification skill
- Audit skill
- Quality-evaluation skill
Related Topics
For more information, refer to the following topics:
- Run a Documentation Workflow in DocsFab
- Skill Behavior Contracts in DocsFab
- Documentation Constraint Evidence in DocsFab
Moved Topic Links
Earlier section bookmarks lead to the canonical topic below.
Refer to DocsFab Review JSON Result Reference.