Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
efaa0e0
feat(validators): nothing-built inverse completion gate
anandgupta42 Aug 29, 2026
ef5db19
feat(validators): build-green completion gate
anandgupta42 Aug 29, 2026
606bdee
feat(validators): literal-deliverable (spec-name) completion gate
anandgupta42 Aug 29, 2026
519b189
feat(validators): incremental-config consistency lint
anandgupta42 Aug 29, 2026
9a797a6
feat(validators): dialect-guard lint
anandgupta42 Aug 29, 2026
0b46fff
docs: engine-split assessment for the two parse-level completion checks
anandgupta42 Aug 29, 2026
0cc027a
fix(validators): scope build-green failure counts correctly
anandgupta42 Aug 29, 2026
c85b65d
test(validators): pin the lane's registration list
anandgupta42 Aug 29, 2026
a4eb9eb
docs: placement assessment for the five shipped completion-gate valid…
anandgupta42 Aug 29, 2026
e3f5048
docs: end-to-end evidence for the five completion-gate validators
anandgupta42 Aug 29, 2026
e3a1ffd
fix(validators): close the false positives the completion gates fire …
anandgupta42 Aug 29, 2026
7bdd69f
fix(validators): close the review backlog on the deterministic comple…
anandgupta42 Aug 29, 2026
20ad4de
fix(validators): keep the two run-results exemption axes independent
anandgupta42 Aug 29, 2026
8150eef
fix(validators): second review wave on the completion gates
anandgupta42 Aug 29, 2026
81a5dd1
docs(validators): record the dialect-guard branch-semantics gap as a …
anandgupta42 Aug 29, 2026
b6818c3
fix(validators): inspect elif arms and bound the is_incremental() match
anandgupta42 Aug 29, 2026
314f1c3
fix(validators): close the build-provenance holes the consensus revie…
anandgupta42 Aug 30, 2026
f9ba32f
fix(validators): a delivered-but-untouched deliverable must satisfy t…
anandgupta42 Aug 30, 2026
7122bb1
fix(validators): address the bot review wave on the consensus fixes
anandgupta42 Aug 30, 2026
2145d82
fix(validators): make the unknown-command fallback permissive all the…
anandgupta42 Aug 30, 2026
60990f3
fix(validators): second review sweep — under-firing gates and blockin…
anandgupta42 Aug 31, 2026
3d2f6e9
fix(validators): third review sweep — close 9 of 11 net-new P1 vacuou…
anandgupta42 Sep 3, 2026
631be58
test(validators): adversarial coverage for the dialect-guard packages…
anandgupta42 Sep 3, 2026
677b00b
fix(validators): fourth review sweep — close 6 P1/P2s the third-sweep…
anandgupta42 Sep 3, 2026
fc64a36
fix(validators): fifth review sweep — close 6 more sibling gaps the b…
anandgupta42 Sep 3, 2026
b97c58a
fix(validators): close the round-4 false positive, gate enforcement o…
anandgupta42 Sep 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
682 changes: 682 additions & 0 deletions .github/meta/deterministic-validators-followups.md

Large diffs are not rendered by default.

252 changes: 252 additions & 0 deletions docs/internal/deterministic-checks-engine-split.md

Large diffs are not rendered by default.

625 changes: 625 additions & 0 deletions docs/internal/validator-e2e-evidence.md

Large diffs are not rendered by default.

532 changes: 532 additions & 0 deletions packages/opencode/src/altimate/validators/dbt-build-green.ts

Large diffs are not rendered by default.

229 changes: 229 additions & 0 deletions packages/opencode/src/altimate/validators/dbt-deliverable-names.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,229 @@
// altimate_change start — literal deliverable / spec-name completion gate
/**
* Literal-deliverable (spec-name) gate.
*
* A recurring, fully deterministic loss mode in evaluation traces: the work is
* functionally reasonable but shipped under self-chosen names — a prefix
* added, a plural dropped, a "v2" suffix, or an entirely different noun — and
* the agent then self-verifies against its own renamed output and reports
* success. The literal contract in the task document is never re-read.
*
* This gate re-reads it. It compares the deliverable names the task states
* **literally** against the names the project actually defines, and refuses
* to terminate when a required name is absent.
*
* Conservatism is the whole design:
* - Required names come only from `extractRequiredDeliverables`, which
* accepts a name solely from an explicit declaration marker, a
* deliverables section, or a requirement line — and only when it sits in
* an inline code span and is identifier- or path-shaped. There is no
* fuzzy matching and no inference.
* - When no required-names source is discoverable, `appliesTo` returns
* false and the session is never inspected. Silence, never a guess.
* - Produced names are the union of the filesystem inventory and every
* `manifest.json` name/alias, so an aliased relation cannot read as
* missing.
* - Comparison is exact (case-insensitive only). A near-miss name is
* reported as a possible substitute in the hint, never accepted as the
* deliverable.
*
* Deliberately out of scope: required *column* names. Asserting a column
* exists means resolving `select *`, CTEs and upstream schemas — real SQL
* analysis, not a filesystem inventory — so it is not attempted here rather
* than attempted badly.
*/

