Measure what the source says, and refuse what it does not.
An acquirerAcquirerA program that reads a customer's operational records, calculates quantities from them and records the results in NovusLens. reads one of the customer’s own systems, computes a quantity from the raw records, and writes it to the twinTwinNovusLens’s model of one organisation, built from records about its teams, services and how they rely on one another. It is only as current as those records. as observed evidence. The correctorCorrectorThe NovusLens component that compares measurements with recorded statements and adds observations and differences to the log. then sets that evidence against the account the organisation has told NovusLens, and records any difference as a findingFindingA difference in the records that needs a decision. It includes its sources and can describe a slow handoff, disputed fact or missing owner.. Neither step changes the source system.
What it is for
Every figure in the twin depends on its source. Examples include dependencyDependencyA link where one unit needs something from another. A dependency is not a reporting line: it shows where work relies on something else. lead timesLead timeThe elapsed time between starting a request or handoff and completing it. and cellCellA box on the map that stands for a team, service, supplier or AI agent. It is a unit you can inspect, not necessarily a department or a person. Complexity, Agility and RecoverabilityRecoverabilityThe R in C/A/R: how well a team or service restores acceptable service after a failure. Recovery times and tested alternatives can supply evidence. (C/A/RC/A/RThree separate scores for a team or service: Complexity, Agility and Recoverability. Each uses current evidence and the customer’s stated scoring rules.) scores. Acquisition is what lets a figure say which system measured it, over which population, and when. A figure that cannot be measured is shown as unknown rather than filled from a neighbouring source.
How it works
Acquisition commands
./lens connectors acquire --org <org> --kind <kind> runs one acquisition pass. Each kind has its own collector and its own refusals.
--kind | Source and inputs | What it measures | What it refuses or counts |
|---|---|---|---|
servicenow, jira | Ticket APIAPIApplication programming interface: a defined way for one program to request data or actions from another., --url, optional --token, --mapping | Edge lead time: the mean of resolved minus opened, per mapped ticket group, over resolved tickets only. | Open tickets are skipped and counted. Unmapped groups are counted, not guessed onto a capabilityCapabilityWhat a team can provide or an agent can do. NovusLens records the claim and the evidence that supports it.. A partial listing writes nothing. |
prometheus | Prometheus-shaped query API, --url | Cell C/A/R from series the organisation already exports, scaled by its own linear terms. | A cell missing any calibrated series is refused. A series returning more than one sample is refused as ambiguous. Unmapped jobs are reported. |
github | Workflow-runs API in the GitHub shape, --url | Quantities computed from raw runs: pipelines, change lead days, change failure rate and recovery hours. | In-progress runs are skipped. A failure with no later success in its own workflow and branch cannot yield a recovery time. A partial repository listing is refused with its counts. |
pagerduty | Incident API in the PagerDuty shape, --url | Mean hours from created to resolved for each mapped service (pd_mttr_hours). | Unresolved incidents are skipped. A service with no resolved incident has no recovery figure. Standalone, it refuses most triples, because incidents measure nothing about Complexity or Agility. |
composite | Repeatable --source KIND=URL, where KIND is prometheus, github or pagerduty | One calibrationCalibrationThe customer's own rule that turns raw figures, such as a gauge, a workflow history or an incident log, into a C/A/R score. A score moves when the calibration moves, and the change is recorded. drawing each quantity from the source that measures it. See composition. | As for each contributing source, and the whole-or-nothing rule applies to the combined triple. |
network-flow | Flow-log exports: --format hubble or vpc-flow-v2, one or more --flow-log FILE | Traffic leaving each mapped cell, grouped by destination, protocol and whether the network forwarded or dropped it. | Unparseable lines are counted with their line number. Unmapped or ambiguous sources are not attributed. Coverage gaps such as NODATA, SKIPDATA and Hubble lost_events are counted, never read as quiet. |
The network-flow kind is a flow record of what the network forwarded or dropped. It is never a gatewayGatewayA service that accepts requests and passes them into another system. The Mycelium gateway gives NovusLens access to the mesh. decision, and every entry carries observation_point: "network". No real Container Network InterfaceCNIContainer Network Interface: the standard used by software that connects containers to a network. (CNI) or virtual private cloudVPCVirtual private cloud: a private network defined inside a cloud provider's infrastructure. (VPC) source has been connected. Exports are supplied files, so this path needs no paging.
Simulated sources declare themselves in the payloadPayloadThe data carried inside a message, apart from the surrounding fields that identify and date it., and the flag propagates into every entry. A composed entry is simulated if any contributing source is simulated, so mixed data never carries a real label.
The customer-authored mapping
Each acquirer takes a --mapping JSONJSONJavaScript Object Notation: a text format for data, using named fields, lists and values. Configuration files and service messages use it. file that the customer writes. It connects names in the source system to cells or capabilities in the twin. For ticket acquisition the joins are ticket groups to capabilities. For the cell kinds the joins are job, repo or service per cell, and an axes block gives each axis its calibration.
A calibration term names either a series (an exported Prometheus series) or a quantity (a value an acquirer derives). The score is base plus the sum of per_unit times each value, clamped to 0 to 100 and rounded. The loader refuses a calibration with no terms, no finite base, or a zero or non-finite per_unit. A constant cannot stand for a measurement. NovusLens applies the calibration as declared and never tunes it. A change to it is an edit to the mapping fileMappingA customer-written file that connects names in a source system to teams or services in NovusLens. For scores, it also specifies the scoring rules., which is visible in that file’s history.
The mapping can declare window_days for ticket acquisition. Without it, the population is every resolved ticket in the instance’s history, and the record says so.
Paging and incomplete collections
Listings are read page by page through one shared walker in paging.py. It keeps a page budget of 50 pages, removes duplicate rows using their identifiers, and stops if a source repeats a page. A listing is complete when the source reports its end or the advertised total has arrived. Without an advertised total, a short final page ends it. It is never complete because the budget ran out or the rows repeated.
Every listing returns a record of fetched, advertised, pages and complete. The acquirers act on that record:
- The ticket acquirer writes nothing when the listing is partial, and its report says how many tickets were retrieved and how many were advertised.
- The CIContinuous integrationAutomated checks run when code changes, usually including building the code and running tests. and incident acquirers refuse any repository or service whose listing is incomplete. The refusal carries the counts. A partial page is never scored.
- The GitHub-shaped listing is refused after its first page when the advertised total is beyond the budget, because the outcome is already decided.
- Incomplete repository and service lists are labelled incomplete. Unmapped items are never presented as a complete inventory.
Composition
composite runs each contributing source’s collector and merges the quantities per cell, using each source’s own join. The mapping stays one file with one calibration. Each term names the series or quantity of the source that measures it, so the organisation decides which source supplies each measure. A self-exported gauge can be superseded by naming the system of record in a term, and the gauge then has no route into the score.
By default all three scores must be available before any is written. A quantity that no given source supplies refuses the cell, and the missing names appear in the report. With partial_axes: true in the calibration, each measurable axis is written and the rest stay unknown. Unknown is shown as unavailable and is never written as zero.
ProvenanceProvenanceThe record of where a value came from, who stated it and when. Every value keeps this record. cites every contributor, for example prometheus:…+pagerduty:… (simulated source).
The corrector
correct runs one comparison pass (corrector.run_correction). It compares the asserted account, which is rebuilt from the commit log, with the measured account in the telemetryTelemetryMeasurements and activity records sent by a running system, such as ticket timings, network traffic or workflow results. namespacesNamespaceA naming area that keeps one set of records apart from another. NovusLens uses the conway/ prefix so its keys do not occupy Mycelium's reserved names..
- Read. Edge and cell feeds are read through the telemetry scanners. A feed older than three times its declared
refresh_interval_msis discarded. A feed that never declared its freshness is treated as stale and reported as a warning. - Attribute. A feed attaches to an asserted edge only on an exact or subset name match. If several asserted edges share a name, the feed must name its consumer and provider. Anything else is an unmatched finding, not a guess.
- Fence. The pass takes the distributed lockDistributed lockA lock that coordinates programs running in separate processes or on separate machines, so only one holder is recognised at a time.
conway-correct-<org>with a 30-second time to liveTTLTime to live: the period after which a temporary record or lock is no longer treated as current. (TTL), but only to obtain a fencing tokenFencing tokenA number that identifies a turn to update shared data. NovusLens rejects an earlier correction’s writes after a newer correction is applied.. The lock is released before the writes start, so a long pass cannot hold it past its TTL. The token travels on every observed write from the pass. - Write. Each measured edge carries the token.
observed_at_msuses the feed’swritten_at_ms, or the pass time if absent.valid_until_msaddsCONWAY_OBSERVATION_TTL_DAYSdays (default 30) to that time. Cell and axis observations are written the same way. Flow observations follow the same pattern. - Find. A difference between the stated and measured handoffHandoffWork or information passed from one team or service to another. Its measured or stated completion time determines the dependency class. becomes an
asserted-observed-deltafinding, with the identifierdelta-<org>-<consumer>~<provider>~<capability>. A measurement against an asserted value of Unknown is afirst-measurementfinding instead. A feed that matches nothing becomes anunmatched-telemetryfinding. The asserted account is kept beside the measurement. Findings close when a later pass no longer reproduces the condition. - Record. Each correction also writes an authoritative record through
consistent_set. Nothing reads that record back, so it demonstrates linearisabilityLinearisabilityAccepted operations behave as if they happened one at a time in a shared order. That order respects operations that already finished. and nothing more. A write-once conflict is reported as a warning and does not stop the pass.
The protection against a stale holder is the token and the materialiserMaterialiserThe component that reads recorded changes in order and updates the database. It records its place so it can continue after a restart.’s check. In apps/graph/src/conway_graph/materialiser.py, an observed write whose token is below the correction epochCorrection epochThe highest correction token already applied for one organisation. An observation with a lower token belongs to an older correction pass and is refused. already applied for the organisation is refused with StaleCorrectionError. It is quarantinedQuarantineA holding area for a log entry that could not be applied. The entry keeps its error and waits for a retry or an operator. and surfaced, and the newer correction stands. The stored correction token advances to the highest token seen. Writes that carry no token are admitted, which the code reserves for tests and seeds.
The lock taken for a token, the observed writes carrying it, and the materialiser refusing a stale holder's late write.
Estate drift
drift compares what the infrastructure codeInfrastructure codeFiles that describe the computing resources an organisation intends to create and how they should be configured. declares with what the provider’s control plane reports, and surfaces the difference as findings. The declared tier is Terraform state. The actual tier is the control-plane export. Both are kept, and no verdict is issued.
The join is strict. A declared resource is matched on its recorded deployed name (terraform:resource_name), converted to the standard identifier format, within the same provider and domain. Names are never matched fuzzily. A declared resource with no deployed name is counted as unjoinable and is never reported as drift. Three finding kinds are raised:
estate-drift-absent-resource: declared in code, not reported by the control plane.estate-drift-attribute: both tiers report the resource, and its region differs.estate-drift-undeclared-resource: the control plane places the resource in a containerContainerA running program with its own packaged software and isolated environment, managed by Docker on the host. that names a managed-by tool, and that tool’s state does not describe it. A resource with no managing tool is unmanaged, not drift.
Each finding carries the coverage counts, so a clean sweep is not read as “no drift” when nothing was comparable. Without --estate SOURCE=FIXTURE, the sweep re-reads the newest artifact of each kind from the evidence store.
Interfaces
| Entry point | Role |
|---|---|
acquire --kind …, --mapping, --token, --refresh-ms (default 30000) | One acquisition pass, per kind above. |
acquire --kind composite --source KIND=URL | Composed pass across several sources. |
acquire --kind network-flow --format … --flow-log FILE | Flow-export pass. |
correct | One comparison pass. Prints corrections, confirmations, telemetry warnings and the fencing token used. |
drift [--estate SOURCE=FIXTURE] | Declared-against-actual estate sweep. |
boundary --gateway HOST:PORT [--enrollment FILE] | Read-only pull of an agent estateAgent estateThe collection of AI agents an organisation operates, together with the systems and providers they use.’s boundary feedBoundary feedRecords supplied by the customer’s own systems about activity at the edge of an agent or service. NovusLens reads those records; it does not watch the agent.. Covered on the agent evidence sources page. |
CONWAY_OBSERVATION_TTL_DAYS | Observation windowObservation windowThe period in which a measurement counts as current. The default is 30 days. After it passes, the measurement remains as history and is counted as expired. for corrected evidence. Default 30. |
NOVUSLENS_MESH_HTTP_ADDR, NOVUSLENS_MESH_HTTP_BASE | Default twin host and port (127.0.0.1 and 8400). |
Invariants and failure behaviour
- Read-only against the customer. An acquirer only reads the source. A failed or partial read writes nothing for the affected scopeScopeThe work, data, activity or access that a rule, statement or calculation covers. For access, it names the records an account can see. For team measures, “Own operations” covers the selected unit. “Including descendants” adds the units beneath it..
- Use measurements, never fill gaps with estimates. An unmapped item, an open ticket, an unresolved incident or an in-progress run is counted and reported. It is never estimated onto a cell.
- Whole or nothing. A C/A/R triple is written in full or not at all, unless the calibration sets
partial_axes. - A combined record keeps simulated labels. Any simulated contributor makes the entry simulated.
- Old measurements stop counting as current. Evidence that stops being refreshed goes stale after three refresh intervals, and an observation expires after its window. The row stays and remains historically measured.
- Both accounts are kept. A delta is a finding for a person to resolve. Neither the asserted nor the measured account is overwritten.
- Stale holders are refused. A late write under an older fencing token is quarantined at the materialiser.