# Capital ⇄ Teamcenter Exchange Format (v1.2)

> **Demo-package note.** This is the full, canonical contract. Sections below
> that reference the Teamcenter-side build — `tc-service/`, `sample-data/`,
> `docs/vendor/`, `capital-xml-roundtrip.md`, `capitalapi.jar` — describe files
> that are **not included in this demo package** (by design; see the top-level
> `README.md`). The annotated config template ships here at
> **`docs/tc-connection.sample.properties`**. The bundled `bundle.json` is a
> valid **v1.1** bundle against this v1.2 contract.

> **v1.1 (Platform Schematic)** adds, additively to v1.0: `components[].domain`,
> `components[].ports`, `components[].parameters`, `networks[].parameters`, a
> top-level `connections[]` array, and the `ParameterDef` maturity concept.
> v1.0 bundles remain valid. This schema is now **bidirectional**: the same
> format flows TC → Capital (import/verify) and Capital → TC (Logical BOM
> publish via the "Publish to Teamcenter…" action, delivered as an outbox JSON
> file and/or POST to `publish.postPath`, default `/import/bundle`).

This is the **neutral JSON contract** between the Teamcenter extraction side and the
Capital Systems Architect import plugin. Either side can be developed and tested
independently against this document.

- The Teamcenter side (REST service or export script) **produces** a JSON document
  matching this schema — either served over REST or written to a `.json` file.
- The Capital plugin **consumes** it via `TeamcenterRestAdapter` (live) or
  `FileSourceAdapter` (file on disk, for testing without Teamcenter).

## Top-level structure

```json
{
  "meta": {
    "schemaVersion": "1.1",
    "source": "teamcenter",
    "generatedAt": "2026-07-03T14:00:00Z",
    "project": "Saber 2.0",
    "comment": "optional free text"
  },
  "components": [],
  "functions": [],
  "allocations": [],
  "networks": [],
  "signals": [],
  "messages": [],
  "connections": [],
  "requirementLinks": []
}
```

All eight data arrays are optional — a bundle may carry only components, only
requirements links, etc. `meta.schemaVersion` is required; the importer rejects
unknown major versions.

Every object supports a free-form `"attributes": { "name": "value" }` map for
extra properties that don't have a first-class field. The Capital side maps
these onto Capital object properties where configured.

## components

Device / ECU / part library data (typically from Teamcenter item + revision).

```json
{
  "id": "TC-000123",              // required, stable unique id (TC item id)
  "name": "Body Control Module",  // required
  "type": "ECU",                  // ECU | DEVICE | CONNECTOR | PART | OTHER
  "partNumber": "BCM-4711-A",
  "revision": "B",
  "description": "…",
  "attributes": { "supplier": "Bosch" }
}
```

## functions

Functional model elements (logical functions to be allocated to components).

```json
{
  "id": "FN-0042",                // required, unique
  "name": "Exterior Lighting Control",
  "description": "…",
  "parentId": "FN-0001",          // optional — functional hierarchy
  "attributes": {}
}
```

## allocations

