Edge Conditions and Governed Auto-Learning
Status: Proposed
Document type: Specification
Authority: Subordinate to INTEGRATEWISE_ARCHITECTURE.md
Canonical sections implemented: §8 (Truth, memory scopes, promotion) · §9 (Four flows, Normalizer/Triage) · §10 (Capability effect classes, governance) · §11.4 (Response state) · §14 (Tenancy)
Owner: Platform + Data
Version: 1.0
Last verified against: HEAD d941a422
Last verified date: 2026-07-31
Resolves: S0b (org_memory write ambiguity) · M1 (store vs cache) · M3 (agent memory citation)
Adds implementation precision. Does not redefine kernel vocabulary, ownership boundaries,
truth authority, runtime ordering or governance invariants.
1. Invariants
- Spine is the system of record. Brain, Twin, Knowledge, Workbench and analytical stores
are projections or reasoning layers. None of them is a parallel canonical store. - Uncertainty is represented explicitly.
unknown·unresolved·insufficient_data
·conflicted·stale·failedare valid persisted states. The platform never converts
uncertainty into a guessed canonical value. - Tenant boundaries fail closed. Missing or invalid workspace context returns zero rows
and rejects writes. - Autonomy is granted by policy, never inferred by an agent. Agents may discover patterns
and propose rules. Only Governance authorises autonomous execution. - All derived behaviour is versioned and reversible. Rules, score formulas, mappings,
learned patterns and execution policies require version history, provenance, evaluation
evidence and rollback.
Invariant 4 is the agent-scoped corollary of the Authority Law (confidence increases
trust; confidence never grants authority). It is not a ninth law — it is the same law applied
where it is most often violated.
2. No dual-write: the transactional outbox
The system must not perform independent synchronous writes to Spine and to any projection.
Source event
↓
┌─ Spine transaction ──────────────────────┐
│ canonical entity mutation │
│ field lineage mutation │
│ transactional outbox event │ ← same transaction, always
└──────────────────┬───────────────────────┘
↓ asynchronous consumers
Brain projection · search index · vector index
signal engine · Workbench projection · org-memory cache
The canonical mutation and the outbox event commit in the same database transaction.
Everything downstream is an asynchronous consumer of that event.
2.1 Delivery semantics
At-least-once delivery · stable event identifiers · idempotent consumers · monotonic entity
versions · retry with exponential backoff and jitter · dead-letter queues · reconciliation
jobs.
The architecture does not claim cross-store exactly-once delivery. Any document that does
is wrong.
2.2 Partial failure
| Failure | Resulting state |
|---|---|
| Spine transaction fails | No canonical change and no outbox event |
| Spine succeeds, projection fails | Canonical state valid; outbox retries |
| Search/vector projection fails | Canonical valid; that projection is lagging |
| Retry limit exceeded | Event enters the projection repair queue |
| Projection receives an older version | Successful no-op |
| Projection receives a duplicate | Successful no-op |
interface ProjectionStatus {
projectionName: string;
entityId: string;
canonicalVersion: number;
projectedVersion: number;
status: "current" | "lagging" | "failed" | "rebuilding";
lastSuccessfulAt?: string;
lastErrorCode?: string;
}
2.3 This resolves S0b and M1
Architecture S0b records org_memory as a documented cache with six direct writers and no
defined reconciliation. The outbox settles it:
Neon
memory.org_memoryis the store. The D1org_memorytable is a projection.
Exactly one writer — the promotion gate, inside the Spine transaction. The other five
services become read-only consumers of the outbox.
M1 is closed on that basis. The same reasoning retires the "Brain as second write
destination" framing entirely: Brain is a reasoning and learning projection used by the
Twin, not a truth store. The durable control path is unchanged:
Spine → Twin proposal → Governance → Hermes execution → re-ingestion into Spine
3. Edge Condition Registry
Every pipeline stage registers its known edge conditions:
edge_code · stage · detection_condition · severity · handling_strategy ·
fallback_state · retry_policy · owner · user_visibility · audit_required
3.1 Canonical edge conditions
| Code | Condition | Required handling | Fallback |
|---|---|---|---|
SOURCE_UNKNOWN | Source system unidentifiable | Deterministic fingerprinting; classifier recommendation only | Quarantine for triage |
SOURCE_CLASSIFICATION_AMBIGUOUS | Multiple source candidates within margin | Do not assign automatically | Quarantine with candidate list |
SCHEMA_VERSION_UNKNOWN | Payload schema unsupported | Preserve raw; request mapping | Dead-letter |
FIELD_COERCION_FAILED | Value not convertible to canonical type | Store raw; emit field-level error | Canonical field null |
REQUIRED_FIELD_MISSING | Required canonical field absent | Configured derivation only | Incomplete provisional record |
IDENTITY_COLLISION | Multiple candidates exceed threshold | Require disambiguation; do not merge | Unresolved identity queue |
IDENTITY_LOW_CONFIDENCE | No candidate meets threshold | Provisional identity only where policy permits | Quarantine |
IDENTITY_SPLIT_SUSPECTED | Entity appears to represent multiple real entities | Suspend automatic updates to conflicting fields | Identity-review case |
IDENTITY_MERGE_CONFLICT | Merge would overwrite incompatible values | Field-level merge proposal | Leave entities separate |
STALE_EVENT | Event older than stored watermark | Record; do not overwrite current state | Historical event store |
OUT_OF_ORDER_EVENT | Sequence gap or reorder | Buffer where possible; else reconciliation marker | Reconciliation queue |
DUPLICATE_EVENT | Idempotency key already seen | Successful no-op | Audit duplicate |
SPINE_COMMIT_FAILED | Canonical transaction fails | Retry transient only | Dead-letter and alert |
PROJECTION_UPDATE_FAILED | Projection consumer error | Async retry from outbox | Projection repair queue |
HEALTH_INPUT_INCOMPLETE | Required dimensions absent | Calculate only where permitted; expose completeness | insufficient_data |
HEALTH_INPUT_STALE | Inputs exceed freshness policy | Reduce confidence or suppress | stale |
RLS_CONTEXT_MISSING | Workspace context unset | Reject; return no data | Security audit event |
AUTOMATION_POLICY_MISSING | No applicable approval policy | Block execution | Governance queue |
ACTION_PARTIAL_FAILURE | Multi-step action partly completed | Stop dependents; compensate where defined | Manual recovery case |
LEARNING_DRIFT_DETECTED | Rule performance degrades | Suspend autonomous mode | Return rule to shadow |
4. Source identity
Resolution order — deterministic evidence first, classifier last but one:
- Signed connector or integration identifier
- Registered ingress endpoint
- OAuth installation or connection identifier
- Source-specific event signature
- Registered payload fingerprint
- Classifier recommendation
- Manual triage
An AI classification result is never authoritative source identity by itself.
interface SourceClassification {
candidates: Array<{ provider: string; confidence: number; evidence: string[] }>;
modelVersion: string;
classifiedAt: string;
}
Automatic assignment requires all three:
top_confidence >= configured_minimum
AND top_confidence − second_confidence >= configured_margin
AND no deterministic evidence contradicts the result
Otherwise: quarantine.
This is the same shape as the capability effect classes (§10.0): a classifier is a derive
capability, and a derive capability produces candidates, never facts.
5. Normalization and coercion
Normalization never silently discards or mutates an invalid source value.
interface CanonicalFieldValue<T> {
value: T | null;
rawValue?: unknown;
sourceSystem: string;
sourceRecordId: string;
observedAt: string;
normalizedAt: string;
normalizationStatus: "valid" | "coerced" | "invalid" | "missing" | "conflicted";
normalizationRuleVersion: string;
confidence?: number;
}
| Input condition | Behaviour |
|---|---|
| Safe, lossless conversion | Convert; mark coerced |
| Locale-dependent value | Use source or workspace locale |
| Ambiguous date or currency | Reject automatic conversion |
| Numeric field contains units or text | Parse only through a registered rule |
| Value exceeds canonical range | Preserve raw; mark invalid |
| Enumerated value unknown | Map to unknown — never the nearest known value |
| Null vs blank vs absent | Preserve the distinction |
{
"field": "arr",
"value": null,
"rawValue": "around 2 lakh",
"normalizationStatus": "invalid",
"errorCode": "FIELD_COERCION_FAILED"
}
6. Identity resolution
Deterministic identifiers before probabilistic similarity:
- Verified external system identifier
- Verified email or domain identifier
- Exact normalized identifier match
- Composite deterministic match
- Probabilistic candidate generation
- Human disambiguation
Threshold alone is insufficient — separation is required:
automatic_match allowed when:
top_score >= match_threshold
AND (top_score − second_score) >= ambiguity_margin
AND no immutable identifier conflicts
When several candidates clear the threshold without separation: do not pick the highest, do
not merge. Open an identity-resolution case preserving all candidate scores and evidence.
{
"resolution_status": "unresolved",
"canonical_status": "provisional",
"eligible_for_automation": false
}
7. Health score semantics
Three independent outputs. One number cannot carry all three.
interface HealthScore {
score: number | null;
band: "critical" | "at_risk" | "healthy" | "insufficient_data" | "stale";
completeness: number;
confidence: number;
calculatedAt: string;
formulaVersion: string;
missingDimensions: string[];
staleDimensions: string[];
}
Bands are half-open, so boundaries are deterministic:
| Band | Definition |
|---|---|
| Critical | 0 <= score < 50 |
| At Risk | 50 <= score < 80 |
| Healthy | 80 <= score <= 100 |
Exactly 50 is At Risk. Exactly 80 is Healthy.
Partial calculation is permitted only when available_weight >= minimum_weight_coverage and
required dimensions are present; otherwise score: null, band: "insufficient_data".
A partial score is never displayed without its completeness indicator. A stale dimension is
either excluded or decayed by a versioned function — and which one is explicit in the formula
version.
8. Row-level security fails closed
When app.current_org_id is absent, empty, malformed or unauthorised: reads return zero rows,
writes are rejected, a security-policy violation is recorded. No default organisation is
inferred, and no organisation is selected from request data alone.
create or replace function security.current_org_id()
returns uuid language plpgsql stable security invoker as $$
declare value text;
begin
value := current_setting('app.current_org_id', true);
if value is null or btrim(value) = '' then return null; end if;
begin return value::uuid;
exception when invalid_text_representation then return null; end;
end; $$;
create policy workspace_isolation on spine.entities
using (org_id = security.current_org_id())
with check (org_id = security.current_org_id());
A privileged service role may bypass RLS only inside an isolated system worker that
receives an explicit organisation identifier, verifies connector ownership, writes the
organisation identifier into every mutation, produces an immutable audit record, and does not
process records from multiple organisations in one transaction unless explicitly designed for
administrative operations.
9. Governed auto-learning
Learning is a governed evidence pipeline, not direct mutation of agent behaviour.
observation → candidate → suggested → shadow → approval review → active
↓
remain active · return to shadow · suspend · retire
| State | Meaning | Execution permission |
|---|---|---|
observation | Individual event or outcome | None |
candidate | Statistically recurring pattern | None |
suggested | Human-readable proposed behaviour | None |
shadow | Evaluated against live opportunities, no action | None |
approved_proactive | May present recommendations | User action required |
approved_autonomous | May execute within a policy envelope | Governed execution |
suspended | Disabled by drift, error or policy change | None |
retired | Permanently inactive, retained for audit | None |
An agent may move a pattern from observation to candidate or suggested. An agent may
never promote a rule to approved_autonomous.
This extends architecture §8.2c-i: Triage classifies, the Promotion Gate governs. Here the
same separation applies to behaviour rather than memory.
9.1 Candidate eligibility
Minimum observations · distinct entities · distinct users or workflows · observation period ·
maximum single-entity concentration · outcome consistency · no unresolved policy violation ·
no prohibited sensitive-data dependency.
minimum_observations: 10
minimum_distinct_entities: 3
minimum_observation_days: 7
maximum_single_entity_concentration: 0.50
minimum_outcome_consistency: 0.75
Initial operating defaults — not permanent constants.
9.2 Bounded pattern types (first release)
Trigger → recommended action · trigger → classification · trigger → routing destination ·
repeated approval decision · repeated field correction · repeated workflow sequence ·
repeated risk or opportunity signal.
Free-form policy generation is not permitted in the first release.
9.3 Graduation
Suggested → shadow requires a machine-testable trigger and output, available and
sufficiently complete data, explicitly scoped entities and actions, no unresolved security or
compliance issue, and a rollback path.
Shadow evaluation measures support · precision · recall where measurable · false-positive
rate · false-negative rate · abstention rate · coverage · outcome impact · performance by
segment · performance over time.
interface RuleEvaluation {
ruleVersionId: string;
opportunityId: string;
predictedOutcome: unknown;
observedDecision?: unknown;
observedOutcome?: unknown;
matched: boolean;
correct?: boolean;
falsePositive?: boolean;
falseNegative?: boolean;
evaluatedAt: string;
}
Proactive approval requires minimum evidence volume, precision above threshold, no critical
policy violations, an explanation the approver understands, and a recorded Governance approval.
Proactive rules may create suggestions and proposals. They may not perform external writes.
Autonomous approval requires successful shadow and proactive operation, false-positive
rate within policy, every possible action explicitly enumerated, an action budget, a rollback
or compensation strategy, authorisation by required approvers of that rule version, and a
Governance-specified envelope:
interface AutonomousPolicyEnvelope {
allowedActionTypes: string[];
allowedEntityTypes: string[];
maximumActionsPerHour: number;
maximumValueAtRisk?: number;
allowedTimeWindows?: string[];
requireFreshDataWithinMinutes: number;
minimumRuleConfidence: number;
minimumInputCompleteness: number;
approvalExpiresAt: string;
emergencyDisableSupported: true;
}
9.4 Feedback is evidence, not an update
Feedback events — accepted unchanged · accepted after modification · rejected as incorrect ·
rejected as irrelevant · rejected on timing · rejected for missing context · ignored · reversed
after execution · marked harmful.
A thumbs-up alone is insufficient; request a structured reason where feasible. Weighting may
consider role, domain authority, whether a correction was supplied, outcome measurability, and
conflicting feedback between actors.
Feedback never causes direct online modification of production behaviour. It aggregates
into evaluation evidence and moves through the rule lifecycle.
10. Learning storage
A single brain.learnings table cannot support auditable learning. Seven concerns, seven
tables: observations · pattern candidates · rules · rule versions · evaluations · feedback ·
execution audit. All tenant-scoped with RLS. Full DDL: Appendix A.
10.1 The learning schema straddles the repo boundary — split it
The repository split (Repository Topology)
assigns operational state to Product and governed records to Platform. The learning tables fall
on both sides, and the boundary is the approval record:
| Table | Repo | Why |
|---|---|---|
learning.observations | Product | Operational, high-churn evidence |
learning.pattern_candidates | Product | Derived evidence; no authority |
learning.rules, rule_versions | Platform | Carry approved_by / approved_at — governance records |
learning.rule_evaluations | Platform | The evidence that justifies approval; an auditor needs it beside the approval |
learning.feedback_events | Platform | Same — evidence supporting a governance decision |
learning.rule_execution_audit | Platform | Immutable execution record; belongs with the gate |
The rule: evidence generation is Product; the approval and everything an auditor needs to
reconstruct it is Platform. Observations reach Platform through the same outbox path as any
other promotion — never by direct cross-repo write.
11. Confidence is six things
| Concept | Question it answers |
|---|---|
| Pattern confidence | How consistent is the observed pattern? |
| Prediction confidence | How sure is this specific prediction? |
| Input completeness | How much of the required input is present? |
| Data freshness | How current is it? |
| Historical precision | How often has this rule been right? |
| Policy eligibility | Is this permitted at all? |
interface DecisionEvidence {
predictionConfidence: number;
inputCompleteness: number;
freshnessConfidence: number;
historicalPrecision: number;
sampleSize: number;
eligibleForProactiveMode: boolean;
eligibleForAutonomousMode: boolean;
}
A single confidence number must never represent all six.
11.1 Calibration — name the method
Confidence must be calibrated against observed outcomes, and for small samples a conservative
lower bound is used rather than raw accuracy. A rule at 100% over 3 observations is not
more reliable than one at 94% over 500.
Addition to the source proposal: the method must be named, not left to each service, or
two components will compute "confidence" differently — the exact drift this architecture
exists to prevent. Specify the Wilson score lower bound at 95%, with the confidence level
recorded on the rule version. Under Wilson, 3/3 yields ≈ 0.44 and 470/500 yields ≈ 0.92 — the
ordering the invariant demands, produced by a stated formula rather than by judgement.
12. Drift and expiry
Drift signals: declining precision · rising false-positive rate · material input-distribution
change · new source schema versions · workspace policy change · changed user correction
behaviour · declining outcome effectiveness · persistent abstention · repeated execution
reversal.
approved_autonomous → suspended → shadow → re-evaluation and re-approval
Rules carry approval expiry. Expired rules return to proactive or shadow per policy.
Drift must be able to suspend a rule without deploying application code.
13. Twin read/write contract
May read: canonical Spine entities · relationships · field provenance · projection status ·
permitted organisational memory · applicable policies · previous proposals and feedback ·
current rule versions.
May write: conversation events · observations · proposals · suggested entity corrections ·
suggested memory entries · feedback requests · candidate patterns.
May never write directly: canonical entity changes · approved organisational memory ·
autonomous policies · active rule versions · external system mutations.
This closes M3: agent memory may be cited by the Twin within its own scope, but may not
inform organisational-level claims until it has passed triage and promotion.
13.1 Agent modes
| Mode | May do | May not |
|---|---|---|
| Advisory | Explain and analyse | Create executable proposals; mutate anything |
| Proactive | Detect conditions; create proposals | Execute without approval |
| Autonomous | Execute a previously approved rule version within its envelope | Create or broaden its own authority |
Autonomous mode must verify input completeness and freshness before each execution, use
idempotency keys, record a complete audit, and stop when confidence or policy eligibility falls
below threshold.
14. Observability
Pipeline: ingestion success rate · quarantine rate · normalization error rate · identity
collision rate · outbox lag · projection lag · dead-letter count · reconciliation backlog.
Health scoring: input completeness · stale-input percentage · recalculation latency ·
formula version distribution · band transition count.
Learning: observations processed · candidates created · suggestions accepted/modified/
rejected/ignored · shadow precision and coverage · autonomous execution count · false-positive
rate · reversal rate · drift suspensions · rule approval age · rule version distribution.
All broken out by workspace, connector, rule and pipeline stage.
15. Acceptance criteria
- An unknown payload source cannot enter canonical normalization without source resolution
- Invalid field values preserve their raw representation
- Ambiguous identity candidates never merge automatically
- A successful Spine commit survives failure of every downstream projection
- Every projection can be rebuilt from canonical events
- Missing organisation context returns zero rows and rejects writes
- Scores of exactly 50 and 80 resolve deterministically
- A health score cannot conceal incomplete or stale inputs
- User feedback cannot directly mutate production behaviour
- A rule cannot become autonomous without a recorded Governance approval
- Every autonomous execution references a fixed rule version and policy envelope
- Drift can suspend a rule without deploying application code
- Rules and projections are idempotent and replayable
- Every learned behaviour is tenant-scoped, versioned, explainable and reversible
- The Twin cannot mutate canonical truth or external systems outside governed paths
- (added) No component performs a synchronous write to both Spine and a projection
- (added) Confidence lower bounds are computed by the single named method (§11.1)
Appendix A — Learning schema
create schema if not exists learning;
create table learning.observations (
id uuid primary key,
org_id uuid not null,
actor_type text not null,
actor_id uuid,
context_type text not null,
context_entity_ids uuid[] not null default '{}',
trigger_type text not null,
proposed_action jsonb,
actual_action jsonb,
outcome jsonb,
feedback text,
reason_code text,
source_event_ids uuid[] not null default '{}',
occurred_at timestamptz not null,
schema_version text not null,
created_at timestamptz not null default now()
);
create table learning.pattern_candidates (
id uuid primary key,
org_id uuid not null,
pattern_type text not null,
definition jsonb not null,
status text not null,
support_count integer not null default 0,
distinct_entity_count integer not null default 0,
first_observed_at timestamptz,
last_observed_at timestamptz,
confidence numeric,
evidence_query_version text not null,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create table learning.rules (
id uuid primary key,
org_id uuid not null,
name text not null,
current_version integer not null,
lifecycle_state text not null,
created_by_type text not null,
created_by_id uuid,
created_at timestamptz not null default now(),
retired_at timestamptz
);
create table learning.rule_versions (
id uuid primary key,
rule_id uuid not null references learning.rules(id),
version integer not null,
trigger_definition jsonb not null,
condition_definition jsonb not null,
action_definition jsonb not null,
explanation text not null,
source_candidate_ids uuid[] not null default '{}',
confidence numeric,
confidence_method text not null, -- §11.1: named, not implied
policy_envelope jsonb,
approved_by uuid,
approved_at timestamptz,
approval_expires_at timestamptz,
created_at timestamptz not null default now(),
unique(rule_id, version)
);
create table learning.rule_evaluations (
id uuid primary key,
org_id uuid not null,
rule_version_id uuid not null references learning.rule_versions(id),
opportunity_id uuid not null,
prediction jsonb,
observed_decision jsonb,
observed_outcome jsonb,
matched boolean not null,
correct boolean,
false_positive boolean,
false_negative boolean,
evaluated_at timestamptz not null,
created_at timestamptz not null default now()
);
create table learning.feedback_events (
id uuid primary key,
org_id uuid not null,
rule_version_id uuid references learning.rule_versions(id),
proposal_id uuid,
actor_id uuid not null,
feedback_type text not null,
reason_code text,
original_output jsonb,
corrected_output jsonb,
created_at timestamptz not null default now()
);
create table learning.rule_execution_audit (
id uuid primary key,
org_id uuid not null,
rule_version_id uuid not null references learning.rule_versions(id),
proposal_id uuid,
action_type text not null,
request_payload jsonb not null,
execution_status text not null,
result_payload jsonb,
idempotency_key text not null,
compensation_status text,
executed_at timestamptz,
created_at timestamptz not null default now(),
unique(org_id, idempotency_key)
);
Every table requires workspace-isolated RLS per §8.
Related: Architecture §8–§11 ·
Repository Topology ·
Marketplace Channels and Data Flow