Skip to content
CapEx feeds & findings

CapEx feeds & findings

The Atlas CapEx engine is the capital-governance data-quality engine behind these pages:

  • Capex Monitor
  • Capex Lineage
  • the Analyst and CFO dashboards
  • Progress
  • the DQ overview’s CapEx score

CORE ported the engine from Atlas and none of its numbers. An operator uploads three feeds, the console stores them in its own appfs root, and every figure on every page is computed from what was uploaded.

The contract is CapexService in grpc/operators/core/api/proto/runink/core/atlas/v1/capex.proto. The engine is the stdlib package grpc/operators/core/internal/atlas/capex. The store is grpc/operators/core/internal/console/atlas_capex_store.go.

The three feeds

Each feed is a CSV with a fixed header contract (internal/atlas/capex/parse.go, feedSpecs). The contract is also returned in GetFeeds, so the upload dialog can show it without hard-coding it.

Feed (Feed enum)Columns, in orderMust not be blank
FEED_CAPITAL_PLAN (capital_plan)ItemId, Description, Type, Category, Lead_time_in_weeks, PurchaseDate, Units, PricePerUnit, AmountDescription, Amount
FEED_CAPITAL_REQUEST (capital_request)ID, RequestDate, ItemId, ApprovalDate, ApprovalId, Vendor, AmountID, Amount
FEED_SPEND (spend)PoNo, PODate, Amount, ApprovalId, ItemId, TypePoNo, Amount

Header names are trimmed. The first column with a given name wins. Dates must be ISO YYYY-MM-DD. A date that does not parse raises its own finding (CP-008, CR-010, SP-006).

Ingest: stage → preview → commit

Ingest replaces whole feeds. It has three steps, ported from Atlas’s ImportFeedsDialog. There is no row-level edit and no merge, so the uploaded file is the feed.

StageFeed

Parses one CSV for one feed and holds the result server-side under a stage_id. The current feeds do not change. A file-level problem is returned as a ParseIssue in errors with an empty stage_id, not as a gRPC error, because the file is at fault rather than the caller.

PreviewIngest

Evaluates the rules over the candidate dataset: the staged feeds overlaid on the current ones. It returns the before/after summary and the candidate findings. It is a read, so it writes nothing and records no scan run.

CommitIngest

Each staged feed replaces its current feed wholesale, and one ScanRun is recorded. The stages are consumed. An ingest takes at most one upload per feed.

ClearFeed empties one feed and records a ScanRun with trigger SCAN_TRIGGER_CLEAR. CORE never refills a feed with sample rows. There is deliberately no “reset to sample” RPC.

Caps (each one refuses; nothing is truncated)

CapValueWhat you get
CSV size per upload8 MiBParseIssue code too_large, nothing staged
Data rows per feed50,000ParseIssue code too_large, nothing staged
EncodingUTF-8 onlyParseIssue code not_utf8, with the line of the first bad byte
Pending stages12RESOURCE_EXHAUSTED, naming when the oldest expires
Stage lifetime30 minutesan expired stage_id is INVALID_ARGUMENT
Commit note1,000 bytesINVALID_ARGUMENT
Scan history50 runsthe oldest is dropped
Unpaged per-row lists in a read500 entriesthe read says so in provenance.unmeasured

The gRPC-web transport accepts messages up to 16 MiB (atlasMaxRecvBytes). This keeps the 8 MiB feed cap, not the transport, as the limit a user meets.

Pulling a feed from a connection

PullFeed stages a feed from a registered connection instead of an uploaded file:

  • It runs one bounded, read-only SELECT of the mapped columns from one dataset. Identifiers are validated and engine-quoted.
  • LIMIT is the row cap + 1, so “exactly at the cap” and “over the cap” can be told apart.
  • It has a 90-second budget (capexPullBudget).
  • It uses the same store/estate runtime and the same audited, one-call credential read as Resolve.

The rows are written as CSV and handed to StageFeed unchanged. A pulled stage therefore gets every upload check, and a person still commits it after the preview.

Unlike Resolve, a pull keeps cells. A feed is data by definition, just as an uploaded CSV is.

The request has an optional runner field. It follows the same rules as a Resolve action: absent means the connection’s binding, console means Runink managed, and a name means that runner. See Resolve.

A commit whose every stage was pulled records its ScanRun as SCAN_TRIGGER_CONNECTION, and each feed’s filename reads <connection> · <dataset>. If even one uploaded file is in the commit, the trigger is SCAN_TRIGGER_UPLOAD (capexCommitTrigger).

Scan runs

ScanTriggerCause
SCAN_TRIGGER_UPLOADA CommitIngest that includes at least one uploaded CSV
SCAN_TRIGGER_CONNECTIONA CommitIngest whose every stage was pulled
SCAN_TRIGGER_MANUALA playbook’s capex_scan step. The proto marks a hand-requested re-scan as reserved for a later phase
SCAN_TRIGGER_CLEARA ClearFeed

