@@ -346,6 +346,12 @@ remain write operations. Catalog changes require realistic multi-call agent
346346evaluations, including held-out queries, tool-call count, errors, latency, and
347347context size; scripted schema checks alone do not establish good tool choice.
348348
349+ Recovery-plan actions are sealed variants. The concrete input type derives the
350+ action discriminator, the output schema advertises the variants with ` oneOf ` ,
351+ and decoding rejects unknown, mismatched, or multiple payloads. Application
352+ code therefore receives a typed action rather than validating a discriminator
353+ against a nullable argument bag.
354+
349355The canonical source-audit workflow is a machine-readable contract exposed by
350356` workflow.get_source_audit_contract ` :
351357
@@ -430,6 +436,42 @@ rate capacity. Only replayable reads are retried. Backoff honors GitHub rate
430436headers, is bounded, observes context cancellation, and redacts URL userinfo
431437before retry metadata is persisted.
432438
439+ Repository identities are parsed at input, provider, and persistence
440+ boundaries into a private, comparable ` domain.RepoRef ` . Interior code cannot
441+ construct an owner without a repository name, carry whitespace, or bypass the
442+ owner and repository grammar; it receives a parsed identity and uses explicit
443+ accessors. The zero value is reserved for optional scope and must be tested
444+ with ` IsValid ` . JSON decoding reparses the identity, and larger domain records
445+ hold it in named fields so its codec cannot be promoted over the enclosing
446+ record.
447+
448+ Pull-request merge knowledge is likewise a parsed ` domain.MergeStatus ` , not
449+ independent ` merged ` , ` merged_known ` , and ` merged_at ` fields. Constructors make
450+ unknown, observed-unmerged, and observed-merged outcomes explicit. SQLite and
451+ GitHub adapter reads reject contradictions such as an unknown outcome marked
452+ merged or an unmerged outcome with a merge timestamp; interior code cannot
453+ create those combinations. The relational schema keeps scalar columns for
454+ querying, but rows are reparsed before they enter application models.
455+
456+ Durable run and job lifecycles are read through private state values that bind
457+ statuses to their timestamps. Running work cannot be completed, queued jobs
458+ cannot already be started, terminal work requires a completion time, and only
459+ cancelled or cancellation-requested jobs carry a cancellation time. Terminal
460+ run transitions are conditional on the stored running state, while job
461+ transitions update the status and required timestamps atomically. Corrupt or
462+ unknown persisted combinations fail at the corpus boundary.
463+
464+ JSON inputs that express alternatives remain wire-compatible discriminated
465+ objects, but they are parsed before any durable job is submitted. Thread sync
466+ becomes either repository discovery with repository-only filters or an exact
467+ thread set. Portfolio sync becomes either authored discovery or an explicit
468+ pull-request set. Actor identity becomes either a canonical login or a node ID,
469+ and coverage becomes either a repository target or an exact typed thread.
470+ Workers receive these private variants rather than the original field bags, so
471+ mode-specific fields cannot be silently ignored and identity strings are
472+ canonicalized before duplicate detection. The normalized wire form, not the
473+ caller's mutable slices or pointers, is what the durable job records.
474+
433475## Acquisition and workspaces
434476
435477Acquisition and workspace packages invoke ` git ` directly with prompts, hooks,
@@ -453,6 +495,30 @@ host paths. The application resolves each ID and verifies that it belongs to
453495the selected investigation before persisting executable state. The explicit
454496CLI remains a local-user interface and may accept a directly supplied path.
455497
498+ Observation definitions cross command and MCP boundaries as untrusted specs.
499+ The application parses a complete base-and-candidate contract before it enters
500+ the evidence service. Parsed observations have a private representation: their
501+ source and artifact-path relationship is established once, default occurrence
502+ is normalized, and regular expressions are compiled once for execution.
503+ Persistence decodes through the same parser, so malformed stored contracts do
504+ not re-enter the trusted model. Execution therefore consumes parsed values and
505+ does not repeat structural validation or regular-expression compilation.
506+
507+ Durable workflow JSON is parsed again on read. Concern, investigation,
508+ hypothesis, opportunity, validation, and evidence discriminators cannot enter
509+ application logic as unchecked strings; legacy empty states are canonicalized
510+ only where their historical meaning is unambiguous. Telemetry metrics decode as
511+ either an available value or an unavailable reason and reject payloads claiming
512+ both. External validation receipts atomically store their synthetic definition
513+ and run, while external evidence manifests atomically store the complete claim
514+ set. A failed import therefore leaves no orphan definition or partial manifest.
515+
516+ Bulk local-metadata and collection inputs are fully parsed before writable
517+ corpus access. Collection references are stored in canonical repository,
518+ thread, or UUID form, and malformed later members cannot follow earlier writes.
519+ Thread projections similarly parse kind, lifecycle state, repository key,
520+ and number before a transaction begins and again when SQLite rows are read.
521+
456522## Search and analysis
457523
458524Search uses the local SQLite corpus and FTS5 indexes; agents query bounded
@@ -468,6 +534,14 @@ Snapshots created before manifests were introduced report
468534` indexed_coverage_unknown ` ; their zero skip counts are never presented as proof
469535of complete coverage.
470536
537+ Repository coverage uses collection membership to represent presence: a
538+ returned ` domain.FacetCoverage ` is necessarily present, while a missing facet is
539+ absent from the collection. Its private constructor binds the facet name,
540+ observation time, completeness, and non-negative count. Immutable code-index
541+ artifacts similarly use their digest-bound manifest as the sole in-memory
542+ authority; duplicated query columns are checked against that manifest while
543+ decoding and discarded rather than exposed as a second source of truth.
544+
471545Title, labels, body, and hydrated evidence are materialized into one search
472546document per thread and ranked by one BM25 invocation. Ranks from the legacy
473547thread and facet indexes are never compared; the facet index is used only to
0 commit comments