import { promises as fs } from "fs"
import type { Validator, ValidatorContext, ValidatorResult } from "../../session/validators/types"
import {
findDbtProjectRoot,
findTaskInstructionFiles,
extractRequiredDeliverables,
collectProducedNodeNames,
modelsModifiedSince,
modelNameFromPath,
resolveWithinRoot,
sanitizeForPrompt,
type RequiredDeliverables,
} from "./validator-utils"

/** The task contract for this workspace, when one is discoverable. */
interface Contract {
/** Path of the first document that contributed a deliverable, for display. */
taskFile: string
/** Every document that contributed at least one deliverable. */
taskFiles: string[]
required: RequiredDeliverables
}

/**
* Read the workspace's literal deliverable contract, merged across EVERY
* task/instruction document that names one.
*
* A workspace can carry more than one document with real obligations — a
* `TASK.md` naming one model and a `REQUIREMENTS.md` naming another — and
* stopping at the first one that parses silently drops every deliverable in
* the rest. A session that satisfies only the first document's contract then
* passes this gate while the remaining required models are entirely absent.
*/
async function readContract(cwd: string, dbtRoot: string): Promise<Contract | null> {
const taskFiles: string[] = []
const models: string[] = []
const files: string[] = []
const modificationModels: string[] = []
const modificationFiles: string[] = []
const modelSet = new Set<string>()
const fileSet = new Set<string>()
const modificationModelSet = new Set<string>()
const modificationFileSet = new Set<string>()
let primarySource: RequiredDeliverables["source"] | null = null
for (const task of await findTaskInstructionFiles(cwd, dbtRoot)) {
const required = extractRequiredDeliverables(task.content)
if (!required) continue
taskFiles.push(task.path)
if (primarySource === null) primarySource = required.source
for (const m of required.models) if (!modelSet.has(m)) { modelSet.add(m); models.push(m) }
for (const f of required.files) if (!fileSet.has(f)) { fileSet.add(f); files.push(f) }
for (const m of required.modificationModels) {
if (!modificationModelSet.has(m)) { modificationModelSet.add(m); modificationModels.push(m) }
}
for (const f of required.modificationFiles) {
if (!modificationFileSet.has(f)) { modificationFileSet.add(f); modificationFiles.push(f) }
}
}
if (taskFiles.length === 0) return null
return {
taskFile: taskFiles[0]!,
taskFiles,
required: { models, files, modificationModels, modificationFiles, source: primarySource! },
}
}

/** True when `relative` exists under either the dbt project or the workspace. */
async function fileExists(dbtRoot: string, cwd: string, relative: string): Promise<boolean> {
for (const root of new Set([dbtRoot, cwd])) {
// Refuse to resolve outside `root` — see `resolveWithinRoot`. A required
// path is task-document content and can contain `..` segments.
const safePath = await resolveWithinRoot(root, relative)
if (!safePath) continue
try {
const stat = await fs.stat(safePath)
if (stat.isFile()) return true
} catch {
// keep looking
}
}
return false
}