ListScanRuns is the Progress page’s history, newest first.

Findings are recomputed on read and never stored. They are cached by upload tables, Go-Live date and governance scope. This is why a ListFindings page_token fails with an INVALID_ARGUMENT like the following once anything changes between pages:

the findings changed (a new ingest, Go-Live date, rule status or threshold) since this page_token was issued — list again from the first page

Configuration: the Go-Live date

GetConfig returns the engine configuration. Today the only configurable field is go_live_date. With nothing stored, it answers Atlas’s default, 2027-03-31, with provenance.source = DATA_SOURCE_DEFAULT, so the page can say the date is a default rather than a decision.

UpdateConfig changes the date. That changes CP-007’s verdicts on the next read. It does not record a ScanRun, because no feed changed.

The other engine constants are changed only through rule governance, as PARAMETER changes. Their defaults (capex.DefaultConfig) are:

  • 14-day schedule tolerance
  • $500 capital unit-price threshold
  • 3-day minimum approval lag
  • 5% request/plan tolerance
  • 1.10 overspend factor

The rule book

GetRuleBook returns every rule: Atlas’s 21, in Atlas’s order, followed by three date rules that CORE added (internal/atlas/capex/rules.go).

RuleSheetSeverityCondition
CP-001capital_planCriticalItemId cannot be blank
CP-002capital_planCriticalEvery item must have a valid ItemId (unique, well-formed)
CP-003capital_planMediumDescription must be meaningful — not generic like ‘Tool’
CP-004capital_planHighType must be Manufacturing, Tools, or Testing
CP-005capital_planHighCategory must be Capital or Non-Capital
CP-006capital_planCriticalUnit price < $500 must be Non-Capital
CP-007capital_planCriticalPurchaseDate must align with Go-Live − Lead_time_in_weeks (±14d tolerance)
CR-001capital_requestCriticalItemId cannot be blank
CR-002capital_requestCriticalItemId must reference capital_plan
CR-003capital_requestCriticalApprovalId cannot be blank
CR-004capital_requestHighVendor cannot be blank
CR-005capital_requestCriticalRequestDate must not exceed plan PurchaseDate
CR-006capital_requestMediumRequestDate aligned with plan PurchaseDate (±14d)
CR-007capital_requestHighApprovalDate ≥ RequestDate + 3 days
CR-008capital_planCriticalEvery capital_plan item must have at least one capital_request
CR-009capital_requestHighRequest amount must be within 5% of the capital_plan amount
SP-001spendCriticalApprovalId, ItemId, and Type cannot be blank
SP-002spendCriticalPODate must be ≥ ApprovalDate (capital_request)
SP-003spendCriticalSpend > approved amount × 1.10 requires additional approval
SP-004spendCriticalApprovalId must reference capital_request
SP-005spendHighSpend Type must match capital_plan Type for the same ItemId
CP-008capital_planCriticalPurchaseDate must be a valid ISO date (YYYY-MM-DD)
CR-010capital_requestCriticalRequestDate and ApprovalDate must be valid ISO dates (YYYY-MM-DD)
SP-006spendCriticalPODate must be a valid ISO date (YYYY-MM-DD)

CR-008 is a capital_request rule reported on the capital_plan sheet, because the defect is the plan row that has no request. That is why Sheet and Feed are separate enums.

Control-effectiveness dimensions

The dashboards group rules into Atlas’s dimensions (dashboard.go):

DimensionRules
CompletenessCP-001, CR-003, CR-005, SP-001
ValidityCP-004, CR-004, SP-005
ConsistencyCR-002, SP-002, SP-004
AccuracyCP-005, CR-006, SP-003
TimelinessNo rule mapped. Absent
UniquenessNo rule mapped. Absent

Rule status changes what is scored

Only APPROVED rules are scored:

  • PENDING and DRAFT rules still produce findings, flagged dry_run, but those findings are excluded from every figure: summary, dashboards, insights and scan runs. Each excluded rule is named in provenance.unmeasured.
  • REJECTED rules produce no findings at all.

Approved thresholds feed the engine configuration. See Rule governance.

Dashboards and absence

GetAnalystDashboard and GetCfoDashboard are computed from the current feeds. A section the feeds cannot answer is listed in absent_sections, with the reason, and is never filled. On the CFO page, for example, forecast variance, capital efficiency, ROI and the executive summary are absent.

Who may write

Every CapEx write is admin-write under CORE_ATLAS_ADMINS, and each is audited under resource atlas-capex:

RPCAudit action
StageFeedatlas.capex.stage
PullFeedatlas.capex.pull
CommitIngestatlas.capex.commit
ClearFeedatlas.capex.clear
UpdateConfigatlas.capex.config.update

On a console with no sign-in configured, each write is refused with a message that begins this console has no sign-in configured, so an Atlas CapEx change would be unattributable.