Processing Pipeline Architecture
Date: 2026-02-27
Status: Current implementation
Supersedes: 08-processing-pipeline-observability.md (merged into this document)
1. Purpose
This document defines how data flows through the platform from the moment a connector submits discovered entities to the moment findings and evidence packs are queryable via API. It also defines the scan safety guarantees that protect against data loss from incomplete scans, and the operational observability contract — metrics, alerts, SLOs, and dashboards required for production operation.
It covers two pipeline states:
| State | Description | When |
|---|---|---|
| Current | 3 jobs, authority path materialization, circuit breaker safety | What runs in production today |
| Long-term target | 6 stages, fully decomposed | Future, after multi-connector reconciliation |
Each state is defined with: trigger, inputs/outputs, collection mutations, state transitions, and failure handling.
2. Current Pipeline (Production Today)
2.1 Overview
History: Authority path materialization and path-level finding evaluation were added in February 2026 as part of the W1.1 milestone. Circuit breaker safety was added on 2026-02-27 following a production incident where a broken connector scan removed all authority paths. See Scan Safety plan for the incident summary and design rationale.
2.2 Job 1: sync_ingestion
Trigger: Dequeued from in-memory FIFO after ingestion endpoint returns 202.
Source: src/workers/handlers/sync-ingestion.ts
What it does (in order):
1. Create ConnectorSyncDoc → connector_syncs (status: running)
2. transformGraph() → entities + evidence (in memory)
3. computeDiff() → events, changed/created/deleted IDs (in memory)
├── ScanScope: incremental mode skips deletion detection
├── ScanScope: targeted mode scopes deletion to declared entity types
└── Circuit breaker: evaluateDeletionBreaker() checks global + per-type thresholds
4. upsertEntities() → entities collection
5. insertEvents() → events collection
6. insertEntityVersions() → entity_versions collection (changed entities)
6b. Soft-delete absent entities → entities collection
├── GATED by circuit breaker: if triggered, ALL entity deletions blocked
└── Sets removed_by_sync_id for rollback determinism
7. upsertExecutionEvidence() → execution_evidence collection
8. materializeExecutionPaths() → entities collection (embedded paths)
9. assembleExecutionChains() → execution_chains collection
10. materializeAuthorityPaths() → authority_paths collection
└── Independent AP removal safety net (AP_REMOVAL_THRESHOLD = 0.50)
10b. Remove APs for deleted workloads → authority_paths collection
└── GATED by circuit breaker: if entity breaker triggered, AP removal also blocked
11. Update sync → completed → connector_syncs (status: completed, metrics)
└── Records circuit_breaker_details in sync metrics if any breaker fired
Collection mutations:
| Step | Collection | Operation | Notes |
|---|---|---|---|
| 1 | connector_syncs | insert/update | Status: running |
| 4 | entities | bulkWrite (upsert) | All entity types |
| 5 | events | insertMany | Change events |
| 6 | entity_versions | insert + update | New version, expire previous |
| 6b | entities | update | status=deleted for absent entities (circuit breaker gated) |
| 7 | execution_evidence | bulkWrite (upsert) | Activity records |
| 8 | entities | update | Write execution_paths[] embedded array |
| 9 | execution_chains | bulkWrite (upsert) | 1 chain per workload |
| 10 | authority_paths | bulkWrite (upsert) | New and updated paths |
| 10 | authority_paths | updateMany | status=removed for absent paths (circuit breaker gated) |
| 11 | connector_syncs | update | Status: completed + metrics |
Sync metrics fields:
metrics: {
entities_created, entities_updated, entities_affected,
events_created, paths_recomputed,
chains_created, chains_updated,
authority_paths_created, authority_paths_updated, authority_paths_removed,
// Present when any circuit breaker fires:
circuit_breaker_triggered, deletions_blocked, authority_paths_removal_blocked,
circuit_breaker_details: {
entity_deletion_ratio, entity_deletion_threshold,
ap_removal_ratio, ap_removal_threshold
}
}
If any step fails: Update sync to status=failed, throw. No rollback (upserts are idempotent on retry).
2.3 Job 2: evaluate_findings
Trigger: Dequeued after Job 1 completes (FIFO ordering).
Gate: Checks sync.status === "completed". If not, skips evaluation.
Source: src/workers/handlers/evaluate-findings.ts, src/evaluator/index.ts
Job 2 is read-only over derived state. Chain (re-)materialization is owned by Job 1 step 9 (sync ingestion) and by the deploy-gate
assemble_chainsjob — see ADR-026. The evaluator does not writeexecution_chains.
What it does:
1. Fetch all evaluable entities (identity, workload, credential, connection)
2. Fetch all active authority_paths for this tenant
3. Run entity-level + path-level evaluator rules:
├── Entity-level: 14 rule files → 15 finding types across all evaluable entities
└── Path-level: path-evaluator.ts runs authority-path checks
(dormant_authority, reachable_sensitive_domain, unknown_identity_binding,
unproven_execution, scope_drift, reachability_drift, ownership_drift,
llm_egress, external_egress, ownership_ambiguous, ownership_unknown)
4. For each rule that fires:
a. Compute finding ID: "eval:" + hash(finding_type, entity_id|path_id)
b. Detect content change (severity, explanation, evidence_refs)
c. If new or changed: upsert FindingDoc
d. Path-level findings: set path_id, intervals[] tracking
5. Auto-resolve stale findings:
a. Active findings whose rule no longer fires → status: remediated
b. Path-level: set resolution_reason, close interval
6. Update authority_paths.current_state:
a. active_finding_count = count of active findings for each path
b. max_finding_severity = highest severity among active findings
7. Capture posture snapshot (non-fatal)
8. Enqueue build_evidence_pack for each created/updated finding
Collection mutations:
| Step | Collection | Operation |
|---|---|---|
| 1-2 | entities, authority_paths | read |
| 4 | findings | bulkWrite (upsert) |
| 5 | findings | update (status → remediated) |
| 6 | authority_paths | update (current_state) |
| 7 | posture_snapshots | upsert |
2.4 Jobs 3..N: build_evidence_pack
Trigger: One job per changed finding, enqueued by Job 2.
What it does:
1. Fetch finding + entity + related entities
2. Fetch execution evidence, entity versions, events
3. Build 9-section evidence pack content
4. Render markdown
5. Compute integrity hash (SHA256 with tenant_id)
6. Insert EvidencePackDoc (with previous_pack_id chain)
7. Update finding with evidence_pack_id
Collection mutations:
| Step | Collection | Operation |
|---|---|---|
| 1-2 | multiple | read only |
| 6 | evidence_packs | insert |
| 7 | findings | update |
2.5 Sync State Machine
pending → running → completed
→ failed
| Status | Meaning |
|---|---|
pending | Sync record created, handler not yet started |
running | Handler executing (import, resolve, paths, authority paths, chains) |
completed | All steps succeeded, metrics recorded |
failed | Any step failed, last_error recorded |
The evaluate_findings gate only fires if status=completed. If the sync failed, no evaluation happens and findings from the previous sync remain as-is.
2.6 Deploy-Triggered Re-Materialization
Some derived collections — execution_chains is the canonical example — depend on assembly logic that is platform code, not connector input. When that code changes (a new ENTRY_POINT_SUBTYPES entry, a new BFS edge, a new chain role), existing source-system data does not change. A new sync is not enqueued automatically, and the previously-materialized rows do not reflect the new logic until a sync happens to fire. The result is a stale derived collection that the v0.6 UI surfaces depend on.
The pipeline closes this gap with a deploy-gate trigger. When a deploy includes a change to chain-builder.ts (or to the schema files that define the entry-point set, the chain-role table, or the BFS edge list), the deploy enqueues one assemble_chains job per active tenant. The job runs assembleExecutionChains against the existing entity graph and upserts the resulting chains. It does not re-ingest source-system data, and it does not run the evaluator.
The assemble_chains job is idempotent. Re-running it against an unchanged graph produces the same composition_hash for every chain and is a net no-op beyond last_seen_at. The same job kind is also enqueued by an operator script for cold-recovery scenarios — incidents where the deploy gate missed an enqueue, or where an operator wants to re-materialize a single tenant during investigation.
The trigger source is the deploy, not the connector. This matters because the root cause of the failure mode is a code change, not a source-system change. Routing the trigger to its actual cause keeps Job 1 (continuous sync) and Job 2 (read-only evaluation) at their documented responsibilities. The evaluator does not write execution_chains; the deploy gate covers what sync alone cannot.
Garbage collection of chain anchors for workloads that no longer match ENTRY_POINT_SUBTYPES is a separate concern. The assemble_chains job upserts; it does not delete. Deletion semantics for orphaned chain documents are tracked as future work.
Rationale and rejected alternatives — including the "let the evaluator materialize" pattern — are documented in ADR-026: Chain Re-Materialization Triggers.
3. Scan Safety & Data Loss Prevention
This section documents the defense-in-depth architecture that prevents data loss from incomplete or broken connector scans. All features described here are implemented and integration-tested.
Trigger: Production incident on 2026-02-26 where a broken connector scan removed all 5 authority paths for the default tenant. See Scan Safety plan for the full incident post-mortem.
3.1 Design Principles
- Never trust a single scan to be complete. Connectors can fail partially — API limits, permission revocations, timeouts. The platform must treat incoming data as potentially incomplete.
- Absence ≠ deletion. An entity missing from one scan should not be immediately removed without safety checks.
- Protect high-value data with circuit breakers. If a sync would remove a significant portion of existing data, halt destructive operations and record diagnostics.
- Connectors must declare scope. A connector scanning only Function Apps should not trigger deletion of unrelated ServiceNow workloads.
- All destructive operations must be observable and reversible. Operators must be able to see what each scan changed, detect anomalies, and roll back bad syncs.
3.2 ScanScope Declaration
Connectors declare scan coverage in the scanScope field of NormalizedGraph:
| Mode | Deletion Detection | Use Case |
|---|---|---|
full (default) | Enabled — absent entities flagged for deletion, subject to circuit breaker | Standard periodic sync |
incremental | Disabled — additive only, no deletions | Partial update, new entities only |
targeted | Scoped to scannedEntityTypes[] only | Type-specific refresh (e.g., only workloads) |
Additional optional fields: sourceSystems[] (scope deletion to specific source systems), errors[] (connector-reported partial failures).
Source: src/ingestion/types.ts (ScanScope interface)
3.3 Circuit Breaker: Entity Deletion (Layer 1)
Prevents mass entity deletion from a suspect scan. Evaluated in computeDiff() before any destructive operations.
Global threshold: DEFAULT_DELETION_THRESHOLD = 0.50 (50%)
Per-type thresholds:
| Entity Type | Threshold |
|---|---|
identity | 30% |
workload | 40% |
role | 40% |
permission | 40% |
resource | 60% |
owner | 50% (global default) |
When triggered:
- Zero entities are soft-deleted (all deletions blocked for the entire sync)
- Sync completes normally — additive operations (upsert, events, paths) proceed
circuit_breaker_triggered: truerecorded in sync metrics with detailed diagnostics
Source: src/ingestion/diff-engine.ts (evaluateDeletionBreaker, DEFAULT_DELETION_THRESHOLD, DELETION_THRESHOLDS_BY_TYPE)
3.4 Circuit Breaker: Authority Path Removal (Layer 2)
Independent safety net inside the authority path materializer. This breaker is evaluated AFTER entity processing — even if entity deletion was not triggered (e.g., incremental mode with missing edges), the AP materializer independently blocks mass path removal.
Threshold: AP_REMOVAL_THRESHOLD = 0.50 (50% of existing active paths across all workloads in the sync)
When triggered:
- Zero authority paths are removed
apRemovalBlockedandapRemovalRatiorecorded in sync resultauthority_paths_removal_blockedrecorded in sync metrics
Source: src/ingestion/authority-path-materializer.ts (AP_REMOVAL_THRESHOLD)
3.5 Cascading Pipeline Gate
When the entity deletion circuit breaker fires, it blocks ALL downstream destructive operations:
- Step 6b: Entity soft-deletion (direct — checked via
circuitBreakerTriggered) - Step 10b: Authority path removal for deleted workloads (cascading — also checked via
circuitBreakerTriggered)
This prevents the causal chain: deleted entities → missing execution_paths → AP materializer removes authority paths. The circuitBreakerTriggered flag gates both Step 6b and Step 10b in the sync handler.
The AP materializer's own breaker (§3.4) is independent — it fires based on its own ratio calculation, not the entity breaker. This is defense-in-depth: two independent safety nets at different layers of the pipeline.
Source: src/workers/handlers/sync-ingestion.ts (circuitBreakerTriggered checks at Step 6b and Step 10b)
3.6 Rollback Determinism
All soft-delete operations record removed_by_sync_id:
- Entities:
EntityDoc.removed_by_sync_id(set in Step 6b) - Authority paths:
AuthorityPathDoc.removed_by_sync_id(set inmarkAuthorityPathsRemoved)
This enables:
- Targeted rollback: Restore all entities/paths removed by a specific sync
- Audit trail: Identify which sync caused each deletion
- Incident response: Determine the blast radius of a bad scan
3.7 Circuit Breaker Observability
Sync metrics include full diagnostic data when any breaker fires:
{
"circuit_breaker_triggered": true,
"deletions_blocked": 82,
"authority_paths_removal_blocked": 6,
"circuit_breaker_details": {
"entity_deletion_ratio": 0.854,
"entity_deletion_threshold": 0.50,
"ap_removal_ratio": 0.75,
"ap_removal_threshold": 0.50
}
}
3.8 Integration Test Coverage
5 integration tests verify the complete safety chain:
| Test | What it verifies |
|---|---|
| Healthy → broken scan | Entities and authority paths preserved when breaker fires |
| Under threshold | Small changes (below threshold) are deleted normally |
| Three-scan recovery | Healthy → broken → healthy: full data recovery |
| Incremental mode | Deletion detection skipped entirely |
| Breaker metrics | Sync metrics contain detailed circuit breaker diagnostics |
Source: test/integration/ingestion/circuit-breaker.test.ts
4. Collection Mutation Matrix
Which job writes to which collection, and how:
| Collection | sync_ingestion | evaluate_findings | build_evidence_pack |
|---|---|---|---|
connector_syncs | insert + update (status, metrics) | — | — |
entities | bulkWrite (upsert), update (soft-delete, execution_paths) | read | read |
events | insertMany | — | read |
entity_versions | insert + update (expire) | — | read |
execution_evidence | bulkWrite (upsert) | read | read |
execution_chains | bulkWrite (upsert) | — | — |
authority_paths | bulkWrite (upsert), updateMany (remove) (circuit breaker gated) | update (current_state) | — |
findings | — | bulkWrite (upsert), update (auto-resolve) | update (evidence_pack_id) |
evidence_packs | — | — | insert |
5. Failure Handling
5.1 sync_ingestion Failures
| Failure | Behavior | Recovery |
|---|---|---|
| Schema validation (step 2) | Sync marked failed, error logged | Fix connector output, re-submit |
| DB write error (steps 4-11) | Sync marked failed, last_error set | Submit a new syncId with the same graph payload — entity-level upserts are idempotent by entity ID, not by syncId |
| Authority path traversal error (step 10) | Sync marked failed | Debug traversal, submit new syncId with same payload |
| Circuit breaker triggered | Sync completes normally, destructive ops blocked | Auto-recovers on next healthy scan — no manual intervention required |
| Duplicate syncId | 200 returned, no jobs enqueued | Intentional — idempotent |
Key property: All entity writes are upserts with deterministic IDs (based on tenantId + sourceSystem + sourceId). Submitting the same NormalizedGraph with a new syncId produces the same entity state. This makes the pipeline safe to replay without rollback.
Idempotency note: The duplicate syncId check (200 no-op) prevents accidental double-submission. For recovery after a failed sync, submit the same graph payload with a new syncId. The entity-level upserts will converge to the same state regardless of syncId.
5.2 evaluate_findings Failures
| Failure | Behavior | Recovery |
|---|---|---|
| Sync not completed | Evaluation skipped, warning logged | Fix sync_ingestion, re-trigger |
| Individual rule throws | Rule error logged, entity marked as failed, auto-resolve skipped for that entity, other rules continue | Fix rule, re-trigger evaluation |
| Path-level evaluation throws | Same as entity-level — skip that path, continue | Fix rule, re-trigger |
| DB upsert error | Throw, evaluation stops | Re-trigger evaluation (findings are idempotent) |
5.3 build_evidence_pack Failures
| Failure | Behavior | Recovery |
|---|---|---|
| Finding not found | Warning logged, job skipped | Finding was deleted — no action needed |
| Entity not found | Warning logged, job skipped | Entity was deleted — stale finding |
| Pack build error | Error logged, job fails | Re-trigger — pack insert is idempotent (new UUID) |
6. Queue Mechanics
Runtime: WorkerRuntime (src/workers/runtime.ts)
| Property | Value |
|---|---|
| Queue type | In-memory FIFO array |
| Processing model | Single-threaded async loop |
| Concurrency | 1 job at a time (sequential) |
| Job ordering | Guaranteed FIFO — sync_ingestion always runs before evaluate_findings |
| Persistence | None — queue lost on process restart |
| Retry | None built-in — re-submit triggers idempotent replay |
Job lifecycle:
enqueue() → queued → processing → completed
→ failed (logged, counter incremented)
Job ordering guarantee for a sync:
1. sync_ingestion ← always first (enqueued at submission time)
2. evaluate_findings ← always second (enqueued at submission time)
3. build_evidence_pack × N ← enqueued by evaluate_findings
Jobs for different syncs can interleave, but the FIFO ordering guarantees that for any given sync, ingestion completes before evaluation starts. The evaluate_findings gate (check sync.status=completed) provides an additional safety check.
7. Correctness Guarantees
7.1 Determinism
| Guarantee | How |
|---|---|
| Entity IDs | sha256(tenantId + ":" + sourceSystem + ":" + sourceId).slice(0, 24) |
| Authority path IDs | hash(tenant_id, workload_id, identity_id, resource_id) |
| Path lineage IDs | hash(tenant_id, workload_id, resource_id) |
| Finding IDs (entity) | "eval:" + hash(finding_type, entity_id) |
| Finding IDs (path) | "eval:" + hash(tenant_id, path_id, finding_type) |
| Composition hashes | hash(identity_id, sorted(roles), sorted(actions)) |
Re-running the same input produces the same IDs → upserts are idempotent.
7.2 Temporal Integrity
| Property | Enforcement |
|---|---|
effective_from never changes | Set on creation, never updated |
resolved_at set exactly once per interval | Set when finding leaves active state |
intervals[] is append-only | New intervals appended, existing never modified |
| Entity versions are temporal | valid_at set on creation, expired_at set when superseded |
| Evidence packs are immutable | Insert-only, never updated, chained via previous_pack_id |
7.3 Post-Sync Validation
After the full pipeline completes (sync_ingestion + evaluate_findings + evidence packs), the following invariants must hold. Validation runs at the end of evaluate_findings, since that is when path-level findings are created:
1. Every active path-level finding references an active authority path
∀ f in findings where f.path_id ≠ null AND f.status = "active":
∃ p in authority_paths where p._id = f.path_id AND p.status = "active"
2. Authority path finding counts match actual findings
∀ p in authority_paths where p.status = "active":
p.current_state.active_finding_count = count(findings where path_id = p._id AND status = "active")
3. No sync stage was skipped
sync.status ∈ {"completed", "completed_with_errors", "failed"}
(never stuck in "running" for > 10 minutes)
4. Sync metrics are internally consistent
metrics.authority_paths_created + metrics.authority_paths_updated
+ metrics.authority_paths_removed ≥ 0 (accounting correctness)
If any validation fails: set sync status to completed_with_errors and emit P1 alert (see Section 8 below).
8. Long-Term Target
When multi-connector reconciliation (Phase 4D) becomes necessary, the monolithic sync_ingestion handler splits into separate stage workers:
Each stage writes a checkpoint to connector_syncs. The sync state machine extends:
Each stage:
- Writes a checkpoint to
connector_syncs - Can only start if previous stage is
completed - Workers are re-entrant and idempotent
- Retryable failures remain in stage with bounded retries (max 5, exponential backoff)
- Non-retryable failures move sync to
failed_<stage>and create alert
Replay: Starts from the failed stage checkpoint, not from stage 1. Requires input payload to be immutable and addressable.
This decomposition is not needed while the platform operates with a single connector and in-process queue. It becomes necessary when:
- Multiple connectors submit concurrently and need cross-connector reconciliation
- Stage-level retries are needed (e.g., external API calls in RESOLVE)
- Stages need independent scaling (e.g., EVALUATE is CPU-heavy)
See Doc 15 (Section 3) for the full stage-level specification.
9. Observability Architecture
9.1 Execution Model
Use scheduled micro-batch ETL per (tenant_id, connector_id) sync.
- Trigger sources: scheduled sync, manual sync, replay sync
- Unit of work: one
sync_id - Concurrency guard: one active sync per
(tenant_id, connector_id) - Processing shape: sequential jobs (W1.1) or ordered stages (long-term target)
This keeps behavior deterministic and auditable while avoiding streaming complexity before it is needed.
9.2 Structured Logging
All pipeline logs must include:
timestampleveltenant_idconnector_idsync_idstage(orjob_typefor W1.1)attemptworker_idtrace_iderror_code(when applicable)
9.3 Metrics
Required metric families:
sv0_sync_started_totalsv0_sync_completed_totalsv0_sync_failed_totalsv0_job_duration_ms{job=sync_ingestion|evaluate_findings|build_evidence_pack}sv0_queue_depth{queue=...}sv0_sync_age_minutes(time since last completed sync per connector)sv0_authority_paths_created_totalsv0_authority_paths_removed_totalsv0_findings_opened_totalsv0_findings_resolved_total
Long-term target (after stage decomposition):
sv0_stage_duration_ms{stage=import|resolve|reconcile|project|evaluate|publish}sv0_stage_retry_total{stage=...}
9.4 Tracing
Trace spans for the current pipeline map to jobs:
sync.ingestion(parent span with attributessync_id,tenant_id,connector_id,trigger_type)sync.evaluatesync.evidence_pack
Long-term target spans map 1:1 with stages:
sync.import,sync.resolve,sync.reconcile,sync.project,sync.evaluate,sync.publish- Parent span:
sync.run
10. SLI/SLO and Alert Policy
SLO Targets
- Sync success rate (24h):
>= 99.0% - P95 end-to-end sync latency:
<= 15m - P95 evaluate→evidence lag:
<= 5m - Data freshness: last successful sync age
<= 2x schedule interval
Alert Matrix
| Alert | Condition | Severity | Owner |
|---|---|---|---|
| Sync hard failure | sync status = failed | P1 | Platform on-call |
| Job stall | no status/heartbeat update for >10m while running | P1 | Platform on-call |
| Ingestion silence | no completed sync for >2x expected interval | P1 | Connector owner + platform |
| Queue backlog growth | queue depth over threshold for 15m | P2 | Platform on-call |
| Delta anomaly | paths/findings delta > 3x 14-day baseline | P2 | Security engineering |
| Correctness violation | post-sync validation failure (completed_with_errors) | P1 | Platform + security engineering |
Long-term target additions (after stage decomposition):
- Stage stall: no checkpoint update for
>10min running stage - Retry exhaustion: stage retries > 5
11. Dashboards and Runbooks
Required Dashboards
- Pipeline health (success/failure/job latency)
- Freshness and backlog (sync age, queue depth, stalled syncs)
- Security output integrity (path and finding deltas, correctness failures)
Required Runbooks
- Sync failure triage and replay procedure
- Schema validation failure handling
- Data freshness outage handling
- Delta anomaly triage (connector bug vs real environment change)
Cleanup Gates
Cleanup tasks (deprecated collections, legacy routes) are blocked until:
- Observability contract from this section is live
- Two consecutive weeks of stable SLO compliance
- Fault-injection drills completed (transient failure retry, permanent failure escalation)
12. Implementation Status
Completed
All W1.1 pipeline features are implemented and in production as of February 2026:
| Task | Description | File(s) | Status |
|---|---|---|---|
| 1 | AuthorityPathDoc type definition | src/domain/authority-paths/types.ts | Done |
| 2 | materializeAuthorityPaths() function | src/ingestion/authority-path-materializer.ts | Done |
| 3 | Authority path storage methods | src/storage/storage-adapter.ts, src/storage/mongo/adapter.ts | Done |
| 4 | MongoDB indexes for authority_paths | src/storage/mongo/adapter.ts | Done |
| 5 | Call materializer from sync_ingestion handler (step 10) | src/workers/handlers/sync-ingestion.ts | Done |
| 6 | Extend FindingDoc with path-level fields | src/domain/findings/types.ts | Done |
| 7 | Path-level evaluation pass in evaluate_findings | src/workers/handlers/evaluate-findings.ts, src/evaluator/index.ts | Done |
| 8 | Path-level auto-resolve logic | src/evaluator/index.ts | Done |
| 9 | Update current_state on authority paths after evaluation | src/workers/handlers/evaluate-findings.ts | Done |
| 10 | Extend sync metrics with authority path counts | src/workers/handlers/sync-ingestion.ts | Done |
| 11 | Circuit breaker safety (entity + AP breakers, cascading gate) | src/ingestion/diff-engine.ts, src/ingestion/authority-path-materializer.ts | Done |
| 12 | Scan scope declaration | src/ingestion/types.ts, src/ingestion/diff-engine.ts | Done |
| 13 | Posture snapshot capture | src/workers/handlers/evaluate-findings.ts | Done |
| 14 | Integration tests (circuit breaker, path materialization, findings) | test/integration/ | Done |
Not changing (current scope)
| Item | Why |
|---|---|
| Worker runtime (in-memory FIFO) | Sufficient for single-connector, in-process use |
| Monolithic sync_ingestion handler | No benefit to splitting without external job queue |
Legacy embedded execution_paths[] on entities | Kept for backward compat until Phase 7 cleanup |
| Sync state machine (running/completed/failed) | No stage-level states needed yet |