Files
audita/docs/roadmap/implementation.md

288 lines
17 KiB
Markdown

# Implementation Plan: Partial Validator Decision Sets
**Last updated:** 2026-09-25
**Status:** Implemented; all stages completed.
**Source baseline:** `60e2fe7`.
**Audience:** gpt-5.6-terra coding agent.
## Scope and Execution
Implement [partial success for validator decision sets](decision-set-reconciliation.md).
That roadmap owns product intent and target semantics; this document selects the
implementation and verification work. Follow Stage 1 through Stage 3 in order and
record completion evidence beneath each stage. Each stage is a focused agent task.
Read [development guidance](../development.md) and all policies under
[docs/policy](../policy/): [agents](../policy/agents.md),
[architecture](../policy/architecture.md), [documentation](../policy/documentation.md),
[testing](../policy/testing.md), and [release](../policy/release.md). Use graph-first
code discovery, preserve unrelated work, and commit completed coherent units with
plain English messages. Do not introduce approval rounds between stages.
Aim directly at the final implementation. No temporary modes, compatibility shims,
new configuration, extra model calls, or upstream dependency work is required.
Keep PromptKit structural validation/repair and the runner's internal cardinality
checks. Implement the agreed outcomes, not extra restrictions suggested by existing
tests or incidental internal structure. Internal helper signatures and layout can
be adjusted idiomatically while preserving the decisions below.
This replaces the completed span-deletion plan, available in Git at
`60e2fe7:docs/roadmap/implementation.md`. It does not reopen that feature. The final
stage includes correcting historical links that otherwise point to this new plan.
No release preparation, tag, push, CI wait, or paid-provider evaluation is included.
## Selected Implementation Decisions
### Reconciliation boundary
Keep reconciliation in `internal/framework/validators`, after successful structured
completion and before converting the batch into runner-facing results. Replace
`mapLLMResponseToDecisions` with a small pure reconciliation helper; use an ordinary
private result struct containing:
- `Decisions []Decision`: one outcome per requested item, in request order;
- `UnavailableIndices []int`: requested indices without a usable verdict;
- `IgnoredIndices []int`: unexpected indices encountered in response order, including
repeated unexpected entries, for debug attribution.
No generic reconciliation framework or new shared abstraction is needed. Requested
identities come from `batch.Items`, not transcript segment IDs, the whole section,
or any other batch. Expected input identities remain an Audita internal contract.
First collect response entries by requested index and count occurrences, separating
unexpected indices. Then walk requested items once to construct outcomes. This
avoids accepting an early duplicate before seeing a conflicting later entry.
Use these candidate-level reason codes in `validators/models.go`:
| Condition | Internal outcome/reason |
| --- | --- |
| Missing requested entry | `DispositionUnavailable`, `missing_validation_decision` |
| More than one entry for a requested index | `DispositionUnavailable`, `duplicate_validation_decision` |
| Single requested entry failing the existing confidence range check | `DispositionUnavailable`, `invalid_validation_decision` |
| Single usable entry | Existing approved or `llm_rejected` decision with its model reason. |
Unavailable outcomes have `Approved: false` and fixed safe explanatory messages.
Duplicate classification takes precedence over inspecting individual duplicate
values; identical duplicates follow the same rule. Unexpected entries are ignored
before inspecting confidence. Retain the existing confidence check as a local
check for decoded callers/test doubles, scoped to that candidate; do not broaden
this into structural salvage, confidence thresholds, or new semantic validation.
The production JSON Schema still rejects out-of-range confidence before mapping.
An empty response list makes every requested item unavailable. Response ordering
must not change verdicts or which candidate is affected. Preserve the existing
handling of valid negative decisions, including reason text. No candidate may
advance merely because another candidate was approved.
### Operation accounting and ordinary reports
Each actual validation batch remains one `outcomes.Operation`. Use existing
`ReasonValidationDecisionsInvalid` (`validation_decisions_invalid`) for a batch
with unavailable requested candidates. Set `Succeeded: false`, set `ProposalIndices`
to only those unavailable indices, retain execution counts, and leave recovery
reasons empty. Still return usable decisions from that batch alongside explicit
unavailable outcomes, with no Go error for the reconciled model defect.
If no requested candidates are unavailable, set `Succeeded: true`. Add
`outcomes.RecoveryValidationDecisionsFiltered` with serialized value
`validation_decisions_filtered` when extras were ignored. Merge it with actual
`output_repaired` and `service_recovered` reasons in the same recovery operation.
Valid negative decisions do not prevent operation success. A clean response creates
no new recovery reason.
When unavailable candidates and extras coexist, record the failed operation only;
keep ignored-entry evidence in debug. Do not create both an error and a recovery
for that operation. Successful survivors must continue even though the operation
itself records a failure. Retain actual execution counts on all paths.
Reuse existing ordinary YAML structures and version 1. The only new public report
value is the decision-specific recovery reason; affected error indices already fit
`Failure.ProposalIndices`. Do not add ignored entries to `Operation.Filtered` or
`filtered_proposals`, which describe correction proposals, and do not increment
`proposal_responses_with_filtering`. The recovery reason and existing operation
identity suffice for ordinary reporting; detailed extra indices belong in debug.
Update `processreport.safeMessage` for `validation_decisions_invalid` to explain
that some required decisions were unavailable without claiming all batch decisions
were discarded. Generalize the recovery message to include response filtering,
rather than only proposal filtering. Exact prose is not a test contract. Preserve
work-unit error grouping, operation counts, and existing YAML/Markdown/CLI totals.
### Debug evidence and runner records
Existing interaction diagnostics already retain the full decoded response and final
content. Add the reconciliation result to the response diagnostic payload for a
successfully decoded validation response. Perform reconciliation before writing
that diagnostic; retain existing call-error diagnostics and diagnostic-write failure
handling. All reconciled decisions, including unavailable outcomes, receive the
returned response artifact path. This records unexpected indices and duplicates
without inventing candidate ledger entries or an additional debug file.
Carry `validators.DecisionDisposition` through `runner.ValidatorDecisionRecord`
and `runner.ValidatorRejectedChange` in an additive `disposition` field. Populate
it from each actual `Decision`, including partial decision evidence returned on
fatal paths. Keep the existing collection names and application flow; no parallel
unavailable-candidate collection is necessary.
In `processreport`, add `disposition` to `LedgerValidatorDecisionRecord` and map
unavailable candidates to a top-level ledger disposition `unavailable`, instead
of `rejected`. Ordinary editorial/deterministic rejections remain `rejected`.
Use the explicit disposition, not a reason-code allowlist, so existing timeout,
provider-failure, and oversized-input outcomes retain the same distinction too.
Update affected test fixtures to supply dispositions where they represent real
validator outputs. Do not invent a fatal check or compatibility subsystem for a
zero-value disposition in handcrafted fixtures.
Retain candidate identity, literal proposed replacements (including explicit empty
strings), and omission of applied replacements for unapplied candidates. Existing
fatal/interrupted/module-failure handling and debug-off behavior remain. Supplied
model decisions remain visible in interaction evidence even when reconciliation
cannot accept them as authoritative verdicts.
## Stage 1 — Reconcile Model Decisions and Preserve Valid Work
**Completed:** 2026-09-25. Reconciliation preserves usable decisions, attributes
unavailable requested indices and ignored extras, and keeps strict runner
cardinality. Validator and runner boundary checks passed with
`go test ./internal/framework/validators ./internal/framework/runner`.
**Work**
1. Read the validator models, `llm_validators.go`, existing partial-batch tests,
`outcomes/outcomes.go`, and `runner.validateSectionCandidates`.
2. Implement the reconciliation result/helper and candidate reason codes selected
above. Replace the whole-batch mapping-error branch with outcome construction
and correct batch accounting. Add the decision-specific recovery constant.
3. Move successful-response diagnostic assembly after reconciliation and include
its result. Preserve raw/decoded response evidence and diagnostic paths; keep
recoverable call errors, fatal errors, and cancellation on their existing paths.
4. Keep `EnforceDecisionCardinality` and `EnforcePartialDecisionCardinality` strict.
Ensure final validator decisions are complete and unique internally. The runner
should continue passing only approved candidates onward without treating an
operation's failure metadata as a reason to discard valid decisions.
5. Replace obsolete batch-rejection expectations in `llm_validators_test.go` and
relevant runner tests. Preserve cardinality tests that exercise broken internal
validators. Update the repaired-but-domain-invalid tests so actual unavailable
cases remain errors with counts retained and no recovery record.
**Focused verification**
Use a small table at the validator boundary for mixed missing/extra indices,
conflicting and identical requested duplicates, extra-only responses, empty lists,
reordered responses, and a single invalid-confidence decoded test-double entry.
Include noncontiguous expected indices and valid negative decisions. Assert semantic
outcomes and affected indices, not helper layout or exact messages. Include one
controlled case combining successful structural/service recovery facts with ignored
extras and one failed reconciliation retaining counts without recovery reasons.
At the runner boundary, use the real LLM-backed validator with a controlled client
and a later validator: prove a survivor reaches later required validation, a later
rejection prevents application, and unavailable candidates never reach that later
validator as approved. Use successful proposal operation facts where accounting is
asserted; do not check an empty operation list. Reuse existing fixtures/tests rather
than creating a second framework or duplicating every table case.
Run `go test ./internal/framework/validators ./internal/framework/runner`.
**End state:** Usable decisions survive model indexing defects, strict internal
cardinality remains satisfied, and operation facts explain partial failure or
successful extra filtering. No extra model generation is introduced.
## Stage 2 — Carry Dispositions Through Debug and Report Partial Outcomes
**Completed:** 2026-09-25. Runner and ledger records carry explicit
dispositions; ordinary reports distinguish unavailable decision errors from
extra-index filtering recoveries. Focused report, ledger, runner, and YAML
checks passed with `go test ./internal/framework/runner
./internal/framework/processreport ./internal/core/reporting`.
**Work**
1. Add and populate disposition fields through the runner and ledger as selected
above. Map unavailable candidates distinctly and preserve empty replacement
serialization. Check both normal and fatal partial-evidence paths.
2. Update `processreport` messages and review `Build`, `mapFailure`, and `mapRecovery`.
Reuse their existing aggregation; change only assumptions that would misreport
the new operation facts. Core YAML structures and Markdown table layout remain.
3. Extend existing report/ledger tests for an operation with surviving work plus
unavailable indices, extra-only recovery, and a mixed defect operation. Verify
error indices exclude survivors, extra filtering does not count as proposal
filtering, and failed operations do not also generate recovery records.
4. Verify debug ledger entries distinguish genuine rejection from unavailability,
and interaction diagnostics preserve ignored entries and reconciliation reasons.
Unknown response indices must not create fake ledger candidates. Retain existing
redaction and optional-debug-failure tests rather than reproducing them all.
5. Update `docs/integrations/output-files.md` for the new recovery reason and debug
disposition, and `docs/internal/diagnostics-reporting.md` for the mapping changes.
Explain that validation failure indices identify unavailable candidates, not
necessarily every candidate submitted to that batch.
**Validation**
Run `go test ./internal/framework/runner ./internal/framework/processreport ./internal/core/reporting`.
Use semantic YAML/summary assertions; no whole-report prose golden, exact message
snapshot, new schema version, or new rendering mode is needed.
**End state:** Reports and debug evidence accurately describe partial validation
success and handled extras through existing reporting mechanisms.
## Stage 3 — Verify the Assembled Workflow and Finalize Documentation
**Completed:** 2026-09-25. Controlled subprocess runs covered a mixed decision
response and an extra-only response through the CLI, transcript, YAML, Markdown,
debug ledger, and interaction evidence. Fixture execution facts showed one
provider attempt and zero structural repairs per logical call. `go test
./cmd/audita` and `go test ./...` passed. Canonical validator guidance and
roadmap status were updated; the two span-deletion plan links now target the
historical plan at `60e2fe7`.
**Work**
1. Extend the existing local-provider subprocess fixtures in
`cmd/audita/main_integration_test.go`. Add a representative mixed-decision run
with an applied survivor, a legitimate rejection, a missing requested decision,
a duplicate requested decision, and an extra index. Return responses by the
requested correction identities; do not freeze incidental validator call order.
Ensure the survivor passes all remaining required validation. Assert successful
CLI completion, correct transcript, one work-unit error with only unavailable
indices, no reconciliation recovery for the failed operation, and debug
evidence distinguishing rejection/unavailability without unknown candidates.
2. Add or adapt a compact extra-only case proving usable work survives and one
`validation_decisions_filtered` recovery appears. Verify YAML, Markdown, and CLI
agree on totals using semantic assertions. Keep proposal-filtering counts separate.
Existing reporting-disable/debug selection tests remain the owners of those options.
3. In these fixtures give PromptKit its normal configured structural repair allowance,
but supply schema-valid responses. Verify reconciliation adds no generations or
structural repairs using fixture requests/execution facts, without encoding today's
total validator count as a permanent contract. Keep structural-malformation tests
and recoverable batch-failure tests; do not relax the schema to make fixtures pass.
4. Update `docs/internal/validators.md` and any affected canonical descriptions found
by searching for whole-batch rejection claims. Explain tolerant external response
reconciliation versus strict internal cardinality. The structural-validation
boundary remains explicit, and guided recovery remains deferred in `future.md`.
5. Mark the feature roadmap implemented only after the assembled checks pass, and
record actual completion evidence in this plan. Repair the two span-deletion
roadmap links to the old plan with the historical URL
`https://gitea.maximumdirect.net/eric/audita/src/commit/60e2fe7/docs/roadmap/implementation.md`.
Preserve historical release notes and unrelated roadmaps.
**Validation**
Run `go test ./cmd/audita` while developing the fixture, then `go test ./...` once
for the completed implementation. Run `git diff --check` and verify affected
Markdown links. Do not add live-provider checks, a platform matrix, repeated full
suite runs, or release gates solely for this feature. No code is implemented by
this planning pass; these commands are instructions for the implementation sprint.
**End state:** The accepted partial-success behavior is exercised through the real
CLI/library boundary, canonical documentation matches it, and coherent changes
are committed for subsequent release consideration.
## Open Questions
None required before implementation. The implementation decisions above preserve
the agreed product scope. General structural salvage and guided regeneration remain
separate future decisions, not unresolved prerequisites for this work.