THE GATE
The Port Coverage Gate
A deterministic transform will hand you a wrong answer just as confidently as a right one. This is the layer that checks the answer: four independent questions asked of every run, each of which exists because the pipeline once failed silently in exactly that way and nobody noticed. It currently proves all 123 ports in scope land correctly, which a day ago it could not say of a single one.
Why a gate at all
Three separate defects reached a green pipeline on this deployment, and not one of them produced an error. A Teamcenter utility accepted a misspelled option, printed *** Importing File ***, created nothing and exited 0. A transform accepted a flag it did not support, ignored it and exited 0. A deduplication step discarded 43 real component instances and exited 0.
On this stack, "no error" is not evidence. Every check below exists because something passed while being wrong.
The four questions
They run in this order deliberately: each is meaningless if the one before it is untrue.
| # | Question | Answered against | Fails on |
|---|---|---|---|
| 1 | Conservation. Does the artifact still contain what the model had? | the Teamcenter extract | any component that vanished |
| 2 | Coverage. Did every port land on something that can carry it? | the neutral exchange bundle | unrealized port/branch cells |
| 3 | Regression. Does this run owe anything it did not owe before? | the stored baseline | new gaps only, not pre-existing debt |
| 4 | Provenance. Was the baseline produced by this same build? | a hash of the transform code | refuses to compare rather than inventing a delta |
Check the output against the source, not against yourself
This is the one that matters most, and it was the last to be built. A translator that reconciles its final artifact against its own intermediate is grading its own homework: both come from the same transform, so anything the transform drops is missing from both sides of the comparison.
That is not theoretical. Closing a version skew on two supporting modules took the component count from 93 to 50, discarding two of every three thrusters on a satellite model. The coverage ledger went from 213 subjects to 110 and reported every one of them blocking, exactly as before. A smaller number read as a smaller model, not a broken one. The only visible signal was the file shrinking from 1,094,386 bytes to 797,101.
So conservation starts from tree.json, the extract, which the transform did not write, and follows the counts forward one hop at a time:
extract.json --> exchange bundle --> capital.xml
(Teamcenter) (neutral) (Capital)
Checking each hop separately is what makes it useful. "93 became 50" is a bug report. "93 survived into the bundle and 43 vanished at the emitter" names the file to open.
Current run: every block occurrence Teamcenter reported reached Capital.
The port dashboard
Live figures from the current run of model 040808, read back out of Teamcenter rather than off a workstation. Every port in scope is now shown to land on something that can carry it.
123 cells, 123 device pins, 74 clusters, and nothing lost between the Teamcenter extract and the Capital document. This is the first run in which the gate has passed rather than reported.
image and video as signal tokens when an HDMI interface was declared, and the copy never did. The auditor read imageOut as a signal port and expected a pin; the emitter read the same port as carrying no domain and wrote none. Consolidating onto the one registry closed all ten at once. The independence being protected was not real: the emitter already imported its sibling module, and the registry imports nothing at all.UNREALIZED. They were false. The auditor indexed the Capital document by cluster name, and 93 clusters share only 50 names, so cells belonging to a duplicate namesake were judged against a different component's interfaces. The tell was in the distribution: all 29 sat on duplicate-named components, while the artifact demonstrably carried all 138 port ids. Keying on the port id instead moved 27 of them to REALIZED and 2 to DEGRADED, and UNREALIZED is now zero.UNVERIFIABLE or NO_ARTIFACT. A gate that fails everything carries exactly as much information as one that passes everything. Three changes moved it from a number to a measurement, and not one of them was a change to the model.213 was never 213 findings
The old headline decomposed into two things that had nothing to do with each other, and neither was a gap in the model.
90 cells were our own misconfiguration. NO_ARTIFACT against the software, NX and Amesim branches: this pipeline builds the Capital artifact and nothing else, so those cells said only "this pipeline does not build software". The auditor had supported a --branches scope since it was written, and its own source warns that an unscoped run "buries the real finding under noise". The tier simply never passed it. Scoping to capital removed 42% of the output at a stroke.
123 cells were one defect, not 123. Every one carried the identical evidence string, and every one was routed: true:
{ "component": "Flight Processor",
"port": "attDataIn", "domain": "signal", "branch": "capital",
"state": "UNVERIFIABLE", "routed": true,
"evidence": { "interfaces": 2,
"why": "cluster interfaces are named IF1..IFn with no
Teamcenter port name to bind against" } }
One evidence string repeated 123 times is a single fix wearing a crowd's clothing. It is worth reading the evidence before counting the rows.
Port identity: the thing that made the number mean something
The diagnosis was verified directly against the 1.1 MB artifact rather than taken from the auditor's own claim. The file carried 145 clusterinterface elements and exactly six distinct names between them, numbered positionally, one sequence per cluster. shortdescription was empty, so the Teamcenter name was not parked there either, and the string attDataIn appeared zero times in the whole document.
Connectivity survived; identity did not. The pathways were right, the 43 topological segments were right, every port was routed. What could not be established was which Teamcenter port any given Capital interface corresponded to.
| Before | Now | |
|---|---|---|
| distinct interface names | 6 | 33 |
| what they look like | IF1, IF1, IF1 … | attDataIn, busDataIf, monDataIn, pwrIn, imageOut |
TC Port ID properties | 0 | 251 |
| components in the artifact | 93 | 93 (50 had the collapse come with it) |
| coverage verdict | 123 capital cells, not one of them verifiable | 123 cells, all 123 realized |
TC Port ID property is the join key, deliberately read-only so an engineer can rename an interface for legibility without breaking reconciliation.Why this took longer than turning on a flag
Both fixes were configuration, not new code. Neither was a one-line change, and the reason is worth recording.
The flag already existed and had been deliberately left off. --port-identity was built and tested long before this, switched off because it changes the emitted Capital contract and that is the caller's decision. But the transform on the Dispatcher host predated the flag entirely. An older build does not reject an option it has never heard of: it accepts it, ignores it, and exits 0. Passing the flag would have changed nothing and reported success, which is the same silent no-op that let a stale transform swallow --emit-exchange for weeks. The module now checks the artifact really contains a TC Port ID when identity was requested, and says so loudly when it does not.
And the newer transform brought a defect with it. The emitter that knows how to stamp port identity also collapses components sharing a name, unconditionally, and on this model that discards 43 of 93 occurrences including two of the satellite's three thrusters. The two features shipped in the same generation of code, so port identity could not be adopted without the loss. That collapse is now an option rather than a certainty, defaulting to the long-standing behaviour so nothing else moves, with an explicit opt-out taken only where it has been measured.
What the gate refuses to say
Two deliberate silences, both of which cost more to build than the loud version would have.
A missing baseline is reported as could not compare, never as "no changes". Those look identical on a dashboard and mean opposite things. On a first run the tool says plainly that it had nothing to compare against.
A baseline produced by a different build of the transform is refused outright. When the pipeline changed underneath it, comparing across that boundary would have manufactured roughly a hundred phantom removals, addressed to engineers who caused none of them and could fix none of them. The baseline now carries a fingerprint of the transform code, and a mismatch produces:
BASELINE was produced by a different pipeline build
baseline ceb9cf9cc72830cd this run a8f82005d66655e7
No comparison was made. A transform change is not a model
change, and diffing across one reports gaps nobody
introduced and nobody can fix.
That guard was written for a hypothetical and earned its keep the same day, firing correctly on a real rollback.
Running it
Both checks run inside the Dispatcher module as ordinary stages, and both are advisory until switched on. The switches are separate on purpose: a coverage regression is debt, while a conservation loss is a wrong artifact, and a site may reasonably block on the second long before it can afford to block on the first.
$ node bin/audit-extract-conservation.mjs \
--tree tree.json --exchange exchange.json --capital capital.xml --gate
extract block occurrences 93
distinct occurrence path 93
distinct name 69 <- NOT an identity
exchange components 93
reported as merged 0
capital devices 93
PASS - every block occurrence Teamcenter reported reached Capital.
The run also emits a deliberately tiny rollup.json, flat enough that a Teamcenter saved query can turn it into a native report without parsing anything:
{ "model": "040808", "coverage_scope": "capital",
"coverage_subjects": 123, "coverage_blocking": 0,
"coverage_regressions": null, "coverage_compared": false,
"extract_blocks": 93, "emitted_components": 93,
"dropped_components": 0, "conservation_losses": 0 }
extract_blocks and emitted_components both read 93 while only 74 components reach the Capital document, and that is not a contradiction: Capital is now told to carry only components with an electrical or signal port, so a valve, a tank, a thermistor and a radiator are held back by declaration. The conservation check is given the same restriction the emitter was given, and reports it as declared rather than as content going missing. Without that, a deliberate scope and a silent drop would look identical at this hop.
UNREALIZED → REALIZED and 2 moved UNREALIZED → DEGRADED, and the diff counted the downgrades and the newly-visible cells as new debt. The provenance check should have refused this comparison and did not, because it hashed the transform and not the auditor — and the auditor is what computes the ledger. A phantom delta walked in through the one door the guard had left open. The fingerprint now covers the measurer as well as the thing measured.The Dispatcher wiring these stages sit inside is documented on the Capital branch page, and the gaps this gate has surfaced are tracked on the status board.
What each state means
The report states facts and links here. These are the verdicts the auditor can record for one port on one branch, worst first. The code in the second line is the machine name written into the ledger and used by waivers; it is what to quote in a bug report.
| State | What it asserts | Blocks a handoff |
|---|---|---|
Wrong disciplineMISBOUND | The branch bound this port to an attach point whose discipline contradicts the port domain. | Yes |
Outside branch scopeOVERCLAIMED | The branch materialized this port although its domain is outside what the branch can represent. | Yes |
Fallback attach pointDEGRADED | The branch bound this port to a fallback attach point rather than a declared port of the symbol. | Yes |
Not boundUNREALIZED | The branch ran and holds the component, but never bound this port to anything. | Yes |
No artifact producedNO_ARTIFACT | This port routes to a branch that has produced no artifact at all. | Yes |
Not routed hereUNDECLARED_ROUTE | The branch holds this port and can carry it, but the routing registry never sent it there. | Yes |
Identity not carriedUNVERIFIABLE | The branch materialized objects but carries no port identity, so binding cannot be checked either way. | Yes |
No recognised domainUNROUTED | This port carries no recognisable domain, so no branch is expected to hold it. | Yes |
Emitted more than onceDUPLICATED | The branch holds this port more than once, every copy carrying the same identity. | Yes |
Unwired in TeamcenterUNWIRED_IN_SOURCE | Teamcenter itself wires this port to nothing, so no branch can bind it. | No |
WaivedWAIVED | A blocking state that a named person has accepted on the record. | No |
RealizedREALIZED | Bound to a declared attach point that can carry this port domain. | No |
Only one state is recorded per port/branch cell. Where several apply, the worst is reported, so a port that is both bound to the wrong discipline and using a fallback attach point is reported as Wrong discipline.
Corrective actions
Each state is addressed to somebody specific. A finding routed to the wrong owner is the main reason these sit unresolved.
| State | What to do, and who does it |
|---|---|
| Wrong discipline | Branch owner: rebind to an attach point of the correct discipline. This is a wiring defect, not a gap. |
| Outside branch scope | Branch owner: stop emitting it, or correct the domain routing so it is not sent here. |
| Fallback attach point | Symbol owner: declare a real port on the symbol, then rebind. |
| Not bound | Branch owner: bind the port. The component arrived, so the gap is in the port pass. |
| No artifact produced | Run that branch, or scope the audit with --branches to the branches this pipeline actually builds. An unscoped run reports every branch you did not build. |
| Not routed here | Registry owner: add the domain to the routing registry. |
| Identity not carried | Branch owner: carry the port identity through export. Waivable per branch by a named owner while that work is outstanding. |
| No recognised domain | Model owner: type the port, or add its domain to the registry. |
| Emitted more than once | Branch owner: de-duplicate the export. One port, one pin. |
| Unwired in Teamcenter | Model owner, not the branch. This does not block a handoff, because no branch can clear it. |
| Waived | Review at the waiver expiry. The debt is visible, not cleared. |
Two findings are commonly misread as model defects and are not. No artifact produced
usually means the audit was run unscoped against branches this pipeline never builds; scope it with
--branches. Unwired in Teamcenter is addressed to whoever owns the
model and deliberately does not block, because no branch owner can clear it.