Constructdocs
section · guides

Integrity and trust

How Construct ensures that every artifact it produces traces to source, never invents, and surfaces uncertainty honestly.

4 min read·guides / concepts / integrity-and-trust

Construct operates in the same workflow where decisions get made and shipped. That means fabricated claims, over-confident summaries, or embellished rationales don't stay in an assistant session -- they end up in PRDs, ADRs, and handoffs that influence real work. Construct treats integrity as a hard requirement, not a soft preference.

This page explains the four-layer system that enforces it.

Layer 1Canonical rulerules/common/no-fabrication.mdLayer 2Artifact-prose lintlib/comment-lint.mjsLayer 3Intake traceabilityintake_id in frontmatterLayer 4Contract postconditionsspecialists/contracts.json

Layer 1: The canonical rule

rules/common/no-fabrication.md is the policy every specialist reads. The short version:

  • Every load-bearing claim traces to a source. Cite with [source: path#anchor], [source: intake-<id>], or a fetched URL.
  • When a fact isn't in the source, write unknown or [unverified]. Don't paper over a gap with confident prose.
  • Never invent: customer names, quotes, ticket IDs, percentages, dates, file paths, function names.
  • Distinguish observation from inference. Observations carry a citation. Inferences are marked [inferred] and their supporting observations are cited.
  • When uncertain, surface the uncertainty as a question -- not an assertion dressed in hedging language.

The rule applies to every output: intake summaries, classification rationales, PRDs, ADRs, RFCs, knowledge notes, handoffs, review verdicts, plan entries, MCP tool responses.

Layer 2: Artifact-prose lint

lib/comment-lint.mjs enforces a pattern bank against artifact files at docs/specs/prd/**, docs/decisions/adr/**, docs/decisions/rfc/**, docs/notes/research/**, .construct/knowledge/**, .construct/handoffs/**, and .construct/research/**. Non-artifact markdown (READMEs, cookbooks, concepts) is not in scope.

Blocked patterns in artifact prose:

PatternExample violation
Manufactured confidence"The problem is clearly a scaling issue."
Unattributed numeric claim"Latency dropped 30%." (no [source:...])
Customer mind-reading"Users want a faster dashboard." (no citation)
Speculative projection"This will likely improve retention." (no source)

Severity by context:

  • PostToolUse hook (real-time, while the agent is writing): warns but does not block. The advisory prints the offending line and the rule it violated.
  • construct lint:comments (CLI, release gate, CI): blocks with exit non-zero.

Layer 3: Intake traceability

Every artifact produced from an intake packet carries verifiable provenance in its YAML frontmatter:

---
intake_id: construct-1779164832122-onxe
intake_confidence: 0.82
intake_rationale: Matched keywords: stack-trace, regression, auth
---

construct intake done <id> --output=<path> stamps the artifact. The stamp is refused if the artifact already has a different intake_id -- preventing silent re-attribution.

Artifacts that intentionally have no intake source declare it explicitly:

---
intake: none
---

construct docs:verify warns when an artifact in an intake-fed location (docs/specs/prd/**, docs/notes/research/**, .construct/knowledge/internal/**) lacks either intake_id or intake: none. This surfaces drift rather than blocking, so the gate is informational, not destructive.

Layer 4: Contract postconditions

specialists/contracts.json carries machine-checkable postconditions on the researcher-to-product-manager and product-manager-to-architect handoffs. These are the highest-leverage points: they're where intake-derived signals become committed artifacts.

Three check kinds:

CheckWhat it enforces
artifact-has-frontmatter-fieldRequired fields present (e.g. intake_id)
artifact-has-sectionRequired headings present (e.g. ## Sources)
artifact-claims-citedEvery paragraph with a numeric claim has a [source:...] reference

lib/contracts/validate.mjs#validateHandoff runs these at handoff time. When CONSTRUCT_CONTRACT_ENFORCEMENT=block, a failed postcondition blocks the handoff with BLOCKED_CONTRACT status and a list of the specific failures.

The design intent

Fabrication in an agent system is insidious because it compounds. A fabricated customer quote in an intake summary becomes an assumed requirement in a PRD, becomes a design constraint in an ADR, becomes shipped behavior. The four layers above are designed to break that chain early:

  • The rule sets expectations before the work starts.
  • Lint catches violations in real time, while the specialist is still writing.
  • Traceability makes every artifact's origin queryable.
  • Postconditions verify structure before a handoff crosses a phase boundary.

No layer is sufficient alone. Together they make the trust chain auditable.

When something is flagged

SymptomLayerResolution
"Manufactured confidence" warning on an artifactLayer 2 lintReplace the unsupported assertion with a citation or remove the claim
"Uncited numeric claim" warningLayer 2 lintAdd [source: ...] after the number, or move it to a table (excluded from lint)
construct intake done refuses with "different intake_id"Layer 3The artifact was already attributed to a different intake. Use intake reopen + intake done --output= on the correct one
BLOCKED_CONTRACT on a handoffLayer 4Read result.errors for the failing check, fix the artifact, re-run the handoff

See rules/common/no-fabrication.md for the full policy and the bypass philosophy (there is none).