Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NV9oijZukGyGbRQGGKnK4P Assistant: claude-code Assistant-Model: opus Assistant-Process: 713576@bnt-lap001 Assistant-Session: 384c511d-9bce-4cb8-a676-2aef6c0c8df6
118 lines
5.2 KiB
Markdown
118 lines
5.2 KiB
Markdown
---
|
|
id: KEY-WP-0017
|
|
type: workplan
|
|
title: "Reconcile the canonical model and discovery metadata with the runtime"
|
|
domain: infotech
|
|
repo: key-cape
|
|
status: finished
|
|
owner: claude
|
|
topic_slug: canonical-model-and-discovery-conformance
|
|
created: "2026-09-07"
|
|
updated: "2026-09-07"
|
|
state_hub_workstream_id: "0904caad-ad8b-573f-9cdb-b6da09262a18"
|
|
---
|
|
|
|
Closes gap G02 of `history/2026-09-05-011726-scope-intent-assessment.md`.
|
|
`spec/canonical-model.yaml` and `src/internal/domain/model.go` both claim to be
|
|
the source of truth for the client registration shape, and they disagree: the
|
|
spec restricts grants to `authorization_code`, requires redirect URIs for every
|
|
client, and omits the service-subject, tenant, audience, lifetime, role and
|
|
MFA/handoff fields the runtime actually reads. Discovery advertises a fixed basic
|
|
scope and claim list that omits core profile claims and every configured resource
|
|
scope.
|
|
|
|
The point of the work is the executable link, not the edit: reconciling the two
|
|
files once leaves them free to drift again the same afternoon.
|
|
|
|
## Reconcile the client entity with the runtime model
|
|
|
|
```task
|
|
id: KEY-WP-0017-T01
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "996f6895-f36b-585e-844d-3e55d02eefda"
|
|
```
|
|
|
|
Bring the `Client` entity in `spec/canonical-model.yaml` in line with
|
|
`domain.Client`: add `audience`, `serviceSubject`, `tenant`, `roles`,
|
|
`mfaRequired`, `registrationUrl` and `enrollmentUrl`; extend the `grantTypes`
|
|
enum to include `client_credentials`; and make `redirectUris` required only for
|
|
clients that use the authorization-code grant, matching what config validation
|
|
already enforces. Record which fields are deliberately absent from the canonical
|
|
model rather than silently omitted — `clientSecret` and `tokenLifetime` are
|
|
runtime policy, not identity data, and both are `yaml:"-"` in the Go model.
|
|
|
|
Added all seven fields, extended the grant enum, and made `redirectUris`
|
|
optional at the schema level with the authorization-code condition stated in its
|
|
description. `tokenProfile` and `environments` were declared in the spec but read
|
|
by nothing; they are kept and marked `runtime: false` rather than deleted, since
|
|
removing them would discard intent the check can now hold honest.
|
|
|
|
## Add an executable conformance check
|
|
|
|
```task
|
|
id: KEY-WP-0017-T02
|
|
status: done
|
|
priority: high
|
|
state_hub_task_id: "77df5043-2b23-56cd-947e-317f7117ac18"
|
|
```
|
|
|
|
Add a test that compares the canonical model's `Client` field set against
|
|
`domain.Client`'s YAML tags by reflection and fails on divergence in either
|
|
direction: a Go field with no spec entry, or a spec field the runtime does not
|
|
read. Fields intentionally excluded must be named in one list the test reads, so
|
|
excluding a field is a deliberate, reviewable act rather than an omission. This
|
|
is the generation/conformance link the assessment asks for.
|
|
|
|
`src/internal/domain/conformance_test.go` checks `Client`, `User`, `Group` and
|
|
`Membership` by reflection over YAML tags. It earned its place on the first run
|
|
by finding drift outside the assessment's list: `User.tenant` was read by the
|
|
runtime and undeclared in the spec. `yaml:"-"` fields are excluded by
|
|
construction, which is what keeps `clientSecret` and `tokenLifetime` out without
|
|
a hand-maintained exception list.
|
|
|
|
## Reconcile discovery metadata with the profile surface
|
|
|
|
```task
|
|
id: KEY-WP-0017-T03
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "5eb36f9b-a8e0-541e-8f81-310db49b77a0"
|
|
```
|
|
|
|
`claims_supported` omits `tenant`, `principal_type`, `assurance`, `scope` and
|
|
`nonce`, which the token endpoint emits on every token, and `scopes_supported` is
|
|
a fixed list that omits every configured resource scope. Advertise the core
|
|
profile claims and derive the scope list from the registered clients. Keep the
|
|
distinction the assessment asks for: an omitted core claim is a contract defect,
|
|
while optional discovery metadata is not, so document which is which rather than
|
|
treating every omission alike.
|
|
|
|
`claims_supported` now lists the core profile claims (`tenant`, `principal_type`,
|
|
`assurance`, `scope`, `nonce`) alongside the scope-gated human claims and the
|
|
optional cached `tenant_roles`. `scopes_supported` is derived from the registered
|
|
clients — baseline OIDC scopes first, configured resource scopes sorted after, so
|
|
the document is stable regardless of map iteration order — with the baseline
|
|
still advertised when no clients are configured.
|
|
`TestDiscoveryAdvertisesEveryCoreProfileClaim` states the distinction the
|
|
assessment asked for: a missing core claim fails, optional metadata does not.
|
|
|
|
## State a single source of truth
|
|
|
|
```task
|
|
id: KEY-WP-0017-T04
|
|
status: done
|
|
priority: medium
|
|
state_hub_task_id: "63290e2a-bae1-51c3-9cb9-d49994d804b6"
|
|
```
|
|
|
|
Two files currently claim to be authoritative. State in both that the Go model is
|
|
the runtime authority and the canonical model is the reviewed contract the
|
|
conformance check holds it to, so the next reader knows which one to edit first.
|
|
Update `SCOPE.md` and G02's status in the assessment to say what is now enforced
|
|
and what remains — this closes the client-registration and discovery drift, not
|
|
schema enforcement in general (G06 is a separate gap).
|
|
|
|
Both files now state the split: the Go model is the runtime authority, the YAML
|
|
the reviewed contract, and the conformance test the link. SCOPE.md and G02's
|
|
status say what is enforced and that G06 remains open.
|