export const DbtDeliverableNamesValidator: Validator = {
name: "dbt-deliverable-names",
description:
"After the agent declares done, compares the deliverable names the task document states literally against the model, seed and snapshot names the project actually defines, and refuses to terminate when a required name is absent — catching renames and self-chosen substitutes.",

async appliesTo(ctx: ValidatorContext): Promise<boolean> {
const dbtRoot = await findDbtProjectRoot(ctx.workingDirectory)
if (!dbtRoot) return false
return (await readContract(ctx.workingDirectory, dbtRoot)) !== null
Comment on lines +124 to +127

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the task contract outside the mutable worktree

When the session deletes, renames, or edits the discovered task document so it no longer yields a contract, this completion-time lookup makes appliesTo return false; dbt-nothing-built performs the same live lookup, so both contract gates disappear. A session can therefore remove TASK.md, produce no requested model or build artifact, and terminate successfully because every remaining gate sees zero touched models. Capture the original contract at session start or read it from harness state that the agent cannot mutate.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Real, and deliberately left open rather than half-fixed. Recorded as item 19 in .github/meta/harness-review-followups.md in dd858ef59f so this reasoning is not re-derived later.

The finding is right: readContract and artifactExpectation both run at completion time against the live filesystem, so a session that deletes or rewrites TASK.md makes both contract gates return appliesTo === false and can terminate having produced nothing.

The fix you name is the right one — capture the contract at session start, or read it from harness state the agent cannot write — and neither exists in this lane. ValidatorContext carries sessionStartMs and nothing else; there is no session-scoped artifact store to put a snapshot in. That is the same missing infrastructure follow-up items 4 and 8 need.

The available half-measure is worse than the gap: blocking when a task-document candidate path was written during the session cannot distinguish a deletion from a file that never existed, and fires on the entirely normal case of a task that asks for the document itself to be updated. That converts a false negative into a blocking false positive on correct sessions, which is the wrong direction for a gate that terminates work.

Leaving this open rather than resolving it, since it is not addressed.

},

async check(ctx: ValidatorContext): Promise<ValidatorResult> {
const startedAt = Date.now()
const dbtRoot = await findDbtProjectRoot(ctx.workingDirectory)
if (!dbtRoot) {
return { ok: true, details: { skipped: "no dbt project", session_id: ctx.sessionID } }
}
const contract = await readContract(ctx.workingDirectory, dbtRoot)
if (!contract) {
return { ok: true, details: { skipped: "no literal contract", session_id: ctx.sessionID } }
}

const produced = await collectProducedNodeNames(dbtRoot)
Comment thread
anandgupta42 marked this conversation as resolved.
const missingModels = contract.required.models.filter((name) => !produced.has(name))
const missingFiles: string[] = []
for (const relative of contract.required.files) {
if (!(await fileExists(dbtRoot, ctx.workingDirectory, relative))) missingFiles.push(relative)
}

const details = {
task_file: contract.taskFile,
task_files: contract.taskFiles,
required_source: contract.required.source,
required_models: contract.required.models,
required_files: contract.required.files,
produced_count: produced.size,
missing_models: missingModels,
missing_files: missingFiles,
dbt_root: dbtRoot,
session_id: ctx.sessionID,
elapsed_ms: Date.now() - startedAt,
}

if (missingModels.length === 0 && missingFiles.length === 0) {
return { ok: true, details }
}

// Names this session authored that the task did not ask for. These are the
// likely substitutes behind a missing required name; reported as context,
// never asserted as equivalent.
const requiredSet = new Set(contract.required.models)
const authored = await modelsModifiedSince(dbtRoot, ctx.sessionStartMs)
const unrequested = Array.from(
new Set(
authored
.map((p) => modelNameFromPath(p).toLowerCase())
.filter((name) => name.length > 0 && !requiredSet.has(name)),
),
)

const reasonParts: string[] = []
if (missingModels.length > 0) {
reasonParts.push(
`the task names ${missingModels.length} deliverable(s) this project does not define: ${missingModels.join(", ")}`,
)
}
if (missingFiles.length > 0) {
reasonParts.push(`required file(s) missing: ${missingFiles.join(", ")}`)
}

// The workspace directory is repository/environment-controlled and can in
// principle carry adversarial text; `contract.taskFile` is built from it.
// `dbt-nothing-built` already routes its own task-file path through
// `sanitizeForPrompt` for this reason — this interpolation is the
// matching gap in this file.
const safeTaskFile = sanitizeForPrompt(contract.taskFile, 160)
const hintLines: string[] = [
`The task document (${safeTaskFile}) states these names literally. A model that does the right thing under a different name does not satisfy the task, and self-verification against the renamed output will not detect it.`,
]
if (missingModels.length > 0) {
hintLines.push(` • Create or rename to exactly: ${missingModels.join(", ")}`)
}
if (missingFiles.length > 0) {
hintLines.push(` • Create at exactly these paths: ${missingFiles.join(", ")}`)
}
if (unrequested.length > 0) {
// `unrequested` is derived from ACTUAL FILENAMES ON DISK, which — unlike
// `missingModels`/`missingFiles` (constrained to identifier/path-shaped
// code-span tokens by `extractRequiredDeliverables`) — can contain
// arbitrary bytes, including newlines, on a POSIX filesystem. Spliced
// verbatim into `fixHint`, a hostile or merely adversarial filename
// breaks out of the bullet it is quoted in and lands at instruction
// position in the synthetic retry turn `prompt.ts` builds from this.
const safeUnrequested = unrequested.map((n) => sanitizeForPrompt(n, 80))
hintLines.push(
` • Models you created this session that the task did not name: ${safeUnrequested.join(", ")}. If one of them is a renamed version of a required deliverable, rename the file (and any \`ref()\` to it) back to the required name.`,
)
Comment thread
anandgupta42 marked this conversation as resolved.
}
hintLines.push(
" • If a required deliverable is produced under an alias, set `alias` in its config so the required name is the relation name.",
)

return {
ok: false,
reason: `Deliverable-name mismatch: ${reasonParts.join("; ")}.`,
fixHint: hintLines.join("\n"),
details: { ...details, unrequested_models: unrequested },
}
},
}
// altimate_change end
Loading
Loading