Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save KristofferTolboll2/4d67f61a7cd88a5d95277fcdb71ca800 to your computer and use it in GitHub Desktop.

Select an option

Save KristofferTolboll2/4d67f61a7cd88a5d95277fcdb71ca800 to your computer and use it in GitHub Desktop.
Plan: First-Delivery Audit — Tenant, Token/TokenGroup, Location, Network
Context
The team is preparing the first delivery of the EMSP Platform Public API for the UTA pilot. The CDR Deep Dive and cdrs.yaml have already been
aligned through PR #13. This plan captures the remaining alignment work for the four other domains the user named (Tenant, Token+TokenGroup,
Location, Network) against:
- Their Notion Deep Dive pages (links in context/notion-pages-index.md)
- The four RFCs that have appeared since the original docs/yaml were authored. All have advanced status today (re-queried 2026-04-30):
| RFC | Status | Page ID |
|------------------------------------------------|-------------|----------------------------------|
| RFC: Country as First-Class Tariff Scope Axis | Done | 34b6099cb26e81f2a2afc703399118bb |
| RFC: Subscriptions Plan-Based Token Enrollment | In progress | 34b6099cb26e81b4aa9ad313f65bfc22 |
| RFC: Generalized Base Network Publishing | In progress | 34f6099cb26e8011aa19ce032a81f9a7 |
| RFC: Pass-through CPO Pricing | In progress | 34b6099cb26e81b7ad8dd071bb6b1767 |
Goal: align Notion ↔ YAML where they have drifted and land the contractual changes the three in-scope RFCs (Country, Subscriptions, Multi-Publisher)
require for the four pairs. Pass-through is out of our 4-pair scope (it operates at the tariff/CDR layer).
Scope
In scope (4 pairs):
┌────────────────────────────────────────────────────────────────────────────────────┬────────────────────────────────┬───────────────────┐
│ Notion Deep Dive │ YAML │ Local index entry │
├────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────┼───────────────────┤
│ Tenant Management Deep Dive (3486099cb26e81fbad98e065294ba041) │ context/openAPI/tenants.yaml │ tenants │
├────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────┼───────────────────┤
│ Token Management Deep Dive (3436099cb26e81dea83ecab057f446a4) — Token + TokenGroup │ context/openAPI/tokens.yaml │ tokens │
├────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────┼───────────────────┤
│ Locations: Deep Dive (3436099cb26e81fbb186f061735a25d3) │ context/openAPI/locations.yaml │ locations │
├────────────────────────────────────────────────────────────────────────────────────┼────────────────────────────────┼───────────────────┤
│ Networks: Deep Dive (3436099cb26e8147933bc3d5e9d2e8f9) │ context/openAPI/networks.yaml │ networks │
└────────────────────────────────────────────────────────────────────────────────────┴────────────────────────────────┴───────────────────┘
RFCs landed/aligned in this delivery:
- Country RFC (Done) — locations doc anchor.
- Subscriptions RFC (In progress) — Tenant scope/event surface + Token Group membership-management semantics.
- Multi-Publisher RFC (In progress) — Tenant capability flag/priority list + multi-identity + Token Group identity decoupling + Network publish
lifecycle.
Out of scope:
- Net-new spec files (subscriptions.yaml, bilateral-governance.yaml) — separate delivery.
- Pass-through CPO Pricing RFC implementation (tariffs.yaml/cdrs.yaml layer).
- CDR/dispute work (PR #13 + follow-ups already cover it).
- Sessions, Roaming, Tariffs, Bilateral Agreements deep dives.
---
Findings & action items, per pair
Severity: P0 = blocks the contract. P1 = real gap UTA will hit. P2 = cleanup / consistency.
1. Tenant Management
Critical files
- context/openAPI/tenants.yaml
- context/openAPI/_components.yaml (shared WebhookPayload.event_type enum)
P0 — already-landed drift, fix first:
- Path rename incomplete: tenants.yaml line 483 still reads /tenant/api-keys/{key_id}:rotate and uses key_id path param. Other paths already moved
to /tenant/credentials/{credential_id}. Fix: rename to /tenant/credentials/{credential_id}:rotate, rename path param, update operationId.
- WebhookRegistration events field is items: type: string (free-form). Deep Dive Section 2.4 says it's a controlled enum, 422-rejecting unknowns.
Fix: make events[] reference the canonical event-type enum from _components.yaml#/components/schemas/WebhookPayload/properties/event_type (or a
sibling shared enum). This unblocks the RFC-driven event additions below.
P0 — Multi-Publisher RFC landings (now in progress):
- Add Tenant.can_publish_base_network (boolean, admin-set, default false). Capability flag gating publish. Update yaml Tenant schema and Deep Dive
Section 3.1 TenantProfile schema.
- Add Tenant.published_network_priority_order (UUID array, consumer's tiebreak list across published Base Networks).
- Add EmpIdentity.published (boolean), EmpIdentity.publish_state (enum UNPUBLISHED|PUBLISHED|UNPUBLISHING), unpublish_started_at (timestamp),
unpublish_drain_until (timestamp). Update yaml EmpIdentity schema and Deep Dive Section 3.2.
- Add new endpoints:
- GET /tenant/published-network-priority-order
- PUT /tenant/published-network-priority-order
- PATCH /admin/tenants/{tenant_id} (admin-only, sets can_publish_base_network)
P0 — new scopes (Subscriptions + Multi-Publisher):
Extend securitySchemes.OAuth2ClientCredentials.flows.clientCredentials.scopes in tenants.yaml (lines ~1284–1310) and the Deep Dive Section 3.6 scope
catalog with:
- From Subscriptions RFC: subscriptions:read, subscriptions:write, cdrs.subscriptions:read.
- From Multi-Publisher RFC: networks.publish:write, bilateral-governance.requests:read, bilateral-governance.requests:write,
bilateral-governance.requests:approve, tenant.published-network-priority:read, tenant.published-network-priority:write.
P0 — webhook event-type enum extension:
After unifying the events array onto the shared enum (above), append the new event types in
_components.yaml#/components/schemas/WebhookPayload/properties/event_type and mirror in the Deep Dive event catalog:
- Subscriptions: subscription.created, subscription.activated, subscription.suspended, subscription.grace_started, subscription.reactivated,
subscription.cancelled, subscription.expired, subscription.allotment.exhausted, subscription.fee.created, subscription.fee.rated,
subscription.fee.finalized, subscription.fee.settled, subscription.fee.dispute_opened, subscription.fee.dispute_resolved,
subscription.plan.deprecated.
- Multi-Publisher: network.published, network.unpublishing.started, network.unpublishing.completed, network.consumer.attached,
network.consumer.detached, bilateral_request.created, bilateral_request.approved, bilateral_request.rejected, bilateral_request.expired,
bilateral_request.withdrawn.
(Schemas for these payloads live in the future subscriptions.yaml / bilateral-governance.yaml; for first delivery, the enum entries are sufficient
on the Tenant side. Keep payload oneOf references commented out or behind TODO until the spec files land.)
P1 — fix during the same pass:
- Multi-identity narrative: Deep Dive sometimes implies "1 identity per tenant per type" while EmpIdentity is already an array. With Multi-Publisher
in progress, tighten the Deep Dive to explicitly state: a single tenant may hold any number of DEDICATED private identities AND any number of
Published identities simultaneously.
- WebhookRegistration response should add 422 for scope mismatch on subscribe (Deep Dive describes; yaml lacks).
2. Token + TokenGroup
Critical files
- context/openAPI/tokens.yaml
- context/openAPI/_components.yaml
P0 — already-landed drift:
- TokenGroup.emp_identity_id cardinality. Earlier commit b1b25a5 made it required (1..N), but tokens.yaml lines 1674–1683 still type it [string,
'null']. Fix: drop the 'null' variant; keep in required. (Confirms with Multi-Publisher RFC's expectation.)
P0 — Subscriptions RFC landings:
- Add managed_by_subscription_id (uuid, nullable) to the membership join schema and surface it on:
- GET /token-groups/{group_id} (response includes which memberships are subscription-managed).
- POST /token-groups/{group_id}/tokens response.
- TokenGroupMembershipChangedEvent payload.
- Add 422 MEMBERSHIP_MANAGED_BY_SUBSCRIPTION response on DELETE /token-groups/{group_id}/tokens/{token_id}. Body: error envelope with hint pointing
at the subscription's admin cancel endpoint (path TBD until subscriptions.yaml lands; for now: free-text hint).
- Document subscription-driven membership lifecycle in the Deep Dive: subscription CANCELLED/EXPIRED removes the membership; SUSPENDED preserves it
(cheap reactivation).
P0 — Multi-Publisher RFC landings:
- Token Group's tenant_id (ownership) and emp_identity_id (access context) are now two independent dimensions. Update validation rule on POST:
emp_identity_id must be in (own identities) ∪ (published identities). Update yaml description and Deep Dive narrative.
- Preserve IDENTITY_MISMATCH boundary on TENANT sub-network attachment (cross-identity rejected). Formalize the rule: "the network's emp_identity_id
must equal the token group's emp_identity_id" — in both yaml POST description and the Deep Dive.
- Document multi-identity Token concept: a token can be in groups under multiple identities. Note that the Token's own emp_identity_id field
reflects the primary group's identity (or the OCPI Default Network identity if ungrouped) — confirm this rule with Peter; it's the behaviour
tokens.yaml already implies.
P1 — drift:
- OCPI Token.group_id vs Spirii Token Group distinction: tokens.yaml already documents this clearly (lines 1442–1448). Verify the Deep Dive carries
the same callout and a side-by-side example. (Manual verify — Notion fetch failed for the Token agent during audit; hand-check needed before
applying Notion edits.)
- TokenGroupNetworkAssignment schema and token_group.network_assignments.changed event exist in yaml; add a "Network attachment" subsection to the
Deep Dive.
- TokenGroupMembershipChangedEvent triggers (when, by what, what about subscription-driven changes) need a Deep Dive paragraph.
- POST /token-groups/{group_id}/networks request body description: replace "compatible EMP identity" with the precise rule (yaml + Deep Dive).
3. Locations
Critical files
- context/openAPI/locations.yaml
P0 — Country RFC landing (Done status):
- Location.country_code (or its derivation from Address.country) must be explicitly documented as the authoritative source for tariff country
resolution. Country RFC is now Done, and country_code is already a tariff_assignment scope axis in tariffs.yaml (commit e054ec2). Add a paragraph to
the Locations Deep Dive Locations schema section + a description tweak on the field in locations.yaml so integrators know the resolver implication.
P0 — already-landed drift:
- Field name mismatch: locations.yaml Location schema (line 676) uses amenities; Deep Dive uses facilities. OCPI 2.2.1 canonical name is facilities.
Fix: rename in locations.yaml (amenities → facilities). Pre-pilot, no production integrations to break.
P1 — drift:
- time_zone is required by OCPI 2.2.1 and the Deep Dive marks it required, but locations.yaml Location schema's required array does not include it.
Fix: add time_zone to required.
- BusinessDetails (operator, suboperator, owner) added to yaml in commit 53f5e64 per OCPI 2.2.1, but the Deep Dive's Location schema table omits
them. Fix: update the Deep Dive table.
Multi-Publisher RFC impact (light):
- published_network_id may surface on /networks/effective-view outputs (a CPO can be reachable via multiple Published Base Networks). This is a
Networks-side concern (Section 4 below), not a Location schema change.
4. Networks
Critical files
- context/openAPI/networks.yaml
P0 — Multi-Publisher RFC landings (heavy):
The "in progress" status pulls these into delivery. Plan them as a coordinated yaml + Deep Dive pass:
- network_type enum collapse: {DEFAULT, DEDICATED, TENANT} → {BASE, TENANT}. Migration: rows with DEFAULT → BASE + published=true; rows with
DEDICATED → BASE + published=false. TENANT unchanged. Keep legacy values readable (deprecation note) for one release window.
- Add Network.published (boolean), Network.publish_state (enum), Network.connection_precedence (integer; 0 if !published, 100 if published),
Network.parent_network_id (already present for TENANT).
- Add explicit Network.tenant_id on TENANT sub-networks (consumer-side ownership), decoupled from the inherited parent emp_identity_id. This
unblocks consumer Tenant Z creating a TENANT sub-network parented on Tenant X's Published Base.
- New endpoints:
- PATCH /networks/{network_id} (gated on networks.publish:write, flips published).
- GET /networks/{network_id}/consumers (publisher-only).
- POST /networks/{network_id}/bilateral-requests
- GET /networks/{network_id}/bilateral-requests
- GET /networks/{network_id}/bilateral-requests/{request_id}
- POST /networks/{network_id}/bilateral-requests/{request_id}:approve
- POST /networks/{network_id}/bilateral-requests/{request_id}:reject
- POST /networks/{network_id}/bilateral-requests/{request_id}:withdraw
- The bilateral-request state machine (REQUESTED → APPROVED | REJECTED | EXPIRED | WITHDRAWN) and webhooks. Schemas may belong in a future
bilateral-governance.yaml; for first delivery, define them inline in networks.yaml or stub them with $ref placeholders.
- New non_match_reason value PUBLISHED_NETWORK_DRAINING (mostly resolver-side; mirror in any docs that enumerate non-match reasons).
P1 — already-landed drift:
- network_type, parent_network_id, identity-boundary on TENANT sub-network creation, INCLUDE_NETWORK/EXCLUDE_NETWORK deprecation,
publicly_accessible are all aligned today (per Agent 3 audit). Verify during the implementation pass.
- protocol enum on Network includes BILATERAL as a protocol value (distinct from BILATERAL pricing on agreements/tariffs). Confirm Deep Dive does
not conflate them.
---
Cross-cutting items (touch multiple pairs)
1. Webhook event-type enum in context/openAPI/_components.yaml#/components/schemas/WebhookPayload/properties/event_type is the single source of
truth for event names. With three RFCs in progress, batch the additions in one edit (Subscriptions + Multi-Publisher events). Tenant
WebhookRegistration.events[] field references this enum (P0 above) and inherits the additions.
2. Scope catalog (tenants.yaml lines ~1284–1310). Add the 9 new scopes (3 Subscriptions + 6 Multi-Publisher) in one edit.
3. Deep Dive ↔ YAML co-edit approach. For each pair, do yaml first (mechanical, easier to review), then Notion (narrative). Keep edits per pair in
their own commit so the PR description for each is small.
4. Token Deep Dive Notion content was not directly verified during the audit (a fetch attempt failed). Before applying any Notion-side edits to the
Token + TokenGroup pair, do a notion-fetch of 3436099cb26e81dea83ecab057f446a4 to confirm each finding against actual content.
5. Subscriptions/Multi-Publisher payload schemas (e.g., SubscriptionFee, bilateral-request body) ideally live in dedicated spec files. For first
delivery, two acceptable strategies:
- (a) Shadow refs: add the event-type strings to the enum without oneOf payload references; payload schemas land alongside subscriptions.yaml and
bilateral-governance.yaml in the next delivery.
- (b) Inline: define the minimum payloads inline in tenants.yaml / tokens.yaml / networks.yaml to keep the contract closed.
- Recommend (a) for speed; flag for Peter's call.
---
Suggested execution order
1. YAML changes first — one PR per pair, in this order to minimise rebase noise:
a. _components.yaml — event-type enum additions (must precede Tenant edits).
b. tenants.yaml — credential path rename + events enum reference + scope catalog additions + RFC 3 fields on Tenant/EmpIdentity + new
priority-order endpoints.
c. tokens.yaml — TokenGroup.emp_identity_id requiredness + managed_by_subscription_id field + 422 response on DELETE + identity-boundary
description.
d. locations.yaml — amenities → facilities rename + time_zone required + country_code description.
e. networks.yaml — network_type collapse + published/publish_state/connection_precedence/tenant_id fields + bilateral-request endpoints (inline or
stub).
2. Notion edits (per Deep Dive):
a. Tenant Management — multi-identity narrative + scope-catalog table refresh + new endpoints + 422 scope-mismatch response.
b. Token Management — tenant_id vs emp_identity_id framing + network-assignment subsection + OCPI vs Spirii Token Group example +
subscription-membership-lifecycle paragraph.
c. Locations — BusinessDetails rows + country_code authoritative paragraph + facilities-name confirmation.
d. Networks — network_type collapse migration note + publish lifecycle + bilateral-governance subsection.
3. Local audit tracker update (context/local-deep-dive-audit.md) — flip P0/P1 items to closed, leave Pass-through-related notes for the next
delivery.
4. Coordinate with Peter on the four open questions below before locking field names.
Verification
Per YAML change:
- Re-read the modified yaml to confirm schema correctness.
- Run a spec validator. context/openAPI/redocly.yaml exists; bundle/lint the spec post-edit (e.g., redocly bundle context/openAPI/redocly.yaml or
whatever the repo's standard command is — verify in package.json / README).
- grep -r "/tenant/api-keys" context/openAPI/ to ensure path-rename leaves no residue.
- For new event types, grep _components.yaml and confirm each new string appears once in the enum.
Per Notion change:
- Use mcp__notion__notion-fetch to refetch each Deep Dive page after edits. Grep the returned content for the expected new strings (e.g.,
"can_publish_base_network", "managed_by_subscription_id").
- Use mcp__notion__notion-get-comments if a discussion thread tracks the change.
End-to-end check:
- Tenant pair: WebhookRegistration.events[] resolves into the canonical event enum; the enum contains all 25-or-so new event strings; Deep Dive
event catalog matches.
- Token pair: POST /token-groups/{group_id}/networks request body description, response 422 wording, and TokenGroupNetworkAssignment schema all use
the same "compatible EMP identity" definition.
- Locations pair: confirm Location.country_code (or Address.country) is the same field referenced by tariffs.yaml's country-scope resolver (commit
e054ec2).
- Networks pair: network_type collapse migration note is present; old enum values still readable for one release window; bilateral-request state
machine is consistent across yaml + Deep Dive.
Open questions to resolve before edits
These need a quick sync with Peter; each becomes a P2 / deferred item if the answer is "wait":
1. Subscriptions/Multi-Publisher payload schemas — strategy (a) shadow refs or (b) inline? Recommend (a). Confirm.
2. Locations field rename amenities → facilities. Pre-pilot — confirm no driver-app or partner integration depends on the legacy name.
3. Token's emp_identity_id when the token is in groups under multiple identities. Confirm the rule: primary group's identity, or first-joined, or
OCPI Default fallback.
4. network_type collapse compatibility window. Keep DEFAULT/DEDICATED readable as legacy values for how long? One minor release? Two?
Workflow
- YAML changes: applied directly to context/openAPI/*.yaml, committed to a feature branch, opened as a PR. Source-controlled, easy to review on
GitHub.
- Notion edits: drafted and surfaced for user review before any notion-update-page / notion-create-comment call lands. Each Deep Dive change
presented as: (a) target section + anchor, (b) the new content, (c) the rationale. User approves or requests revisions; only then push to Notion.
References
- Local working index: context/notion-index.md.
- Committed reference: context/notion-pages-index.md.
- Audit tracker: context/local-deep-dive-audit.md.
- Prior CDR alignment work: PR #13 (ce54a11) and context/cdr-deep-dive-discrepancies.md.
- Country RFC counter-proposal draft: context/rfc-country-tier-comment.md (now superseded by Country RFC reaching Done; the tier-ordering question
may still be worth raising with Peter post-delivery).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment