Skip to content

ADR-0017: Contract model — governed subjects, required obligations, typed facts, preconditioned reconciliation

Status Accepted (P2-E5 / D-016; north-star <1h still PENDING operator timed run — OQ-24)
Date 2026-07-21
Deciders Konrad Heimel
Context links supersedes/reshapes parts of 0003 · 0005 · 0007 · 0009 · 0010 · 0011 · 0014 · 0015 · full design roast 2026-07-21 (P1-1..P1-8, P2-1..P2-10) · D-016

Context

A third adversarial review found eight P1 design gaps that would have been frozen into the Phase-3 contracts. The three most dangerous: decisions pinned to the source SHA while the target can move underneath (unevaluated merge results can merge); one broad vouch satisfying every independent safety concern; and resolved challenge threads being treated as authorization the forge never proves. The review also showed the interface sketches conflating four APIs (authored policy, runtime input, replay evidence, presentation/ publication) and the provider protocol being untyped and over-disclosing. This ADR adopts the review's recommended direction wholesale; details freeze via the contract fixture (§8).

Decision (proposed)

1. Merge-result validity (P1-1)

Decision preconditions pin source SHA + target SHA + evaluated merge-result digest. Auto-merge requires either a forge merge-train/queue or re-evaluation after any target movement; merge?sha= alone (source-only CAS) is insufficient. L3 conformance case: advance the target after evaluation → prove rejection or re-evaluation.

2. Required obligations replace anonymous vouch coverage (P1-2)

A binding declares the named proofs required per governed subject (require: [ownership, non-destructive, allowed-fields, …]). A rule proves exactly the obligation it names (prove: {obligation, when}) and declares onFailure: {effect, code}. Auto-merge requires every required obligation satisfied for every subject; denies remain a union. Composition is AND-only in v1 — no obligation anyOf. This replaces vouch/coverage: exclusive (ADR-0007 §3 and amendment 2): adding or removing a pack can no longer silently widen safety, and lint can finally say which safety property is missing.

3. Challenge is acknowledgement; authorization is require-review (P1-3)

challenge = resolvable acknowledgement, nothing more; its wording must never claim an identity requirement. Authorization needs the new require-review outcome: satisfied only by forge-proven eligible approval (approval rules / CODEOWNERS evidence, typed eligible principals). Failed authorization never degrades into an author-resolvable thread; if the forge cannot prove eligible approval, auto-merge is not armed. (The ownership example's "owner must resolve" message was the canonical bug.)

4. One-shot mode must not arm what it cannot revoke (P1-4)

A one-shot run may arm deferred auto-merge only when all controlling inputs stay valid until a forge-enforced event. Decisions depending on expiring authorization facts either merge immediately, stay REVIEW, or require a service/scheduled reconciler that can revoke. facts.max_age (ADR-0015 §3) is thereby demoted from advisory comment to arming precondition.

5. Governed subjects and matcher domains (P1-5)

Each class defines how content becomes stable subjects: entries: {mode: document|list|map, root, identity.pointer} → runtime EntryRef (catalog-service:payments-api). Matchers split into explicit domains — files, values.pointers, fileEvents, valueChanges — ending the path-as-glob-and-pointer overload. v1 supports document-per-resource, map-keyed, and list collections with a declared identity key; unkeyed lists are rejected at lint, not guessed.

6. Typed, minimized provider protocol (P1-7, P2-9)

Providers declare typed outputs (type, cardinality, subject, sensitive, maxAge); requests carry only declared projections (full old/new values need an explicit trusted capability). Responses are versioned envelopes; every fact is resolved | unavailable | invalid | expired with observedAt/expiresAt and subject identity — distinct machine states, never a silently absent map key. Controlling facts never fail open (reaffirms ADR-0004 amendment).

7. Declarative publication (P2-1) and contract ownership (P1-6)

The forge boundary becomes Snapshot → Resolve → Reconcile(DesiredReviewState, Preconditions) → PublicationReceipt; preconditions carry source/target/merge digests, decision hash, fact validity deadline, capabilities. The four-record split (DecisionRecord / ReplayBundle / PresentationModel / PublicationReceipt, ADR-0016 §3) plus EvaluationInput and authored resources each own exactly one concern; the serialized schemas are the public API — the Go interfaces stay internal, no v1 SDK (P2-10). CEL semantics are the commitment; only the Go implementation is swappable (tempers ADR-0013's reversibility claim).

8. The contract fixture is the Phase-3 gate

Before any engine code: one strict, versioned end-to-end fixture covering a pinned target/merge result; a renamed entry in a multi-entry document with stable identity; two independently required obligations; an unavailable/expired typed fact; a missing required approval; and the expected DecisionRecord, redacted PresentationModel, and publication preconditions. All examples validate against the same JSON Schemas from then on.

9. Compatibility rules & scope guards

Safety-bearing authored resources reject unknown fields, duplicate keys/IDs, unknown enums; reports are additive-tolerant; provider majors are negotiated. Named collections are lists with mandatory unique IDs; source order has no meaning without explicit priority. Hashes are over canonical normalized JSON with schema-version domain separation. Do-not-generalize in v1 (extends D-012): no user-defined effects, custom aggregators, obligation anyOf, generic entry-selector query language, or LCD forge API. score is not an effect — points are rule-outcome contributions with per-binding scores retained (P2-4). Adoption reality (P1-8): a timed clean-room secure-setup spike on one supported GitLab tier gates the "under one hour" north star; doctor emits a typed capability/precondition report. The success metric gains an independently defined denominator + adjudicated holdout + false- auto-merge budget (P2-8).

Consequences

  • ADR-0007/0010/0011/0014/0015 are reshaped as stated; ADR-0014's expect.yaml moves to obligation/predicate/finding-code assertions with exact as the recommended opt-in mode for closed-list cases — not the default; an omitted exact is must-contain (P2-2, already partially amended). Examples migrate to prove/onFailure when the schemas land (P2-5) — until then they carry DRAFT markers.
  • e2e build tags are compiled/vetted on every PR; real-forge conformance gates forge-adapter changes and releases (P2-6).
  • The review formally refuted four risks as already-controlled (self-weakening policy, rename-fold, template injection, parser exhaustion) — those designs stand.

Counterpoints considered

  • "Obligations are more ceremony than vouch." — Yes: one require: list per binding. That ceremony is the safety composition — it's what makes pack changes local and lintable. The roast's failure scenario (broad ownership vouch auto-merges an unknown dangerous field) is otherwise unfixable by lint.
  • "Merge-result pinning may be impossible on some forge tiers." — Then that tier doesn't get deferred auto-merge (capability gap, fail closed) — same principle as ADR-0015 §2.