Skip to content

Commit 300bb0a

Browse files
dmealingclaude
andcommitted
docs(spec): ADR-0026/FR-019 — @provided on the named-type declaration (not @external)
Revises the round-2 open-question-B decision per review: the ownership flag is NOT a field attr and NOT enum-specific. It is `@provided` — a provenance flag on a named-type DECLARATION (package-level abstract field.enum, object.value, or a future top-level enum), applying uniformly across enums + value objects: "this type is provided by hand-written/third-party code — reference it, don't materialize it." Default false; @values/field-shape stay the metadata contract; the per-port namespace is codegen config (ADR-0001), never a metadata FQN. `@external` rejected (vague — external to which boundary? meaningless on scalar fields). `@generated:false` rejected (collides with identity @generation). - ADR-0026 renamed shared-and-external-enum-types → shared-and-provided-named-types; generalized scope to named types (enums + value objects); shared vs provided kept orthogonal. - FR-019 renamed → shared-and-provided-enums; implements the concept for enums (the driving need) with value-object @provided a noted follow-up; fixtures renamed (enum-shared-materialized-once / enum-provided-not-materialized / error-provided-not-boolean). - roadmap entry updated. Decision docs only — no code. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 2965e39 commit 300bb0a

5 files changed

Lines changed: 238 additions & 219 deletions

docs/superpowers/specs/2026-06-06-fr-019-shared-and-external-enums-design.md