Function → component allocation (the core of Systems Architect's job).

```json
{
  "functionId": "FN-0042",        // must reference functions[].id
  "componentId": "TC-000123",     // must reference components[].id
  "attributes": {}
}
```

## networks

Communication networks / buses.

```json
{
  "id": "NET-CAN-1",
  "name": "Body CAN",
  "protocol": "CAN",              // CAN | CANFD | LIN | FLEXRAY | ETHERNET | OTHER
  "baudRate": 500000,             // optional, bits/sec
  "attributes": {}
}
```

## signals

Signals exchanged between functions, optionally carried on a network.

```json
{
  "id": "SIG-0815",
  "name": "LowBeamRequest",
  "sourceFunctionId": "FN-0042",      // optional but recommended
  "targetFunctionIds": ["FN-0077"],   // zero or more receivers
  "networkId": "NET-CAN-1",           // optional, must reference networks[].id
  "attributes": { "length": "1", "cycleTimeMs": "100" }
}
```

## messages

Network messages/frames that group signals (Capital Systems Architect treats
messages as first-class objects assignable to carriers, separately from signals).

```json
{
  "id": "MSG-0100",
  "name": "BodyStatus1",
  "signalIds": ["SIG-0815"],          // signals packed in this message
  "networkId": "NET-CAN-1",           // optional, must reference networks[].id
  "attributes": { "dlc": "8", "canId": "0x3A0" }
}
```

## v1.1 additions — the Platform Schematic layer

### Ports (on components)

```json
"ports": [
  { "id": "P-FBP-IN", "name": "FUEL_IN", "direction": "IN", "type": "fluid", "attributes": {} }
]
```
`direction`: IN | OUT | INOUT. `type`: fluid, power, signal, mechanical, data, …
Ports make cross-domain interfaces explicit — a fuel pump exposes fluid ports
*and* a power port, which is what lets the electrical team see the fuel team's
demand (the deck's accountability story).

### Domains (on components and connections)

`domain`: ELECTRICAL | FUEL | HYDRAULIC | PNEUMATIC | MECHANICAL | ELECTRONICS | OTHER.
On the Capital side this is carried as the `TC_DOMAIN` property.

### connections

Component-to-component (optionally port-to-port) edges — the multi-domain
schematic lines. Distinct from `signals[]`, which remain the E/E payload.

```json
{
  "id": "CONN-FUEL-01",
  "name": "Reservoir to boost pump feed",
  "domain": "FUEL",
  "fromComponentId": "TC-000301", "fromPortId": "P-RSV-OUT",
  "toComponentId": "TC-000300",   "toPortId": "P-FBP-IN",
  "signalIds": [],                 // set when derived from E/E conductors
  "networkId": null,               // carrier implementing it, if any
  "parameters": [], "attributes": {}
}
```
When Capital publishes, E/E connections are *derived* from interface mappings
(components sharing a signal/message). When Teamcenter feeds Capital, fluid/
hydraulic connections are verified for endpoint existence only (Capital's E/E
model has no fluid-line object — the TC Logical Model is the master for those).

### parameters (maturing KPIs)

Attachable to components, networks, and connections:

```json
{ "name": "weight", "unit": "kg", "values": { "budget": "3.5", "simulated": "3.2", "tested": null-omitted } }
```
`values` is keyed by maturity state — suggested states, in precedence order:
`target`, `budget`, `simulated`, `product`, `tested`. Roll-ups and precedence
logic live on the Teamcenter side (Advanced BOM roll-up); Capital carries the
values as `TC_PARAM_<name>` properties.

## v1.2 additions — parented structure roll-up (MIL-STD-1808 spine)

All additive and optional; a v1.1 bundle behaves exactly as before (components
attach flat under the LBOM root).

### components[].parentId  (string, optional)

The id of the structure element this component nests under. On push,
`push_bundle` resolves the parent — first among items created in the same push,
then **live in Teamcenter** (`getItemFromId`, untagged then run-tagged) — and
attaches the component as a child occurrence of that parent instead of the LBOM
root. Unresolvable parents are reported in `summary.errors` and fall back to the
root (visible failure, never silent wrong-nesting). In Capital the value rides as
the `TC_PARENT_ID` property on the component (set by the engineer via the
Quick-Access-Panel dropdown or the *Assign Structure Parent…* action).

### components[].provenance  (string, optional)

Who authored the element, in which tool. Working values (shorthand set):
`sysml` | `sysml-add` | `elec` | `elec-add` | `fuel-add`. Drives the
color-coding in Systemscenter and the OTI derivation below. Rides in Capital as
`TC_PROVENANCE` (the bare name `provenance` collides with native Capital
attributes — see Format rules below).

### components[].effectivity  (string, optional)

Unit effectivity expression, e.g. `unit>=151`. Belongs to the *change wave*, not
the part: per-component values fall back to a design-level `TC_EFFECTIVITY`
property (set once), then to `meta.effectivity` (bundle-wide default). Rides in
Capital as `TC_EFFECTIVITY`. **Demo semantics: a stamped property, not real
Teamcenter occurrence/unit effectivity.**

### components[].attributes.milStd1808  (string, optional)

The MIL-STD-1808 chapter address (`"28 10"`); rendered as the address badge in
Systemscenter and the pickers.

### meta.effectivity  (string, optional)

Bundle-wide effectivity default inherited by new components at import.

### Hub enrichment semantics (store.import_bundle)

1. **Merge-preserve** — updating an existing component NEVER erases enrichment
   the incoming tool didn't send: empty/absent `parentId` / `provenance` /
   `effectivity` keep their prior values, and prior `attributes` keys are kept
   unless explicitly overwritten. A naive re-publish cannot strip the structure.
2. **Auto-provenance** — new components without provenance get it derived from
   `meta.source` (`nx*` → `fuel-add`, `cameo|sysml` → `sysml-add`,
   `capital*` → `elec-add`).
3. **Effectivity default** — new components inherit `meta.effectivity` when they
   carry none.

Net: a publishing tool only ever needs to supply `parentId` for *new* elements;
everything else is derived or preserved.

### Capital project XML — v1.2 mapping additions

| Bundle | Capital project XML |
|---|---|
| `components[].parentId` | `<property name="TC_PARENT_ID">` on the cluster |
| `components[].provenance` | `<property name="TC_PROVENANCE">` (never bare `provenance`) |
| `components[].effectivity` | `<property name="TC_EFFECTIVITY">` (never bare `effectivity`) |
| valid parents (derived) | `objecttypeinfomgr` → `CLUSTER` → `TC_PARENT_ID` propertytemplate with `range` values (the Quick-Access-Panel dropdown) |

**Format rules (hard requirements, learned against Capital 2512):**

- A cluster `<property>` whose name collides with a native Capital attribute
  (`effectivity`, `provenance`) makes the importer **silently drop the entire
  cluster** — no error, empty design. Always use the `TC_`-prefixed names.
- The OTI section must mirror a PM export byte-shape or it is silently dropped:
  object type name is the internal token **`CLUSTER`** (not "Component");
  boolean attrs are `"true"/"false"` (not `1/0`); include
  `autoassign/displayinqep/propertytag/defaultvalue` explicitly.
- OTI is applied on **bootstrap imports**. A *Copy As Revision* merge does NOT
  refresh project-level OTI (verified); *Overwrite* is untested. See
  `capital-structure-authoring-operations.md` for the refresh process.

## Multi-domain participation (`component.domains[]`)

A multi-domain device — a pump that is hydraulic, electrically powered, and
runs firmware — is **ONE element with ONE identity**, never duplicated per
domain. Duplication would fork effectivity, structure placement, and
publish-back identity. Instead:

- `domain` (scalar, unchanged) = the **primary/owning** discipline — whose
  publish masters the element.
- `domains[]` = the **participation set**, primary first. **Hub-derived on
  every import** (`store._derive_domains`, recomputed over the merged head):
  primary ∪ declared `attributes.domains` (string or list; `,;+/` separators)
  ∪ evidence — connection kinds touching the element
  (power/electrical→ELECTRICAL, signal/data→ELECTRONICS, fluid/fuel→FUEL,
  hydraulic→HYDRAULIC, logic-*→ELECTRICAL) and SOFTWARE when a
  Software-typed function is allocated to it.

Views filter/badge by the set: the Capital Teamcenter Browser shows a
**Domain column** (`FUEL +SW +ELEC`); Systemscenter shows chips on
multi-domain nodes and a domain filter — the pump appears in the FUEL,
ELECTRICAL, and SOFTWARE filtered views, as the same single element.

## Logic detail layer (Capital Logic devices / connectors / nets)

Components of `type` `DEVICE` / `CONNECTOR` and connections whose
`attributes.tcKind` starts with `logic` form the **logic detail layer** —
published by "Publish Logic to Teamcenter…" (`IXLogicAction`, available in
Capital Logic Designer / Platform Designer / SA when a logic design is open).

**Stable ids (2026-07-05).** Regenerating a logical design mints all-new
design-local Capital UIDs, so the publisher derives stable bundle ids:
stamped `TC_ID`/`Element ID` win; then a DEVICE keys on its source platform
cluster (`Component_Base_ID` + `-DEV`); a CONNECTOR on its owning device +
name (`LC-<ownerKey>-<name>`); nets/wires on their name (`LNET-`/`LWIRE-` +
name, uniquified in document order). The hub's store guard additionally
dedups DEVICE/CONNECTOR rows by `(Component_Base_ID)` / `(name)` and
logic connections/signals by `(tcKind, name)`, migrating head references
when an id changes.

**Teamcenter mapping.** Detail items are `componentType` items — the Saber
BMIDE structure rule rejects every other type as a child of a System Block
(error 46147, probe-verified; even `Sys0LogicalComp` fails). Distinction
lives on the revision's native **`seg0Kind`** attribute (`LogicDevice` /
`LogicConnector`), stamped at CREATION via `createObjects`
`compoundCreateInput` — **property updates are a silent no-op on the 2506
JSON gateway** (every setProperties variant; probe-verified), and
`seg0Kind` is multi-valued (`stringArrayProps`, not `stringProps`).
Devices nest as occurrences under the platform component they realize
(name-matched, `<parent>-DEV` item ids); connectors sit under the LBOM root
(`<PROJ>-LC-<name>`); conductors are `Fnd0LogicConn` items trace-linked to
their endpoint items. Ids minted under an older runTag (e.g. `T7…`) are
adopted via `teamcenter.legacyRunTags` (production mode only).

**Round trip.** `teamcenter_export.py` extracts nested devices (+`Fnd0LogicIntrfce`
pins), root connectors (`-LC-`/`seg0Kind`), and conductors with device +
component endpoints. `/export/capital-xml?project=X&logic=1` (or
`--include-logic`) materializes them as a real `<logicaldesign>` (devices,
owner-mated connectors, nets — shape mirrors Capital's own
`SampleLogicDesign.xml`; DTD-validated). Generated devices carry
`Element ID` = the TC detail item id and `Component_Base_ID` = the source
component, so a publish-back from Capital resolves the exact TC items.

## requirementLinks

Traceability links from requirements (Teamcenter requirements module, Polarion
via TC, etc.) to design elements.

```json
{
  "requirementId": "REQ-1234",
  "requirementName": "The system shall …",
  "targetType": "FUNCTION",       // FUNCTION | COMPONENT | SIGNAL
  "targetId": "FN-0042",          // id in the corresponding array (or already in Capital)
  "linkType": "satisfies",        // satisfies | verifies | derives | refines | trace
  "attributes": { "tcUid": "wIYt5Fl0JlIA5D" }
}
```

**TC push mapping:** each distinct `requirementId` becomes a `Requirement`
item (config `teamcenter.requirementType`), related to its targets with
`IMAN_requirement` (config `teamcenter.requirementRelation`) — primary = the
satisfying element revision, secondary = the requirement revision (item-level
fallback if the GRM rule rejects revisions). Requirements are **not** added
to the LBOM structure; they connect via relations only. Note the TC
`Requirement` type enforces the id naming rule `"REQ-"nnnnnn` (six digits) —
`REQ-123` is rejected, `REQ-000123` passes. `attributes.text` becomes the
item description; `attributes.provenance` / `attributes.effectivity` drive
the Systemscenter badges.

## Authoring ownership (authoritative source of truth)

Which tool masters which element — every integration decision (import vs
verify, overwrite vs revision, publish direction) follows from this table.
"SysML" = the MBSE model in Cameo / CATIA Magic; its data reaches Capital via
the Cameo bridge project XML or this bundle contract.

| Element | Source of truth | Flows via | Notes |
|---|---|---|---|
| Functions (Software/Hardware) + hierarchy | **SysML** | `functions[]` / project-XML `function` | SysML `Element ID` GUID (or TC id) is the join key |
| Function ports (pins) | **SysML** | project-XML `funcpin` | Derived from SysML flows / proxy ports |
| Functional signals / nets | **SysML** | `signals[]` / project-XML `funccond` | Incl. performance attrs (bitsize, frequency, MaxAge, MaxLatency) |
| External interface boundary | **SysML** | project-XML `sharedconductor` | Design inputs/outputs |
| Requirements + trace links | **Teamcenter** (or Polarion via TC) | `requirementLinks[]` | Neither authoring tool masters these; both consume |
| LRUs / components / ECUs | **Capital** (platform design) | published back as `components[]` | Never present in SysML-side exports |
| Block/platform connectivity | **Capital** | `connections[]` | Component-to-component edges |
| Function → LRU allocations | **Capital SA** | published as `allocations[]` | The core SA decision; Teamcenter records it |
| Networks/carriers + signal-to-carrier assignment | **Capital SA** | `networks[]`, `signals[].networkId` | Protocol, baud, bus load |
| Logic devices / connectors (detail) | **Capital Logic** (generated from the platform design via "Generate Logical Designs") | published as `components[]` with `type` = `DEVICE` / `CONNECTOR` | Roll up **under** their platform component (matched by name) as child occurrences — so functional + platform + logic share ONE Teamcenter LBOM. Device pins → ports; wires/nets → `connections[]` / `signals[]`. |
| Harness / wiring geometry, topology | **Capital** (Harness tools) | stays in Capital | Physical implementation; TC gets released artifacts |
| Diagrams / layout (all domains) | Owning tool, regenerable | never exchanged | Layout is presentation, not data — regenerate after sync |

Rule of thumb: **SysML owns the problem** (what the system does — functions
and their information flows), **Capital owns the solution** (what implements
it — LRUs, allocations, carriers, wiring), **Teamcenter owns the
configuration** (requirements, revisions, and the record of the mapping
between the two). Consequence for sync: the functional design inside Capital
is a hosted read-only copy of SysML data (see the incremental-changes rules
in `capital-xml-roundtrip.md`), while the platform/LRU side is Capital's to
edit and publish back.

## Mapping to Capital Systems Architect concepts

Verified against the Capital 2512 API (`capitalapi.jar` javadoc +
CustomActionDevelopment.pdf, in `docs/vendor/`). The custom-action API can
UPDATE attributes/properties on carriers and ECUs, staged and applied atomically
when the action returns true; it cannot CREATE design objects or make
allocations/assignments. The implemented behaviour:

| Exchange entity | SA concept | Implemented behaviour |
|---|---|---|
| `components` | `IXComponent` (ECUs/devices) | **Sync**: matched by `TC_ID` property then Name; part number/revision/description attributes + `TC_*` properties updated. Missing → reported. |
| `networks` | `IXCarrier` | **Sync**: same matching; protocol/baud stored as `TC_PROTOCOL`/`TC_BAUD_RATE` properties. Missing → reported. |
| `functions` | `IXFunction` via allocation to components (source: associated functional designs) | **Verify**: exists & allocated somewhere → verified; else warning. |
| `signals`, `messages` | `IXFunctionSignal` / `IXFunctionMessage`; carrier assignment via `IXCarrier.getAssignedSignals()/getAssignedMessages()` | **Verify**: present in design and assigned to the carrier named by `networkId`; gaps reported. |
| `allocations` | `IXComponent.getFunctions()` | **Verify**: function allocated to that component; gaps reported. |
| `requirementLinks` | *(no first-class SA concept)* | **Sync**: `REQ_<requirementId>` property staged on writable targets (carriers/ECUs). |

Gap-closing pairs with SA's rule-based automation: *Allocate-by-Attribute/
Property* and *Assign-Signal/Message-to-Carrier* constraints can key off the
synced `TC_*` properties, so Teamcenter data drives allocation
correct-by-construction even though the plugin API can't allocate directly.

Practical consequence for the Teamcenter side: functions/signals/messages in the
bundle act as *references* into the functional designs already associated with the
platform design, so their `name` (or the `TC_ID` stamped on a previous run) must
match what the design uses. Component and carrier names must match the objects
authored in Systems Architect.

## Referential integrity rules

The importer validates before touching Capital:

1. `allocations[].functionId` must exist in `functions[]` **or** already exist in
   the open Capital design (resolved by id/name — see mapping config).
2. Same rule for every other cross-reference: `allocations[].componentId`,
   `functions[].parentId`, `signals[].sourceFunctionId` / `targetFunctionIds` /
   `networkId`, `messages[].signalIds` / `networkId`,
   `connections[].fromComponentId` / `toComponentId` / `networkId` / `signalIds`,
   and `requirementLinks[].targetId`. Optional fields are only validated when
   present, but a present-and-unresolvable reference is an error.
3. `id` values must be unique within their array.
4. Duplicate allocations (same function+component pair) are collapsed with a warning.

Validation failures produce a report; nothing is written to Capital on a failed
validation (all-or-nothing per bundle).

## REST endpoints expected by TeamcenterRestAdapter

The adapter is endpoint-configurable (see `docs/tc-connection.sample.properties`),
but the default contract it expects from the Teamcenter-side service is:

| Purpose          | Method | Default path                          | Returns                       |
|------------------|--------|---------------------------------------|-------------------------------|
| Health / ping    | GET    | `/health`                             | 200 OK (any body)             |
| Full bundle      | GET    | `/export/bundle?project={name}`       | full exchange JSON            |
| Capital XML      | GET    | `/export/capital-xml?project={name}`  | bundle rendered as Capital project XML (see below) |
| Per-type (opt.)  | GET    | `/export/{components\|functions\|allocations\|networks\|signals\|messages\|requirementLinks}?project={name}` | partial bundle with just that array |

Auth: `Authorization: Bearer <token>` or HTTP Basic, per config. If the
Teamcenter side ends up shaped differently (e.g. raw TC SOA/AWC gateway calls),
only `TeamcenterRestAdapter` changes — the rest of the plugin is untouched.

## Capital project XML export (direct File → Import path)

Besides the JSON contract consumed by the plugin, the Teamcenter side can render
a bundle as **Capital project XML** (`project.dtd`, the same format the Cameo /
CATIA Magic bridge writes) — loadable by any Capital seat via **Capital Project
Manager → File → Import…**, no plugin required. Producer:
`tc-service/capital_project_xml.py` (used by `GET /export/capital-xml`, also a
CLI: `py capital_project_xml.py <bundle.json> [<out.xml>] [--dtd <path>]`).
Reference output: `sample-data/capital-project.xml` (generated from
`sample-data/import-bundle.json`, validated against the Capital 2512 DTD).

Scope: the **functional layer** — this route carries `functions[]` and
`signals[]` into a `<functiondesign>`. `components`, `allocations`,
`connections`, `messages` and `requirementLinks` are not expressible in a
functiondesign; they stay on the plugin sync/verify path.

### Mapping

| Bundle | Capital XML |
|---|---|
| `meta.project` | `project@name` and `functiondesign@name` |
| `meta.lastRevision` | `functiondesign@version` (1→A, 2→B, … 27→AA) — with "Copy As Revision" checked on import, each TC revision becomes a Capital design revision |
| `functions[]` | `<function functiontype="Software">` (override via `attributes.functionType` = `Software\|Hardware`; `attributes.powerConsumption` → `@powerconsumption`) |
| `functions[].id` | `<property name="Element ID">` — the TC id, same slot the Cameo bridge uses for its GUIDs |
| `functions[].parentId` / `.description` / other attributes | `TC_PARENT_ID` / `TC_DESCRIPTION` / verbatim `<property>` entries |
| `signals[]` with source **and** targets | internal net: `<Name>-OUT` funcpin on the source, `<Name>-IN` on each target, one `<funccond>` connecting them (`Element ID` = signal id) |
| `signals[]` with targets only / source only | design **input** / **output**: project-level `<sharedconductor>` (direction in/out, `Element ID` = signal id) + a `<funccond sharedconductor="…">` |
| `signals[].networkId` | `TC_NETWORK` property on the funccond (network name) |
| `signals[].attributes.bitsize/frequency` | `funccond` XML **attributes** (as Cameo emits); other attributes → properties |

### Format rules (hard requirements, learned against Capital 2512)

1. **UTF-8 without BOM** — Capital rejects any file not starting literally
   with `<?xml` ("Unidentified characters at the beginning").
2. **DOCTYPE SYSTEM id must resolve** on the importing machine. The service
   stamps `capitalDtdPath` from `tc-service/config.json` (point it at
   `<CapitalHome>/dtd/project.dtd`); the CLI takes `--dtd`. A newer DTD reads
   older-version documents.
3. `id` attributes are file-local XML Names (DTD `ID` type — must not start
   with a digit). **`baseid` is the merge identity**: the generator derives it
   deterministically from TC ids (`TCX` + sha1), so re-importing an updated
   export updates in place instead of duplicating.
4. Every `connection@pinref` must resolve to a `funcpin@id`, every
   `funccond@sharedconductor` to a `sharedconductor@id`. The generator skips
   dangling function references and reports them as warnings (logged by the
   service, stderr from the CLI).
5. `functiondesign@folderpath` + the `designmgr/folderpath` row place the
   design in Project Manager. Capital folder names are **flat display
   strings** — `/` is not a path separator (verified: a slash lands verbatim
   in the folder name) — so the generator uses a single flat
   `01-Functional_Designs` folder.
6. **Identity & update-in-place**: Capital re-keys all baseids to its own
   `UID…` values when it bootstraps a project from this XML; only the
   `Element ID` properties survive. Imports MERGE into an existing project
   (the "Import Designs to Project" dialog: Overwrite = update-in-place,
   Copy As Revision = new design revision) only when `project@baseid`
   matches — harvest it once via Project Manager → File → Export → Select
   Project… and record it in `tc-service/config.json` `capitalBaseids`.
   Full operational guide: `docs/capital-xml-roundtrip.md`.
