1212** One-paragraph mental model.** The resources framework is a generic, event-sourced,
1313Kubernetes-inspired subsystem for * declarative* management of arbitrary "resource kinds". A user
1414authors a ** manifest** (` apiVersion ` + ` kind ` + ` headers ` + ` spec ` ); the framework stores it as an
15- event-sourced aggregate, fills in server-owned headers (uid , timestamps, ` generation ` ) and an
15+ event-sourced aggregate, fills in server-owned headers (id , timestamps, ` generation ` ) and an
1616** initial** server-owned ` status ` , then ** asynchronously reconciles** it toward the desired state
1717(the status progresses ` Pending → Reconciling → Ready ` /` Failed ` ). Every kind (today:
1818` VariableSet ` , ` SecretSet ` ) implements a small set of traits and registers a ** dispatcher** keyed by
@@ -119,7 +119,7 @@ long-term goal. This page documents what exists now.
119119
120120| Term | Meaning |
121121| --- | --- |
122- | ** Resource** | A single managed object instance of a given kind, identified by a ` ResourceUID ` . |
122+ | ** Resource** | A single managed object instance of a given kind, identified by a ` ResourceID ` . |
123123| ** Kind** | The resource type discriminator string, e.g. ` VariableSet ` , ` SecretSet ` . |
124124| ** ApiVersion** | Versioning string for the kind's schema, e.g. ` kamu.dev/v1alpha1 ` . |
125125| ** Descriptor** | The ` (kind, apiVersion) ` pair (` ResourceDescriptor ` ) used to route to the right dispatcher. |
@@ -143,7 +143,7 @@ long-term goal. This page documents what exists now.
143143The rules below hold across the framework. They are the contract a maintainer (or coding agent) must
144144not break; most are enforced in code and exercised by tests — pointers given where useful.
145145
146- - ** ` ResourceUID ` is immutable and server-allocated.** A new resource's UID comes from
146+ - ** ` ResourceID ` is immutable and server-allocated.** A new resource's UID comes from
147147 ` GenericResourceQueryService::allocate_uid() ` ; callers cannot choose it. Once assigned it never
148148 changes (it is the primary key — see [ §6] ( #6-persistence-model ) ).
149149- ** ` (account_id, kind, name) ` is unique.** Enforced by a DB unique constraint
@@ -249,7 +249,7 @@ pub trait DeclarativeResource:
249249 + TryFrom <ResourceSnapshot , Error = InternalError >
250250 + From <Self >;
251251
252- fn uid (& self ) -> & ResourceUID ;
252+ fn id (& self ) -> & ResourceID ;
253253 fn headers (& self ) -> & ResourceHeaders ;
254254 fn spec (& self ) -> & Self :: Spec ;
255255 fn status (& self ) -> & Self :: Status ;
@@ -269,7 +269,7 @@ pub trait ReconcilableResource: DeclarativeResource {
269269
270270 fn needs_reconciliation (& self ) -> bool { /* observed_generation vs generation */ }
271271
272- fn try_create (now , uid , headers : ResourceHeadersInput , spec ) -> Result <Self , LifecycleError >;
272+ fn try_create (now , id , headers : ResourceHeadersInput , spec ) -> Result <Self , LifecycleError >;
273273 fn try_update_headers (& mut self , now , new_headers : ResourceHeadersInput ) -> ... ;
274274 fn try_update_spec (& mut self , now , new_spec : Self :: Spec ) -> ... ;
275275 fn try_delete (& mut self , now , tombstone_name : String ) -> ... ;
@@ -301,7 +301,7 @@ current state.
301301### Repository
302302
303303` ResourceRepository ` (` repo/ ` ) is the persistence seam: allocate UID, create/update snapshot
304- (with optimistic ` expected_last_event_id ` ), find by name/uid , search identities, and stream UIDs /
304+ (with optimistic ` expected_last_event_id ` ), find by name/id , search identities, and stream UIDs /
305305snapshots by kind.
306306
307307### Dispatchers
@@ -338,7 +338,7 @@ pub struct ResourceManifest {
338338
339339#[serde(deny_unknown_fields)] // ← unknown fields (e.g. `status`) are rejected
340340pub struct ResourceManifestHeaders {
341- pub uid : Option <ResourceUID >, // optional — NOT assignable; an exact pointer to an
341+ pub id : Option <ResourceID >, // optional — NOT assignable; an exact pointer to an
342342 // existing resource for updates (e.g. when renaming)
343343 pub account : Option <ResourceManifestAccount >, // optional — by name OR id; defaults to caller
344344 pub name : ResourceName , // required
@@ -348,13 +348,13 @@ pub struct ResourceManifestHeaders {
348348}
349349```
350350
351- A user may write ** only** : ` apiVersion ` , ` kind ` , `headers.{uid ?, account?, name, description?,
351+ A user may write ** only** : ` apiVersion ` , ` kind ` , `headers.{id ?, account?, name, description?,
352352labels, annotations}` , and ` spec` . ` deny_unknown_fields` means a manifest **cannot** carry ` status`,
353353timestamps, or ` generation ` — those are server-owned.
354354
355- The ` uid ` is ** not** something the user assigns — a new resource's UID is always allocated by the
355+ The ` id ` is ** not** something the user assigns — a new resource's UID is always allocated by the
356356server. It may only be * supplied* on a manifest to point at an already-existing resource for an
357- update; this is what lets a resource be renamed (the ` uid ` keeps the identity stable while ` name `
357+ update; this is what lets a resource be renamed (the ` id ` keeps the identity stable while ` name `
358358changes). Omit it for normal create/update-by-name.
359359
360360** (2) Framework-generated — the rest of headers + all of status.**
@@ -381,18 +381,18 @@ pub struct ResourceStatus { // entirely server-owned
381381}
382382```
383383
384- Also generated: ` uid ` (allocated if the manifest omitted it) and ` last_reconciled_at ` .
384+ Also generated: ` id ` (allocated if the manifest omitted it) and ` last_reconciled_at ` .
385385
386386** (3) Persisted form — the snapshot.** ` ResourceSnapshot `
387387([ ` core/resource_snapshot.rs ` ] ( /src/domain/resources/domain/src/core/resource_snapshot.rs ) )
388388combines authored + generated + event-sourcing bookkeeping:
389389
390390``` rust
391391pub struct ResourceSnapshot {
392- pub uid : ResourceUID ,
392+ pub id : ResourceID ,
393393 pub kind : String ,
394394 pub api_version : String ,
395- pub headers : ResourceHeaders , // authored fields + generated fields
395+ pub headers : ResourceHeaders , // authored fields + generated fields
396396 pub spec : serde_json :: Value , // authored (may be transformed — see SecretSet)
397397 pub status : Option <serde_json :: Value >, // generated
398398 pub last_reconciled_at : Option <DateTime <Utc >>, // generated
@@ -402,9 +402,9 @@ pub struct ResourceSnapshot {
402402
403403``` mermaid
404404flowchart LR
405- M["Manifest (authored)<br/>apiVersion, kind<br/>headers: name, account?, uid ?,<br/>description?, labels, annotations<br/>spec"]
406- A["apply use case<br/>(resolve account, allocate uid ,<br/>bump generation, set timestamps)"]
407- S["Snapshot / State<br/>= authored fields<br/>+ <b>generated</b>: account(ID), uid ,<br/>generation, created/updated/deleted_at,<br/>status{phase, observedGeneration, conditions}"]
405+ M["Manifest (authored)<br/>apiVersion, kind<br/>headers: name, account?, id ?,<br/>description?, labels, annotations<br/>spec"]
406+ A["apply use case<br/>(resolve account, allocate id ,<br/>bump generation, set timestamps)"]
407+ S["Snapshot / State<br/>= authored fields<br/>+ <b>generated</b>: account(ID), id ,<br/>generation, created/updated/deleted_at,<br/>status{phase, observedGeneration, conditions}"]
408408 M --> A --> S
409409```
410410
@@ -428,14 +428,14 @@ Resources are **event-sourced with a materialized snapshot per resource**. Stora
428428
429429** Two tables:**
430430
431- - ** ` resources ` ** — one row per resource (the snapshot): ` resource_uid ` (UUID, ** PK** ),
431+ - ** ` resources ` ** — one row per resource (the snapshot): ` resource_id ` (UUID, ** PK** ),
432432 ` account_id ` , ` resource_kind ` , ` api_version ` , ` resource_name ` , ` description ` , ` labels ` /` annotations `
433433 (JSONB), ` spec ` (JSONB), ` status ` (JSONB, nullable), ` generation ` , ` created_at ` /` updated_at ` ,
434434 ` deleted_at ` (nullable), ` last_reconciled_at ` , ` last_event_id ` . ** Uniqueness:**
435435 ` UNIQUE (account_id, resource_kind, resource_name) ` — note ** api_version is not part of the key** .
436436 A partial index on ` (account_id, kind, api_version, status->>'phase') WHERE deleted_at IS NULL `
437437 backs the summary projection.
438- - ** ` resource_events ` ** — append-only log: ` event_id ` (BIGINT from a sequence, PK), ` resource_uid `
438+ - ** ` resource_events ` ** — append-only log: ` event_id ` (BIGINT from a sequence, PK), ` resource_id `
439439 (FK → ` resources ` ), ` resource_kind ` , ` event_time ` , ` event_type ` , ` event_payload ` (JSONB).
440440
441441** Source of truth.** The event log is authoritative — aggregates are rebuilt by projecting events
@@ -487,7 +487,7 @@ Resolution + permission checks live in `ResourceAccountResolverImpl`
487487 (` CurrentAccountSubject ` ) as the caller; the same admin rule applies for cross-account targeting.
488488- ** UID belonging to a different account.** Lookups are account-scoped (` find_account_snapshot `
489489 filters by ` account_id ` ). A UID that exists but belongs to another account is reported as
490- ** not-found** (` ResourceUIDNotFoundError ` ), not "forbidden" — so existence is not leaked across
490+ ** not-found** (` ResourceIDNotFoundError ` ), not "forbidden" — so existence is not leaked across
491491 accounts. A UID of the wrong * kind* yields ` ResourceTypeMismatchError ` .
492492- ** Account-deletion cascade.** When an account is deleted, ` DeleteAccountResourcesUseCase ` deletes
493493 that account's resources (see [ §13] ( #13-data-flow-walkthroughs ) ). It operates on live resources;
@@ -501,19 +501,19 @@ Resolution + permission checks live in `ResourceAccountResolverImpl`
501501The apply planner ([ ` services/apply_resource_planner.rs ` ] ( /src/domain/resources/services/src/services/apply_resource_planner.rs ) )
502502decides create vs update by resolving the target resource first:
503503
504- - ** No ` uid ` in manifest** → resolve by ` (account, kind, name) ` . Found → update; not found → create
504+ - ** No ` id ` in manifest** → resolve by ` (account, kind, name) ` . Found → update; not found → create
505505 (new UID allocated).
506- - ** ` uid ` in manifest** → load that exact resource (the "exact pointer"). This is what enables a
507- ** rename** : supply the ` uid ` and a new ` headers.name ` ; identity stays stable while the name
506+ - ** ` id ` in manifest** → load that exact resource (the "exact pointer"). This is what enables a
507+ ** rename** : supply the ` id ` and a new ` headers.name ` ; identity stays stable while the name
508508 changes.
509509
510510Concrete conflict cases:
511511
512512| Case | Outcome |
513513| --- | --- |
514- | ` uid ` + changed ` headers.name ` | ** Rename** — name updated on the same resource (` Update ` ). |
515- | ` uid ` whose resource is a different ` kind ` (or apiVersion type) | ** Reject** — ` ResourceTypeMismatchError ` . |
516- | ` uid ` resolving to a resource in a different account | ** Not found** — ` ResourceUIDNotFoundError ` (account-scoped lookup; existence not leaked). |
514+ | ` id ` + changed ` headers.name ` | ** Rename** — name updated on the same resource (` Update ` ). |
515+ | ` id ` whose resource is a different ` kind ` (or apiVersion type) | ** Reject** — ` ResourceTypeMismatchError ` . |
516+ | ` id ` resolving to a resource in a different account | ** Not found** — ` ResourceIDNotFoundError ` (account-scoped lookup; existence not leaked). |
517517| ` headers.account ` targeting another account without admin | ** Reject** — ` AccessError::Unauthorized ` ([ §7] ( #7-account-resolution--authorization ) ). |
518518| Rename target name already taken (same account+kind) | ** Reject** — unique-constraint ` Duplicate ` at persistence. |
519519| Update by name where another account has the same name | ** No conflict** — the account disambiguates; each ` (account, kind, name) ` is independent. |
@@ -612,7 +612,7 @@ pub trait ResourceFacade: Send + Sync {
612612
613613 async fn plan_apply_manifest (& self , request : ApplyManifestRequest ) -> Result <ApplyManifestPlanningDecision , ... >;
614614 async fn apply_manifest (& self , request : ApplyManifestRequest ) -> Result <ApplyManifestApplicationDecision , ... >;
615- async fn delete (& self , selector : ResourceSelector ) -> Result <ResourceUID , ... >;
615+ async fn delete (& self , selector : ResourceSelector ) -> Result <ResourceID , ... >;
616616 async fn delete_many (& self , selector : ResourceBatchSelector ) -> ... ;
617617}
618618```
@@ -622,7 +622,7 @@ pub trait ResourceFacade: Send + Sync {
622622``` rust
623623pub struct ResourceSelector { pub account : Option <ResourceManifestAccount >, pub kind : String ,
624624 pub api_version : Option <String >, pub resource_ref : ResourceRef }
625- pub enum ResourceRef { ById (ResourceUID ), ByName (ResourceName ) }
625+ pub enum ResourceRef { ById (ResourceID ), ByName (ResourceName ) }
626626pub enum ResourceManifestFormat { Json , Yaml }
627627pub enum SpecViewMode { Encrypted /* default */ , Revealed }
628628```
@@ -798,7 +798,7 @@ sequenceDiagram
798798 participant ST as Event store + repo
799799 participant OB as Outbox
800800
801- UC->>L: load(uid ) %% needs_reconciliation()? observed_generation < generation
801+ UC->>L: load(id ) %% needs_reconciliation()? observed_generation < generation
802802 UC->>ST: try_mark_reconciliation_started → append event
803803 UC->>Rec: reconcile(resource) %% e.g. SecretSet builds encrypted projection
804804 alt success
@@ -856,7 +856,7 @@ flowchart LR
856856 A["apply use case<br/>(persist + produce Applied)"] --> OB[("Outbox")]
857857 OB --> C["ResourceLifecycleMessageConsumer<br/>(event bridge)"]
858858 C -->|lookup by kind/apiVersion| D["per-kind ResourceLifecycleEventDispatcher<br/>handle_applied()"]
859- D --> R["ReconcileResourceUseCase::execute(uid )"]
859+ D --> R["ReconcileResourceUseCase::execute(id )"]
860860 R --> Rec["Reconciler<R>"]
861861```
862862
@@ -865,7 +865,7 @@ flowchart LR
865865 ` MESSAGE_CONSUMER_KAMU_RESOURCE_LIFECYCLE_EVENT_BRIDGE ` ,
866866 [ ` message_handlers/resource_lifecycle_message_consumer.rs ` ] ( /src/domain/resources/services/src/message_handlers/resource_lifecycle_message_consumer.rs ) )
867867 consumes it and looks up the per-kind ` ResourceLifecycleEventDispatcher ` by descriptor.
868- 3 . That dispatcher's ` handle_applied ` calls ** ` ReconcileResourceUseCase::execute(uid ) ` ** , which loads
868+ 3 . That dispatcher's ` handle_applied ` calls ** ` ReconcileResourceUseCase::execute(id ) ` ** , which loads
869869 the aggregate, marks reconciliation started, invokes the kind's ` Reconciler<R> ` , and records the
870870 outcome (producing ` ReconciliationSucceeded ` / ` ReconciliationFailed ` ).
871871
@@ -1053,11 +1053,11 @@ list/search, supported kinds, get-identity, error taxonomy, delete, render-manif
10531053summary, and spec view modes.
10541054
10551055** E2E (CLI)** —
1056- [ ` src/e2e/app/cli/repo-tests/src/commands/resources ` ] ( /src/e2e/app/cli/repo-tests/src/commands/resources ) .
1056+ [ ` src/e2e/app/cli/repo-tests/src/commands/resources ` ] ( /src/e2e/app/cli/repo-tests/src/commands/resources/ ) .
10571057Drive the real CLI binary via ` KamuCliPuppet ` . A ` ResourceCtx ` abstraction
10581058(` repo-tests/src/resources/context.rs ` ) runs every scenario against ** both** an implicit local
10591059context and a registered remote context. The ` get_view.rs ` helper parses ` get -o json ` into a
1060- queryable ` ResourceView ` (` ident() ` , ` variable() ` , ` has_secret() ` , ` uid ()` , …) so assertions are
1060+ queryable ` ResourceView ` (` ident() ` , ` variable() ` , ` has_secret() ` , ` id ()` , …) so assertions are
10611061targeted rather than brittle; a golden-view test pins the whole-document shape once per kind.
10621062
10631063### Testing policy — what belongs where
0 commit comments