Governance and ingestion¶
How changes reach a protected branch: who has to approve them, which ones may merge without a person, how merge Conflicts are decided by policy, and where disagreements between Sources wait. Each rule has a page of its own; this one ties them together and ends with a worked configuration.
Where the rules live¶
| Rule | Set in | Default |
|---|---|---|
| Protected default branch | keeper.domains.protect_default_branch |
on: the default branch changes only through Proposals |
| Approvals count | keeper.proposals.required_approvals, Domain setting required_approvals |
1 |
| Approvals from stewards | keeper.proposals.required_approvals_from_stewards, Domain setting of that name |
0 (off) |
| Roles that must approve | keeper.proposals.required_roles, Domain setting of that name |
none (off) |
| Stewards | schema: domain.stewards, kinds.{Kind}.stewards |
none |
| Ingestion mode of extraction | scribe.extraction.mode, Domain setting extraction |
doubts_only |
| Merge of a Source run | the Source's target_branch_policy.merge_policy and local_edits (schema) |
proposal, review |
| Automatic merges into protected branches | scribe.sources.auto_merge_protected, scribe.extraction.direct_merge_protected |
on |
| Merge policies (who wins a Conflict) | Domain setting merge_policy |
text merge on, nothing else |
| Open contradictions at merge | keeper.reconciliation.merge_check, scribe.reconciliation.merge_check, Domain setting reconciliation_check |
warn |
| Automatic merges of agent writes | keeper.agent_writes, Domain setting agent_writes |
off |
| Vocabulary learned from resolution misses | store.resolve_misses, scribe.consolidation |
off: proposals only, always reviewed |
Installation settings are in the configuration file (reference);
give Keeper and Scribe the same file, since both evaluate the approval rules and the extraction
bounds. Domain settings are given when the Domain is created (POST /api/v1/domains with
settings); afterwards PATCH /api/v1/domains/{d} replaces only extraction. A Domain's
required_approvals, required_approvals_from_stewards and required_roles replace the
installation's values; its extraction may tighten them and loosen only what
scribe.extraction.domain_may_loosen allows; its reconciliation_check can only be stricter.
Stewards and Source policies are part of the schema, so they change through a reviewed schema
change like the rest of it (modeling guide).
Approvals¶
A Proposal into a protected branch merges when it has no open Conflict and its approvals are in (design 10.3):
- Count.
required_approvalscounted approvals; authors' own approvals are not counted unlesskeeper.proposals.allow_self_approvalis on, and an outstandingrequest_changesreview blocks the merge (changes_requested_blocks). - Stewards. With
required_approvals_from_stewardsabove 0, that many of the approvals must come from the Proposal's assignees: the principals holding a steward role of the Kinds it touches who may read those Kinds and review it (design 10.7). The Proposal then needs the larger of the two counts in all. When steward roles are declared but too few principals can approve, the merge answers 409stewards-unavailable. - Roles. Each role in
required_rolesneeds one counted approval from a principal holding it who may review the Proposal; it does not raise the count. A merge without one answers 409approvals-requiredwithmissing_roles.
Reviewers find their work with assigned=me on GET .../proposals and
GET .../reconciliation (CLI lorebase proposal list --mine, lorebase reconcile list --mine;
"Assigned to me" in the web app).
Automatic merges¶
Three kinds of merge can happen without a person pressing Merge:
- Source runs with
merge_policy: auto_when_clean(merged when no Conflict remains) orauto_with_authority(the same, and value Conflicts on Attributes whoseauthorityis the Source go to the Source) (Scribe). - Extraction jobs in
directmode: only jobs whose items all came from the rules tier (tier 0), with nothing pending (extraction). - Agent writes with
auto_merge: true, when a role the agent holds is inallowed_roles_for_auto_mergeand the target's approvals are in (agents).
Into a protected branch, Source runs and direct extraction merge only with
auto_merge_protected and direct_merge_protected (both on by default). That trust waives the
plain approvals count, not the rules that name who must approve: when steward approvals or
required roles apply, the merge is not performed and the Proposal waits for review (the run's
stats say merge_blocked: approvals, the job's result direct_blocked: approvals, with
blocking_rules) (operations). Open
Conflicts, pending items and, under block, open contradictions stop an automatic merge too.
A Source never overwrites a correction silently: it writes only what changed upstream since it
last wrote it, and an upstream change over a value someone else set is a Conflict of the
Source's Proposal under local_edits: review (the default), the person's value under keep,
or upstream's under source_wins (Scribe).
Merge policies¶
The Domain setting merge_policy holds the options of the merge engine
(design 6.10). Keeper copies it into each Proposal when the
Proposal is created; Source runs and extraction jobs read it at each merge. Per-element options
are keyed by Kind or Relation id and Attribute id (the id members of the schema document,
lorebase schema get), not by api name.
| Option | Effect on a Conflict |
|---|---|
prefer, prefer_elements, prefer_attributes |
ours or theirs wins value and schema Conflicts, globally, per Kind or Relation, or per Attribute |
authority, authority_attributes |
a ranking of origins (source:{id}, principal:{id}, operation:{name}, actor:{id}, origin:{type}): the side whose last change of the Attribute comes from the higher ranked one wins |
schema_authority |
an Attribute's schema authority (a Source) ranks first for it |
newest_wins, newest_wins_attributes |
the side whose last change is the more recent wins |
text_merge and its options |
long text edited in different places merges (below) |
Rules are tried from the most specific to the least: per Attribute prefer, authority,
newest_wins, then per Kind or Relation prefer, then global authority, newest_wins,
prefer. The first that decides resolves the Conflict, recorded as a resolution with resolver
policy:{name}; when none decides, a person does. A resolution a person stored always comes
first.
Text merge¶
Two different values of a text Attribute that both started from the same base value are
merged by sentence (or paragraph) before any rule is tried, so two branches editing different
parts of a long description, note or clause do not conflict. It is on by default.
| Option | Default | Meaning |
|---|---|---|
text_merge |
true |
on or off for every text Attribute |
text_merge_elements |
none | {kind or relation id: true or false}, for one Kind or Relation |
text_merge_attributes |
none | {attribute id: true or false}, for one Attribute; the most specific setting wins |
text_merge_granularity |
sentence |
sentence (split after ., !, ? or … followed by whitespace) or paragraph (split at blank lines) |
text_merge_max_chars |
200000 |
characters of the three values together above which text is not merged |
- Clean. Edits to different sentences are all kept; two sentences next to each other, one
rewritten on each side, merge too. The merged value is stored as a resolution with resolver
policy:text_merge, and the Review screen lists it under "Resolved by policy". - Conflict. When both sides changed the same sentence differently, or inserted text at the
same place, the Conflict stays a
DV1(same fingerprint as without text merge) withdetails.text_hunks: the whole text in order, each hunkclean(with its merged text) orconflict(base, ours and theirs). A conflicting hunk is narrowed to the words that differ. The policies above then try to decide it; otherwise the Conflict resolver shows it passage by passage (tour), or a resolution withchoice: customand the full text asvaluesettles it over the API. - Not merged. Other types (
string,uri, enums, numbers,json, arrays), values added on both sides (no base), and text above the caps:text_merge_max_chars, and two fixed bounds on the alignment (20,000 sentences or paragraphs over the three values, and the product of the base's units and the larger side's at most 4,000,000). Those stay plainDV1Conflicts. - Constraints.
min_length,max_lengthandpatternare checked on the merged text, and a value that breaks them is a Conflict like any other.
Before and after, on Contract.notes with the default sentence granularity:
base Payment within 60 days of invoice. Deliveries go to the Turin plant. The contract renews every year.
ours Payment within 45 days of invoice. Deliveries go to the Turin plant. The contract renews every year.
theirs Payment within 60 days of invoice. Deliveries go to the Turin and Bilbao plants. The contract renews every year.
merged Payment within 45 days of invoice. Deliveries go to the Turin and Bilbao plants. The contract renews every year.
If theirs had also changed the first sentence (within 30 days) and the last one
(every two years), the last one would still merge and the first would conflict, narrowed to
the number:
[{"status": "clean", "base": "Payment within ", "merged": "Payment within ", ...},
{"status": "conflict", "base": "60", "ours": "45", "theirs": "30", "merged": null},
{"status": "clean", "base": " days of invoice. Deliveries go to the Turin plant. ", ...},
{"status": "clean", "base": "The contract renews every year.",
"merged": "The contract renews every two years.", ...}]
To turn it off for one Attribute, keep it elsewhere and merge by paragraph:
{"merge_policy": {"text_merge_granularity": "paragraph",
"text_merge_attributes": {"att_01KEZF7C00...": false}}}
Reconciliation queue¶
A merge Conflict is about two branches; a contradiction between Sources is about values already
merged. Scribe raises those as candidates in the reconciliation queue (contradictions,
superseded and stale values, probable duplicates), and nothing changes until a person decides
(design 10.6,
Scribe). The merge check ties the two together: under warn a
Proposal lists its open contradictions, under block they stop its merge with 409
reconciliation-open, automatic merges included. Candidates are routed to the same stewards
as Proposals. Operator settings: operations.
Schema suggestions¶
Schema suggestions are proposals a person accepts or rejects; none reaches the schema on its
own. Extraction proposes new Kinds, Attributes and Relations found in documents
(extraction). With store.resolve_misses.enabled, Scribe
also proposes vocabulary from the words agents asked lorebase_resolve for that matched
nothing (design 8.10):
an alias of a stored value (value_alias), of an Attribute or a Kind, and, with
scribe.consolidation.llm.enabled, a description of an Attribute agents often missed. Each
shows the missed phrases with their misses, distinct callers and the similarity that linked
them. They are listed and decided in the same place (web app Review > Schema suggestions,
lorebase schema suggestions, GET .../schema/suggestions, which needs document.read):
accepting opens a schema change Proposal on a new branch, reviewed and merged like any schema
change, so the approval rules above apply. Rejected suggestions stay rejected while their
phrases are missed; a vocabulary suggestion whose phrases all expired
(store.resolve_misses.retention_days) is deleted, decided or not, so an expired rejected alias
can come back if agents ask for it again. Suggestions for a Kind whose Records the reviewer
may not read, or an Attribute whose values it may not read, are listed withheld (without
alias, phrases and value) and cannot be accepted by that reviewer; Attributes that are
sensitive, kept in the vault, derived or resolvable: false never receive one, and an alias
that would name two things is refused at acceptance.
A worked configuration¶
The supply Domain takes nightly ERP data and contract documents. Procurement owns suppliers,
risk analysts own risk flags, and nothing reaches main without a supply steward; deterministic
feeds should flow without waiting for a person.
The schema names the stewards and the Source policy (excerpt):
domain:
id: supply
stewards: [supply-steward]
kinds:
RiskFlag:
stewards: [risk-analyst]
sources:
erp.purchase_orders:
# type, connection, mappings ...
local_edits: review
target_branch_policy: {merge_policy: auto_when_clean}
The installation keeps one approval and lets automation into protected branches:
keeper:
proposals:
required_approvals: 1
reconciliation:
merge_check: warn
scribe:
sources:
auto_merge_protected: true
extraction:
mode: doubts_only
domain_may_loosen: [mode]
direct_merge_protected: true
The Domain is created with its own rules, and its extraction settings set afterwards:
POST /api/v1/domains
Content-Type: application/json
{"id": "supply", "display_name": "Supply chain",
"settings": {"required_approvals_from_stewards": 1,
"reconciliation_check": "block"}}
PATCH /api/v1/domains/supply
Content-Type: application/json
{"settings": {"extraction": {"mode": "doubts_only",
"rules": [{"source_key_prefix": "feeds/erp/", "mode": "direct"}]}}}
What happens:
- A purchase order sync whose run meets no Conflict would merge on its own, but the Domain
requires a steward approval, so its Proposal waits with
merge_blocked: approvalsand a supply steward finds it underassigned=me. Withrequired_approvals_from_stewards: 0it would merge at once. - A run that changes a status someone corrected by hand opens a
DV1instead of overwriting it. A Domainmerge_policywith anauthorityranking that lists the ERP Source would decide such Conflicts for the ERP, since an origin missing from a ranking ranks below every listed one: rank only the origins that should win over people. - A contract uploaded under
feeds/erp/whose items all come from rules is adirectjob, held the same way by the steward rule; one under any other key, or one that used a model, waits asdoubts_onlywith its doubtful items pending. - A Proposal that would merge a value an open contradiction is about stops with
reconciliation-openuntil the candidate is decided or dismissed. - Two analysts editing different sentences of a contract's notes merge cleanly; the same sentence edited twice waits for one of them in the passage view.