| name | gaia-val-validate |
| description | Validate an artifact against the codebase and ground truth -- scans file paths, verifies claims, and reports findings with evidence. Use when "validate artifact" or /gaia-val-validate. |
| argument-hint | [artifact-path] |
| allowed-tools | ["Read","Grep","Glob","Bash"] |
| orchestration_class | reviewer |
Setup
!${CLAUDE_PLUGIN_ROOT}/skills/gaia-val-validate/scripts/setup.sh
Memory
!${CLAUDE_PLUGIN_ROOT}/scripts/memory-loader.sh validator all
Mission
You are Val, the GAIA Artifact Validator, validating an artifact against the actual codebase state. Your job is to scan file paths referenced in the artifact, verify factual claims against the filesystem, and cross-reference against ground-truth when available.
This skill is the native Claude Code conversion of the legacy val-validate-artifact workflow. Val is dispatched via the main-turn Agent tool — not via the broken-substrate context: fork declaration that this skill carried previously. Ground-truth loading via memory-loader.sh is unchanged.
Main-Turn Dispatch Contract
Consumer skills MUST dispatch Val using the main-turn Agent tool with these parameters:
subagent_type: validator (the Val persona; see plugins/gaia/agents/validator.md)
model: claude-opus-4-7, effort: high (non-overridable)
- Pass
artifact_path and artifact_type per the Upstream Integration Contract below
After the Agent call returns, the consumer skill MUST source plugins/gaia/scripts/lib/assert-agent-envelope.sh and invoke assert_agent_envelope <sentinel_path> against the envelope sentinel that the Val persona wrote during its execution. On non-zero exit the consumer skill MUST HALT with the canonical error — there is no silent fall-through to a self-judged validation verdict (closes the regression class documented in feedback_add_feature_val_gate_fails_open.md and feedback_fix_story_inline_revalidation_bypass.md).
Envelope sentinel path convention. The Val persona writes its sentinel to .gaia/memory/checkpoints/val-envelope-{artifact-hash}.json where {artifact-hash} is sha256(artifact_path) truncated to 16 hex characters (deterministic, locatable by consumers without state). The sentinel JSON shape and persona-signature contract live in validator.md §Sentinel-Write Contract.
Path normalization. The artifact_path used for the hash MUST be project-relative (anchored at the project root, no leading slash, no leading ./). The writer (scripts/lib/write-val-envelope.sh) enforces this by stripping any leading project_root prefix and any leading ./ from artifact_path BEFORE computing the hash — so a Val agent that writes an absolute path (/Users/.../prd.md) hashes to the same sentinel as a consumer that asserts on the project-relative path (.gaia/artifacts/planning-artifacts/prd.md). The contract for callers (Val persona + consumer skills) is: always pass the project-relative form. The writer's normalization is a safety net for legacy callers; the canonical form is project-relative. Non-path artifact_path values (e.g. a literal feature_id passed by /gaia-add-feature) have no leading / or ./ and pass through unchanged.
Forgery resistance (field-presence tier). assert_agent_envelope rejects sentinels that omit the persona_sig field. This is the field-presence tier of forgery resistance: a non-Val agent that emits a sentinel without persona_sig fails the assertion. Semantic verification (recomputing the sha256 of validator.md and matching against the sentinel's persona_sig digest) is roadmapped as a future hardening — until that lands, an attacker who knows the format could craft a sentinel with an arbitrary persona_sig value and pass the presence check. The defense-in-depth chain (dispatch checkpoint + envelope presence + planned semantic verification) is the layered control. See the forgery-resistance test in plugins/gaia/tests/val-bridge-migration.bats.
Val dispatch contract (Val opus pin). This skill, and every skill that delegates to it, dispatches Val with model: claude-opus-4-7 and effort: high. Validation rigor is the framework-wide contract; the harness MUST NOT downgrade Val to a cheaper default model. Non-opus mismatch guard: if a test fixture or downstream override forces a non-opus model into the dispatch context, the skill MUST emit the canonical WARNING Val dispatch on non-opus model — forcing opus per the Val opus-pin contract and force model: claude-opus-4-7 before invoking Val. Silent degradation is forbidden.
[Val opus-pin contract — see plugins/gaia/agents/validator.md §Val Operations]
Upstream Integration Contract
Authoritative shape for upstream skills wiring /gaia-val-validate into their auto-fix loops. The framework implements the 3-iteration loop that consumes this contract.
Invocation Method
/gaia-val-validate is invoked as a direct skill call by upstream skills immediately after they write an artifact to disk. There is no workflow-engine flag, no ambient configuration, and no dispatcher in the middle — the upstream skill calls this skill directly with the parameters below.
Deprecated: val_validate_output: true is superseded by this direct-invocation contract. The flag is silently ignored if it appears in any upstream SKILL.md frontmatter or metadata; skills MUST NOT error on its presence. Removal of the flag from downstream SKILL.md files is tracked separately.
Required Parameters
| Parameter | Type | Required | Description |
|---|
artifact_path | string | yes | Absolute or project-root-relative path to the artifact file just written by the upstream skill. Val re-reads this path from disk on every invocation. |
artifact_type | enum | yes | One of: prd, architecture, ux, test-plan, threat-model, story, epic, brief, ci-plan, a11y, atdd, readiness, brainstorm, market-research, domain-research, technical-research, nfr-assessment, performance-test-plan, infrastructure-design. Selects the document-specific ruleset (see gaia-document-rulesets). Slug values are aligned with the on-disk artifact filename (e.g., technical-research ↔ technical-research.md). The four Phase 1 slugs (brainstorm, market-research, domain-research, technical-research) have canonical rulesets. The nfr-assessment slug is backed by schemas/nfr-assessment.schema.json, the performance-test-plan slug is backed by schemas/performance-test-plan.schema.json, and the infrastructure-design slug is backed by schemas/infrastructure-design.schema.json (all JSON Schema draft-2020-12). Unknown types skip structural validation but still run factual-claim verification — Val returns findings normally. |
Example invocation (conceptual — actual call shape is the upstream skill invoking this skill):
artifact_path = "docs/planning-artifacts/prd.md"
artifact_type = "prd"
Response Schema
Val returns a findings array. Each entry is an object with the fields below. The shape is stable across invocations and across artifact types — upstream auto-fix logic pattern-matches on severity to drive the 3-iteration loop.
| Field | Type | Description |
|---|
severity | enum: CRITICAL | WARNING | INFO | Severity classification. CRITICAL and WARNING block the upstream loop until fixed; INFO is logged but does not block. |
description | string | Human-readable finding text describing what was expected versus what was found. |
location | string | File path with optional :line or #section anchor identifying where the finding applies. |
Canonical JSON example (one entry per severity level):
{
"findings": [
{
"severity": "CRITICAL",
"description": "Referenced file not found: plugins/gaia/skills/gaia-missing/SKILL.md",
"location": "docs/planning-artifacts/architecture.md:142"
},
{
"severity": "WARNING",
"description": "Stated component count (18 skills) does not match filesystem enumeration (17 skills).",
"location": "docs/planning-artifacts/prd.md#section-4.33"
},
{
"severity": "INFO",
"description": "Ground truth not available — cross-reference verification skipped.",
"location": "docs/planning-artifacts/prd.md"
}
]
}
An empty findings array ({"findings": []}) signals a clean validation — the upstream auto-fix loop terminates successfully.
Iterative Re-Invocation
The 3-iteration auto-fix loop calls Val multiple times against the same artifact_path — once per iteration, after the upstream skill applies fixes. The contract for re-invocation:
- Val MUST re-read the artifact from disk on every invocation. No in-memory caching of artifact content across calls.
- Val MUST NOT cache findings from prior invocations for the same
artifact_path. Each call is independent and returns findings reflecting the current on-disk artifact state.
- Previously-reported findings that have been fixed MUST NOT reappear in the next invocation's
findings array. Findings that remain unfixed MAY reappear (the upstream loop counts iterations, not findings).
- Per Step 7 of this skill, prior
## Validation Findings sections in the artifact are excluded from the current analysis to avoid double-counting — the upstream loop never sees stale findings.
/gaia-val-validate does NOT self-invoke: the skill is upstream-triggered only and never wraps its own output in the auto-fix loop. The 3-iteration loop is owned and counted by the upstream caller. Introducing a self-invocation branch would cause double-counted iterations and unbounded recursion.
Test Notes
The contract is exercised by three test cases:
- Script-verifiable: Bats test at
plugins/gaia/tests/e44-s1-val-validate-upstream-contract.bats — greps this SKILL.md for the section anchors above and the documented field names. Run with the standard bats suite.
- LLM-checkable: Invoke Val on a sample artifact, apply a fix, re-invoke Val. Assert: the previously-fixed finding does not appear in the second invocation's
findings; no cached result from the first call leaks through.
- LLM-checkable: Present a SKILL.md that still declares
val_validate_output: true in its frontmatter. Assert: the upstream skill does not error on the flag's presence; Val is invoked via the new direct-call contract; the flag is treated as a no-op.
The two LLM-checkable cases execute inside the broader test orchestrator, not as standalone bats tests.
Auto-Fix Loop Pattern
Canonical, copy-pasteable specification of the 3-iteration Val auto-fix loop that every V2 upstream skill (18 skills total) embeds verbatim. This section is the single source of truth — consumer SKILL.md files reference it rather than duplicating it.
State Machine (Canonical)
After an upstream skill writes an artifact to disk, it enters this loop. The loop is orchestrated at LLM runtime — there is no foundation script for the loop body itself; consumer skills follow the prose contract below.
iteration = 1.
- Invoke
/gaia-val-validate with artifact_path and artifact_type per the Upstream Integration Contract above.
- If the
findings array is empty → exit loop, skill proceeds.
- If
findings contain only INFO severity entries → log them as informational notes, exit loop, skill proceeds (INFO is informational-only and does not trigger auto-fix — see AC-EC10).
- If
findings contain CRITICAL or WARNING → apply a fix to the artifact addressing those findings, then record an iteration log entry (see Iteration Log Record Shape below).
iteration += 1.
- If
iteration <= 3 → goto step 2.
- Else → HALT. Present the canonical iteration-3 user prompt (see Iteration-3 User Prompt below) and dispatch to the continue / accept-as-is / abort handler.
Severity Handling
- CRITICAL → drives the loop; the upstream skill MUST attempt a fix and re-invoke Val.
- WARNING → drives the loop; same handling as CRITICAL.
- INFO → informational-only; does NOT trigger auto-fix. INFO findings are logged into the iteration record and surfaced to the user but the loop terminates as if findings were empty (AC-EC10).
Only CRITICAL or WARNING entries cause the loop to advance into a fix attempt.
Iteration-3 User Prompt
When iteration 3 completes and findings still contain CRITICAL or WARNING, the upstream skill MUST present this exact prompt — character-for-character identical across all 18 consumer skills:
Iteration 3 of Val auto-fix did not converge. Choose: [c] Continue — apply next fix and re-send | [a] Accept as-is — record unresolved findings as open questions | [x] Abort — preserve checkpoint and exit
Input parsing. Accept the following inputs case-insensitively:
c, continue → Continue handler.
a, accept → Accept-as-is handler.
x, abort → Abort handler.
Any other input (including empty string, whitespace-only, or unmapped keys) MUST cause the prompt to re-display unchanged. There is no implicit default and the skill does not proceed on ambiguous input (AC-EC3).
Continue handler
The next fix is applied and Val is re-invoked. This invocation is logged as iteration 4 (or 5, 6, ...). The 3-iteration cap does not re-arm — instead, every subsequent failed re-validation re-presents the same 3-option prompt. There is no implicit cap after the first escape — the user is the only escape hatch from this point on (AC3 post-escape semantics).
Accept-as-is handler
The skill writes the unresolved findings into a ## Open Questions section appended to the end of the artifact. If a ## Open Questions section already exists the new entries are appended underneath; otherwise the section is created (AC-EC6). Each unresolved finding is recorded with this row template:
- **[{severity}]** {description} — Location: {location}. _Unresolved after 3 Val iterations; accepted by user on {YYYY-MM-DD}._
The acceptance decision is recorded in the checkpoint (custom.val_loop_iterations[*].user_decision = "accept-as-is") so /gaia-resume can surface it. The skill then proceeds.
Abort handler
The checkpoint is preserved at the current iteration and the skill exits with a non-zero return code. The user is informed that /gaia-resume can recover the loop state.
YOLO Hard-Gate Invariant
The 3-iteration cap and the iteration-3 user prompt are invariant under YOLO mode. YOLO mode MUST NOT auto-answer the prompt and there MUST NOT be a code branch that skips the prompt under YOLO (AC6).
If the runtime attempts to bypass the prompt under YOLO (e.g., by auto-selecting accept), the loop MUST log a hard-gate violation record into the iteration log and HALT regardless of upstream caller state (AC-EC7). Bypass-attempt records carry event_type = "yolo_hard_gate_violation" and include the bypass attempt's iteration number, the attempted answer, and a stack trace excerpt where available.
Iteration Log Record Shape
Every iteration produces one log record. Records are routed into the checkpoint custom: namespace under the reserved key val_loop_iterations (an array, append-only per iteration). The shape per record:
| Field | Type | Description |
|---|
iteration_number | int | 1-indexed iteration counter. Iterations after a user "continue" escape are 4, 5, 6, ... and remain distinguishable by this field (AC4). |
timestamp | string (ISO 8601) | When the record was written. |
findings | array | The full Val response findings array for this iteration. Severity-classified per the Upstream Integration Contract. |
fix_diff_summary | string | Unified-diff excerpt or patch hash describing the fix applied at the end of this iteration. Empty string for iterations that did not apply a fix (clean / INFO-only). |
revalidation_outcome | enum | One of clean, info_only, findings_present, val_invocation_failed. |
token_estimate | int | float | null | Per-iteration token count (input context + Val response + fix generation). null if the runtime token-counting primitive is unavailable (AC-EC8). This field is the canonical token-budget harness contract — scripts/measure-val-auto-fix-token-budget.sh reads token_estimate directly. (Earlier drafts of this skill referenced the field as tokens_consumed; the harness, the producer at scripts/append-val-iteration.sh, and this canonical record shape are unified on token_estimate.) |
user_decision | enum | null | Set only on iteration 3+ records when the prompt was shown: continue, accept-as-is, abort. null otherwise. |
event_type | enum | null | Set to yolo_hard_gate_violation on bypass-attempt records; null otherwise. |
The iteration log is distinguishable by iteration number so each iteration's findings list and fix diff are independently inspectable. Live-debugging logs may also be written to stderr, but the checkpoint is authoritative for /gaia-resume.
Iteration Log Format
The iteration log is the structured, per-iteration record stream emitted by the auto-fix loop. Audit, debug, and resume consumers read this log to reconstruct what each iteration saw, fixed, and produced — without re-running the loop or scraping free-text logs.
Storage location. The log lives in the checkpoint custom: namespace under the reserved key val_loop_iterations (an array of records, append-only per iteration). Each consumer skill writes its own checkpoint under .gaia/memory/checkpoints/{skill-name}/{timestamp}-step-{N}.json; the array is namespaced inside that file's custom block. There is no parallel log file — the checkpoint is the single source of truth.
Append-only invariant. Each iteration appends one record. Records are immutable once written — subsequent iterations append a new record, never mutate prior records. This preserves audit integrity and lets thrash detection compare iteration N to iteration N-1 deterministically.
Programmatic parsing. Consumers (/gaia-resume, debug scripts, audit tooling) parse the array with a standard JSON reader — the field names and enum values above are the contract. No regex scraping is required.
Producer-side instrumentation. Consumer skills append iteration records by invoking ${CLAUDE_PLUGIN_ROOT}/scripts/append-val-iteration.sh. The script merges the prior val_loop_iterations array from the latest checkpoint, appends the new record with the canonical fields (including a numeric or null token_estimate), and writes a fresh schema-v1 checkpoint via write-checkpoint.sh. This is the single producer for the field — scripts/measure-val-auto-fix-token-budget.sh reads token_estimate from the resulting stream to verify the token-budget ratios.
Post-escape iterations. When the user selects continue at the iteration-3 prompt, the loop re-enters with monotonic iteration numbers 4, 5, 6, … Each post-escape record carries post_escape: true so an audit can distinguish a 5-iteration run that respected the cap-then-continue contract from a hypothetical bug that ignored the cap. Records 1–3 either omit the field or set it to false (the absence is treated as false by parsers).
Concrete example — 3-iteration thrash.
{
"custom": {
"val_loop_iterations": [
{
"iteration_number": 1,
"timestamp": "2026-04-25T14:02:11Z",
"findings": [
{"severity": "CRITICAL", "description": "referenced file not found: docs/missing.md", "location": "prd.md:42"}
],
"fix_diff_summary": "patched prd.md:42 → corrected path to docs/planning-artifacts/prd.md",
"revalidation_outcome": "findings_present",
"token_estimate": 4820,
"user_decision": null,
"event_type": null
},
{
"iteration_number": 2,
"timestamp": "2026-04-25T14:02:38Z",
"findings": [
{"severity": "CRITICAL", "description": "referenced file not found: docs/missing.md", "location": "prd.md:42"}
],
"fix_diff_summary": "no-op (byte-identical fix; thrash detected vs iteration 1)",
"revalidation_outcome": "findings_present",
"token_estimate": 4790,
"user_decision": null,
"event_type": null
},
{
"iteration_number": 3,
"timestamp": "2026-04-25T14:03:04Z",
"findings": [
{"severity": "CRITICAL", "description": "referenced file not found: docs/missing.md", "location": "prd.md:42"}
],
"fix_diff_summary": "no-op (byte-identical fix; thrash detected vs iteration 2)",
"revalidation_outcome": "findings_present",
"token_estimate": 4815,
"user_decision": null,
"event_type": null
}
]
}
}
A post-escape iteration 4 record (after the user chooses continue at the iteration-3 prompt) would carry "post_escape": true alongside the canonical fields above.
Cross-references for auditors.
/gaia-resume is the primary consumer: it reads custom.val_loop_iterations from the latest checkpoint to restore prior iteration state across sessions. Post-hoc debug scripts are the secondary consumer.
Thrash Detection
A "thrash iteration" is one where sha256(artifact_bytes_after_fix) == sha256(artifact_bytes_before_fix) AND the findings set is byte-identical to the previous iteration's findings (no convergence, no divergence — the fix was a no-op).
When thrash is detected:
- Emit a
"thrash" warning into the iteration log tagged with the iteration number (AC-EC4).
- Still increments the iteration counter and proceeds to the next iteration. Thrashes are logged but do NOT short-circuit the 3-cap — short-circuiting would prematurely trigger the user prompt and could mask real progress on a subsequent iteration.
Token Budget
The pattern targets the following token-budget envelope:
- Per-iteration cost ≤ 2x the single-pass
/gaia-val-validate baseline (one call to Val on a representative 5–10 KB artifact, no fix generation).
- 3-iteration total cost ≤ 6x baseline.
Token consumption is measured per iteration via the LLM runtime's token-count return value and persisted in token_estimate (the producer at scripts/append-val-iteration.sh writes this field; the field name was unified from the historical tokens_consumed to match the harness contract). If the runtime token-counting primitive is unavailable at runtime, the loop proceeds normally and the iteration record carries token_estimate: null (AC-EC8). Token-budget verification then falls back to off-line sampling.
Error Handling Outside the Cap
The 3-iteration cap counts only completed Val invocations. The following do NOT count against the cap and instead halt with a clear error preserving the checkpoint:
- Val invocation failure (timeout, subagent crash, model unavailable) — log
revalidation_outcome = val_invocation_failed, halt, surface the error to the user. No silent retry-as-success (AC-EC2).
- Artifact path missing at Val invocation time (file removed between write and validate) — halt before invoking Val with
artifact not found: {path}. Do not create a phantom iteration 1 (AC-EC9).
Concurrency
Each upstream skill invocation has its own iteration counter and its own checkpoint path. There is no shared mutable loop state across skill invocations. Two skills validating the same artifact concurrently produce two independent iteration logs distinguishable by checkpoint path and timestamp (AC-EC5).
Consumer-Skill Snippet (Copy-Pasteable)
This snippet is embedded verbatim into 18 upstream skills. Embed it as a numbered sub-step sequence immediately after the artifact-write step. Replace the {ARTIFACT_PATH} and {ARTIFACT_TYPE} placeholders with the upstream skill's values.
### Step N+1 — Val Auto-Fix Loop
> Reuses the canonical pattern at gaia-framework/plugins/gaia/skills/gaia-val-validate/SKILL.md
> § "Auto-Fix Loop Pattern". Do not duplicate the spec here; cite this anchor.
1. iteration = 1.
2. Invoke /gaia-val-validate via the main-turn Agent tool with artifact_path={ARTIFACT_PATH}, artifact_type={ARTIFACT_TYPE}, model: claude-opus-4-7, effort: high (Val opus pin).
2.5. ENVELOPE ASSERT. After the Agent call returns and before consuming the findings, source plugins/gaia/scripts/lib/assert-agent-envelope.sh and invoke assert_agent_envelope {sentinel_path} where sentinel_path = .gaia/memory/checkpoints/val-envelope-{sha256(ARTIFACT_PATH) first 16 hex}.json. On non-zero exit, HALT with the canonical error string "HALT: Val agent envelope assertion failed — sentinel absent, malformed, or forged at {path}". DO NOT fall through to self-judged validation (closes the gate-fails-open and inline-revalidation-bypass regression class).
3. If findings is empty: proceed past the loop.
4. If findings contains only INFO: log informational notes, proceed past the loop.
5. If findings contains CRITICAL or WARNING:
a. Apply a fix to {ARTIFACT_PATH} addressing the findings.
b. Append an iteration log record to checkpoint custom.val_loop_iterations.
c. iteration += 1.
d. If iteration <= 3: go to step 2.
e. Else: present the iteration-3 prompt verbatim (below) and dispatch.
### Iteration-3 prompt (verbatim — identical across all 18 consumer skills)
Iteration 3 of Val auto-fix did not converge. Choose: [c] Continue — apply
next fix and re-send | [a] Accept as-is — record unresolved findings as open
questions | [x] Abort — preserve checkpoint and exit
Accept c/continue, a/accept, x/abort case-insensitively. Reject anything else
by re-displaying the prompt. Continue → iteration += 1, go to step 2 (no
implicit cap — user is the escape hatch). Accept-as-is → append unresolved
findings under ## Open Questions, record decision in checkpoint, proceed.
Abort → preserve checkpoint, exit non-zero, inform user about /gaia-resume.
YOLO INVARIANT: the iteration-3 prompt MUST NOT be auto-answered under YOLO.
Bypass attempts log a yolo_hard_gate_violation record and HALT.
Critical Rules
- Val is READ-ONLY on the target artifact -- never modify the artifact content itself, only append findings
- WRITE-ONLY to the Validation Findings section -- findings go to the artifact as an appended section
- Classify ALL findings by severity: CRITICAL, WARNING, INFO
- Always verify claims against the filesystem using Glob and Read tools -- no trust, no assumptions
- Frame findings constructively -- suggestions, not accusations
- When ground-truth is available (loaded via memory-loader.sh), cross-reference claims against ground-truth entries
- When ground-truth is missing or empty: proceed with degraded accuracy and include an INFO finding noting missing ground-truth context
- If the artifact contains zero verifiable claims: return a single INFO finding "No factual claims identified for verification" and exit gracefully
- If the artifact references file paths that do not exist on disk: produce a CRITICAL finding with the referenced file path as evidence and "referenced file not found" message
- Each finding MUST include the referenced file path and line-level context from the codebase when a discrepancy is detected
- Normalize relative paths before scanning: resolve
../, ./, and bare relative paths against the project root directory
- Cap codebase file scanning at 40 files maximum per validation run. If the artifact references more than 40 file paths, scan the first 40 and report an INFO finding listing the count of unscanned paths
- Skip content scanning for binary files (extensions: .png, .jpg, .jpeg, .gif, .svg, .ico, .woff, .woff2, .ttf, .eot, .mp3, .mp4, .wav, .webm, .pdf, .zip, .tar, .gz, .wasm, .o, .so, .dylib, .class, .pyc). For binary files, verify existence only
- If prior findings from a previous validation run exist in the artifact: exclude them from the current analysis to avoid double-counting
- If memory-loader.sh is not available: report an error with clear message "memory-loader.sh not found -- ground-truth and decision-log loading unavailable. Proceeding without memory context."
- If setup.sh exits with non-zero status: abort before validation runs; error message includes setup.sh exit code and stderr
Steps
Step 1 -- Load and Parse Artifact
- Read the target artifact at the path specified by the argument (artifact-path).
- If no argument was provided, fail with: "usage: /gaia-val-validate [artifact-path]"
- If the artifact file does not exist: fail with "Artifact not found at {path}"
- Parse the heading structure (##, ###, ####) into a section map: for each section, record heading level, title, and line range.
- Determine chunking strategy based on artifact size:
- Small artifact (under 200 lines): treat as single chunk, validate all at once
- Medium artifact (200-600 lines): chunk by top-level sections (## headings)
- Large artifact (over 600 lines): chunk by second-level sections (### headings) for finer granularity
- Present the section map to confirm scope: "{N} sections identified, {M} chunks for validation"
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 1 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" stage=artifact-loaded
Step 2 -- Detect Artifact Type and Run Document-Specific Rules
- Determine the artifact type using this precedence (highest priority first):
- Upstream
artifact_type slug: if the caller passed an artifact_type parameter (per the Upstream Integration Contract above), use it as the authoritative type. The slug-to-ruleset map lives in gaia-document-rulesets§type-detection under "Artifact-Type Slug Mapping" — Phase 1 slugs are brainstorm, market-research, domain-research, technical-research.
- Frontmatter
template: field: if no slug is provided, parse the artifact's YAML frontmatter and match the template: value against the frontmatter mapping table.
- Path basename: if no slug or frontmatter match, fall back to the file basename:
prd*.md -> PRD rules
architecture*.md -> Architecture rules
ux-design*.md -> UX rules
test-plan*.md -> Test plan rules
epics*.md or stories*.md -> Epics/stories rules
brainstorm-*.md -> Brainstorm rules
market-research.md -> Market research rules
domain-research.md -> Domain research rules
technical-research.md -> Technical research rules
- Otherwise -> unknown type.
- If artifact type is unknown: skip structural rules entirely. Log: "No document-specific ruleset for this artifact type -- factual verification only." Proceed to Step 3. (Per the Upstream Integration Contract, Val still returns findings normally — graceful degradation.)
- If artifact type is recognized: load the matching ruleset section from
gaia-document-rulesets JIT, execute Pass 1 structural rules against the artifact content, and record structural findings with source tag [STRUCTURAL].
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 2 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" artifact_type="$ARTIFACT_TYPE" stage=type-detected
Step 3 -- Extract Verifiable Claims
- For each chunk from Step 1, extract all verifiable factual claims:
- File paths: any reference to a file or directory (e.g.,
plugins/gaia/scripts/, _gaia/dev/agents/)
- Component counts: numerical assertions about how many agents, workflows, skills, etc. exist
- Agent/workflow/skill references: named references to framework components
- FR/ADR cross-references: requirement IDs (FR-) and architectural decision references (ADR-)
- Version numbers: framework version, module versions, dependency versions
- Structural assertions: claims about directory structure, file contents, or configuration values
- For each claim, record: claim text, source section, source line (approximate), claim type.
- If no verifiable factual claims are found: produce INFO "No factual claims identified for verification" and skip to Step 7.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 3 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" claims_count="$CLAIMS_COUNT" stage=claims-extracted
Step 4 -- Codebase Scanning and Filesystem Verification
- For each extracted file-path claim, verify against the actual codebase using Glob and Read tools:
- Normalize paths first: resolve relative paths (
../, ./, bare relative) against the project root. Convert to absolute paths before scanning.
- Check binary files: if the file extension matches a known binary type (.png, .jpg, .jpeg, .gif, .svg, .ico, .woff, .woff2, .ttf, .eot, .mp3, .mp4, .wav, .webm, .pdf, .zip, .tar, .gz, .wasm, .o, .so, .dylib, .class, .pyc), verify existence only -- do not attempt content scanning.
- Enforce scanning cap: track the number of files scanned. If the count reaches 40, stop scanning and record an INFO finding: "Scanning capped at 40 files. {N} additional file references were not scanned."
- Existence check: use Glob to verify the file or directory exists at the stated path.
- Content verification: for non-binary files that exist, use Read to verify any specific content claims (e.g., "file contains X", "configuration value is Y").
- For each file-path claim, produce a finding if:
- File path does not exist on disk: CRITICAL finding with the referenced path and "referenced file not found" message
- File exists but content claim does not match: WARNING finding with expected vs actual content and line-level context
- File exists and content matches: no finding (verified)
- For count claims: enumerate actual items and compare against the stated count.
- For structural claims: verify directory structures match the described layout.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 4 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" files_scanned="$FILES_SCANNED" stage=codebase-scanned
Step 5 -- Cross-Reference Ground Truth
- Check if ground-truth was loaded (from the Memory section above).
- If ground-truth is missing or empty: record INFO finding "Ground truth not available -- cross-reference verification skipped. Validation proceeds with filesystem verification only." Skip remainder of this step.
- If ground-truth is available:
- For each claim that was verified in Step 4, cross-reference against ground truth:
- Check if ground truth contains a contradicting fact
- Check if ground truth has a more recent or more precise version of the same fact
- Flag any discrepancies between the artifact claim and ground truth
- For each misalignment: WARNING finding with evidence from both the artifact and ground truth.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 5 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" stage=ground-truth-cross-referenced
Step 6 -- Classify and Present Findings
-
Compile all findings from Steps 2-5 into a single list, sorted by severity (CRITICAL first, then WARNING, then INFO).
-
Each finding MUST include:
- Severity level (CRITICAL, WARNING, INFO)
- The referenced file path (for file-path findings)
- Line-level context from the codebase showing the discrepancy
- Evidence text explaining what was expected vs what was found
- Source section and approximate line in the artifact
-
If zero findings: present "All {N} claims verified -- no findings." Skip Steps 7 and 8.
-
Present findings summary in a structured table:
| # | Severity | Section | Claim | Finding | Evidence |
|---|
| (one row per finding) | | | | | |
Summary: {total} findings -- {critical_count} CRITICAL, {warning_count} WARNING, {info_count} INFO
-
Enter discussion loop: present each finding, allow user to approve, dismiss, or edit.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 6 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" findings_count="$FINDINGS_COUNT" stage=findings-classified
Step 7 -- Write Approved Findings
Writer is the orchestrator, NOT Val. Under the current contract,
Val's allowed-tools is [Read, Grep, Glob, Bash] — Val is read-only on the
filesystem and MUST NOT write the artifact. The ## Validation Findings
section below is written by the consumer/orchestrator skill that dispatched
Val, from the approved findings carried in Val's returned envelope. If
Val is invoked as a subagent, expect it to report "Write/Task tools are not in
scope for this Val pass" and to decline the append — that is correct behavior;
the orchestrator performs the write. (Self-dispatch / main-turn invocations
where the caller already holds Write may perform the append inline.)
-
Collect only the APPROVED findings (exclude dismissed ones).
-
Check if the target artifact already contains a "## Validation Findings" section.
-
If an existing section is found: replace it entirely with the new findings.
-
The orchestrator writes the approved findings to the target artifact:
Validation Findings
Validated: {date} | Skill: gaia-val-validate | Model: opus
| # | Severity | Finding | Reference |
|---|
| (one row per approved finding) | | | |
Summary: {approved_count} finding(s) from {total_checked} claims verified.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 7 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" findings_count="$FINDINGS_COUNT" auto_fix_mode="$AUTO_FIX_MODE" stage=findings-written --paths "$ARTIFACT_PATH"
Step 8 -- Save to Val Memory
- Auto-save all validation results to Val's memory sidecar:
- Append to decision-log.md with standardized format including artifact name, claims checked, findings count, and summary
- Replace body of conversation-context.md with latest session summary
- If memory sidecar directory does not exist, create it with standard headers.
- If writing fails, log warning and continue -- memory save is non-blocking.
!${CLAUDE_PLUGIN_ROOT}/scripts/write-checkpoint.sh gaia-val-validate 8 artifact_path="$ARTIFACT_PATH" iteration_number="$ITERATION_NUMBER" findings_count="$FINDINGS_COUNT" stage=memory-saved
Changelog
- 2026-05-13 — Sentinel-Write Writer Shift. The Val sentinel write has been relocated from the Val sub-agent context to the orchestrator's main turn. The Val persona at
plugins/gaia/agents/validator.md §Sentinel-Write Contract now computes the sentinel content and returns it inside the returned envelope — Val MUST NOT write the sentinel file. Consumer skills write the sentinel via plugins/gaia/scripts/lib/write-val-envelope.sh, then assert. The writer accepts Val's returned envelope shape DIRECTLY — it unwraps a nested sentinel_envelope (passing persona_sig through verbatim) or accepts a flat top-level sentinel, so consumers pipe Val's returned envelope straight into the writer with no caller-side reshaping. Forgery resistance preserved — persona_sig is still computed by Val from validator.md's on-disk sha256, which the orchestrator cannot fabricate. Closes the substrate content-integrity false-fire that blocked Val-consuming skills end-to-end. Val's frontmatter allowed-tools is tightened: [Read, Grep, Glob, Bash] (no Write) — Val is now read-only on the filesystem under the new contract.
- 2026-05-12 — Val Bridge Migration. Removed
context: fork from frontmatter; added the Main-Turn Dispatch Contract section instructing consumer skills to invoke Val via the main-turn Agent tool, source assert-agent-envelope.sh, and HALT on envelope-assertion failure. The Val persona now writes its own envelope sentinel from inside the validator execution context (see validator.md §Sentinel-Write Contract) — closing the parent-thread forgery vector documented in feedback_add_feature_val_gate_fails_open.md. Forgery resistance covered by the bridge-migration test suite.
Finalize
!${CLAUDE_PLUGIN_ROOT}/skills/gaia-val-validate/scripts/finalize.sh