Lines changed: 0 additions & 112 deletions
This file was deleted.
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
# FR-019 — Shared + externally-provided enums
2+
3+
**Status:** Design (ready for implementation)
4+
**Created:** 2026-06-06
5+
**Decision:** [ADR-0026](../../../spec/decisions/ADR-0026-shared-and-provided-named-types.md)
6+
**Builds on:** [enum datatype design](2026-05-23-enum-datatype-design.md) (D6 — abstract `field.enum` + `extends`)
7+
8+
## Why this doc exists
9+
10+
`field.enum` members are the cross-port SSOT, but codegen **redeclares them inline
11+
in every consuming entity** (a nested `public enum AuthType` per C# class; a
12+
per-entity union in TS). The enum design's D6 sanctioned *reuse* via an abstract
13+
`field.enum` + `extends`, yet the emitted code still copies the members into each
14+
consumer. Two adopter needs remain unmet — **shared reuse** (materialize one type)
15+
and **external ownership** (reference a hand-written type, don't emit).
16+
17+
Per [ADR-0026](../../../spec/decisions/ADR-0026-shared-and-provided-named-types.md),
18+
the ownership concept is a **`@provided` flag on the named-type declaration**,
19+
applying uniformly to enums and value objects — **not** a field attr and **not**
20+
enum-specific. This FR implements the concept **for enums** (the driving need);
21+
value-object `@provided` is a noted follow-up using the same attribute.
22+
23+
## Scope
24+
25+
1. **Shared materialization:** a **package-level abstract `field.enum`** is
26+
materialized ONCE per port as a standalone enum type; concrete `extends`-ing
27+
fields reference it.
28+
2. **`@provided: true`** on the named-type declaration: suppress materialization;
29+
consuming fields reference an existing type, resolved via per-port codegen
30+
config.
31+
3. Cross-port conformance fixtures pinning both.
32+
33+
Out of scope (unchanged): int-backed values, display labels, native PG enum.
34+
**Retired:** the C#-only `@csEnumType` FQN attr (ADR-0026 §3).
35+
**Follow-up (not here):** `@provided` on `object.value` (reference a hand-written
36+
value class) — same attribute, separate slice gated on a real need.
37+
38+
## Metamodel (Tier 1 — invariant)
39+
40+
No new type or subtype. Reuses the D6 vocabulary:
41+
- An **abstract `field.enum`** declared at the **package/root level** (a sibling
42+
of `object.entity`, not nested in one): `abstract: true`, `@values: [...]`. Its
43+
`name` is the enum's cross-port identity + the materialized type name.
44+
- A concrete `field.enum` on an entity `extends`-ing it (D6).
45+
46+
New attr **on the named-type declaration** (the abstract `field.enum`), own-only:
47+
- **`@provided`** — boolean. `true` ⇒ do NOT materialize the enum type; consuming
48+
fields reference an existing type. Default `false`. **Not a field attr** — it
49+
lives on the (abstract) declaration; placing `@provided` on a concrete
50+
consuming field is invalid. Loader: `ERR_BAD_ATTR_VALUE` for a non-boolean.
51+
Validation parity across all five ports (own-only).
52+
53+
Unchanged invariants: `@values` non-empty, member regex, no dupes; the wire form
54+
is the string symbol; the discriminator / `CHECK` / validation contracts are
55+
identical whether the enum is inline, shared, or `@provided` (the values stay the
56+
SSOT — a port referencing a provided type still validates against `@values` +
57+
emits the `CHECK`).
58+
59+
**No per-port type binding in metadata** (ADR-0001): the namespace/package a
60+
`@provided` enum resolves to is **codegen config** per port (e.g. an
61+
`enumNamespace` / `enumPackage` / `enumModule` setting), never a metadata attr.
62+
63+
## Per-port codegen (Tier 2 — idiomatic)
64+
65+
For a **shared (package-level abstract, non-provided) `field.enum` named `E` with
66+
values `[…]`**, materialize ONE standalone type and reference it from consumers:
67+
68+
| Port | Materialize the shared type | Field references it |
69+
|---|---|---|
70+
| TS | exported `export type E = "A" \| "B"` (+ a shared `z.enum(["A","B"])`) at module/package level | property typed `E`; Zod uses the shared `z.enum` |
71+
| C# | namespace-level `public enum E { A, B }` + `…HasConversion<string>()` | property typed `E` (no nested redeclare) |
72+
| Kotlin | package-level `enum class E { A, B }` | property typed `E` |
73+
| Python | module-level `class E(str, Enum): A="A"; B="B"` | field typed `E` |
74+
| DB | one shared `CHECK (col IN ('A','B'))` per consuming column (values identical) ||
75+
76+
For **`@provided: true`**: materialize **nothing** for `E`; each consuming field
77+
references `E` resolved to the port's configured namespace/package + `E` (C#
78+
`<cfg.enumNamespace>.E`, Kotlin/Java `<cfg.enumPackage>.E`, Python an import of
79+
`E` from `<cfg.enumModule>`, TS an import of `E`). A missing config for a
80+
referenced `@provided` enum is a **codegen-time error** (clear message naming the
81+
enum + the config key), not a metadata error.
82+
83+
**Byte-identical default:** an enum that is NOT a package-level shared/abstract
84+
enum (the common inline case) emits exactly as today — the per-entity nested
85+
declaration. Shared and `@provided` are additive branches gated on "package-level
86+
abstract `field.enum`". Drift/golden gates enforce no change to the inline default.
87+
88+
Each port's generators are subclass-extensible (the extensibility rounds), so an
89+
adopter can further tailor the materialized enum or its reference without forking.
90+
91+
## Conformance (Tier 5)
92+
93+
Add to `fixtures/conformance/` (metamodel/loader) + the per-port codegen golden
94+
suites:
95+
- **`enum-shared-materialized-once`** — a package-level abstract `field.enum`
96+
extended by two entities; assert (per port) the enum type is materialized ONCE
97+
at package/module level and both entities reference it (no nested redeclaration).
98+
- **`enum-provided-not-materialized`**`@provided: true`; assert no enum type is
99+
emitted and consuming fields reference the configured external name; assert the
100+
`@values` still drive validation + the `CHECK`.
101+
- **`error-provided-not-boolean`**`@provided: "yes"``ERR_BAD_ATTR_VALUE`
102+
(all five ports, own-only; byte-identical envelope where provenance allows —
103+
respect the Java override-field provenance deferral if it applies).
104+
- A codegen-config fixture exercising the per-port `enumNamespace`/`enumPackage`
105+
resolution for the `@provided` case.
106+
107+
The corpus is the oracle: TS reference green first, then each port matches.
108+
109+
## Realization status
110+
111+
- **Unimplemented.** This spec + ADR-0026 lock the decision. Implementation order:
112+
(1) loader `@provided` attr + validation + conformance fixtures (TS green);
113+
(2) TS shared-materialize + provided-reference codegen; (3) per-port fan-out
114+
(C#/Java/Kotlin/Python) against the fixtures; (4) retire `@csEnumType`.
115+
116+
## Cross-references
117+
- [ADR-0026](../../../spec/decisions/ADR-0026-shared-and-provided-named-types.md)`@provided` as a cross-type provenance flag (enums + value objects); shared vs provided orthogonal; Option 2 upgrade path.
118+
- [ADR-0001](../../../spec/decisions/ADR-0001-cross-language-type-binding.md) — metadata→native type binding is per-port build-time config, not metadata.
119+
- [enum datatype design](2026-05-23-enum-datatype-design.md) — D6 abstract-enum reuse; deferred int-backed/display-label/native-PG-enum.

spec/decisions/ADR-0026-shared-and-external-enum-types.md

Lines changed: 0 additions & 106 deletions
This file was deleted.

0 commit comments

Comments
 (0)