Created
April 30, 2026 10:32
-
-
Save KristofferTolboll2/4d67f61a7cd88a5d95277fcdb71ca800 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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