Where the Software Stops: Governing a cBioPortal Study
There is one question worth asking any tool you point at a clinical schema: what is it allowed to change without asking you?
Where the Software Stops: Governing a cBioPortal Study
There is one question worth asking any tool you point at a clinical schema: what is it allowed to change without asking you?
For the CoreModels cBioPortal connector the answer is short, and the same for every vendor we support. One verb writes governed structure, and it only ever adds. Everything else observes. Meaning changes belong to people. This piece walks that posture verb by verb, because a trust story you cannot check is just a slogan.
Import adds; it does not overwrite
POST /graph/integrations/cbioportal/import/{PROJECT_ID} is the only call that writes governed
structure, and it runs at Admin role. Its contract is additive: existing governed nodes are never
mutated or deleted.
Import the same study twice, or a revised version of it, and the response says exactly what that
meant — datasetsAdded beside datasetsSkippedExisting, fieldsAdded, nodesEnriched. If a
staging file disagrees with what the project already governs, the disagreement is not resolved in
the file's favour. It is not resolved at all. It becomes drift for a human to look at.
One deliberate exception, stated plainly because hiding it would be worse: the vendor metadata the
connector attaches to nodes — native datatype, the cbio.* display name and priority, the study
state facts — is refreshed on each import. That is estate bookkeeping, a record of what the vendor
currently says; it never rewrites a description, a type, or a constraint you have governed.
Everything else is a read
Audit, re-audit, generate, history, badge, and status all run at Viewer role, and none of them touches governed meaning. That is not a convention we ask you to trust; it is where the role check sits.
Two details make the read-only claim precise. An audit does not even
remember itself unless asked: recordHistory defaults to false, so the verb stays strictly a read
until you decide the run should leave a mark. Re-audit is the exception and says so — it always
records its run, because "does the last-known study still fit the model?" is precisely the event you
want on the record.
The audit produces findings and stops. field-type-drift on an attribute is a statement of fact.
Whether the governed model should move or the file is wrong is a judgment the connector deliberately
declines to make.
Headers only, as a design decision
The parser underneath every one of those verbs reads the four # header rows and the attribute-ID
row, and discards everything below. That is the governance design, not a shortcut: no patient-level
value ever reaches CoreModels, so a schema-only upload with the data rows stripped is a complete
input, and the whole loop runs on files that are safe to move between institutions. The flip side is
stated just as plainly — whatever lives in the data rows is invisible here. Values, OncoTree codes,
sample counts: the connector makes no claim about any of them, because it never sees them.
Honest about what it could not represent
No mapping between two systems is lossless, and a tool that hides its approximations is teaching you to trust the wrong things. Lossiness in CoreModels is a success channel: an operation can succeed and still hand you an itemized list of what it softened. Errors mean it could not proceed; lossiness means it proceeded, and here is what you should know. For a cBioPortal study the disclosures are concrete:
- cBioPortal's
NUMBERdoes not distinguish integers from decimals, so it maps to a numeric kind covering both — recorded as an approximation, not assumed away.BOOLEANandSTRINGmap exactly; a datatype token we do not recognize falls back to string and says so. - Ask for a staging scaffold from a governed Type that has no elements and the connector does not emit an empty, invalid file. It skips the type and records a structural drop explaining why.
- Very large studies can exceed the snapshot storage cap. The import then reports
snapshotStored: falsewith a lossiness record. Audits against fresh artifacts keep working; the re-audit verb has no stored snapshot to run against, and you are told so rather than left to discover it.
Drift as accumulating evidence
A single audit is a point-in-time check. Governance needs a record, so the check becomes a trail. The history endpoint returns recent runs per study — timestamp, trigger, error and warning and info counts, the finding codes, the artifact fingerprint — and the badge renders the latest recorded run as green, yellow, red, or gray. Each run is machine-readable, so the trail is a sequence of comparable data points rather than prose.
The value shows up the day someone asks when a definition diverged. That question used to be answered by memory. Now it is answered by a fingerprint and a timestamp.
The same loop is built to hold a portfolio, not just a study. Each governed study keeps its own audit trail, its own badge, and its own snapshot stored at import time, and a config-gated scheduled worker — off by default, enabled per project by an operator — re-audits stored snapshots against the current governed model on a cadence, with no files re-uploaded and no credentials involved. When a run crosses from clean to dirty, an opt-in Slack notification says so. Many studies, one dictionary, one recurring question: does everything we have published still mean what we say it means?
Review, then deploy
The audit sits in front of a merge rather than behind it. On the machine-to-machine surface it
returns everything a gate needs: an errorCount that fails the build above zero, coded findings
that name their subjects, and a Markdown report ready to post as a pull-request comment. The change
is reviewed while it is still a proposal.
For the harder case — a study update that would change what the governed model means, not just
whether it still agrees — there is a proposal surface built on the same principle.
POST /graph/integrations/cbioportal/sync/propose/{PROJECT_ID} takes fresh staging files and
returns a plan: a three-way comparison of the estate base stored at import time, the fresh
artifacts, and the live governed graph, with every operation classified.
Additive operations are new governed material — exactly today's import semantics. Metadata-only operations are estate bookkeeping, the refresh an import already performs. Meaning-affecting operations would mutate governed semantics — the class that always requires a human decision. Where both sides moved the same fact since the base, the operation is flagged as a conflict rather than quietly resolved. Each operation carries the "old → new" changes it would make and its own declared lossiness.
Proposing is itself read-only against governed meaning. Its only writes are bookkeeping: the stored plan and its ledger entry, which supersedes any earlier proposal. The plan can be fetched back by id and the ledger read per vendor, so what a study update would do to governed meaning becomes a durable artifact instead of a conversation someone half-remembers. Changing that meaning is still a human act; the plan exists so the act is informed rather than improvised.
The boundary is the software, not the policy
Generation follows the same logic. The connector produces an upload-ready staging scaffold from the governed model, as an artifact, for you to inspect and upload deliberately. It pushes nothing to a portal, and it could not: CoreModels holds no cBioPortal credentials, and a live portal connection is a declared-but-deferred capability.
That is the point worth ending on. The line between "the tool proposed this" and "a human deployed it" is not a policy you enforce or an option you remember to leave switched off. It is where the software stops.
The cBioPortal quickstart in the CoreModels integration docs lists each verb with the role it runs at and the response it returns.