Skip to content

Commit 852d839

Browse files
authored
chore(api): add @beta annotation + apply to NodeDefinition SPI (H2) (#90)
Wires up Track H2 from the v1.6.5->1.7 readiness taskboard. Adds the @beta annotation that the G3 stability policy (#88) referenced as the planned Extension SPI / Experimental marker, applies it to the first concrete Extension SPI seam, and lifts the 'pending' hedges in the policy doc. Changes: - New com.demcha.compose.document.api.Beta annotation, sibling to @internal. Same retention/target/contract shape as @internal so guard tests handle both uniformly; lives next to @internal so IDE autocomplete surfaces both markers. - @beta applied to com.demcha.compose.document.layout.NodeDefinition - the canonical custom-node-type seam carved out of the otherwise @internal document.layout package. The interface Javadoc now explains the Extension SPI contract and links the stability policy. - BetaAnnotationDocumentationTest pins the annotation contract (retention, @documented, target set, source-Javadoc invariants, same-package-as-@internal) in the same shape InternalAnnotationDocumentationTest pins @internal. - docs/api-stability.md: @beta annotation reference cells in section 1 are no longer hedged as 'pending'; the associated quote block lists both markers side-by-side with the guard tests. - docs/architecture/package-map.md: new intro paragraph documents the stability-marker convention; document.layout row now calls out NodeDefinition as the current @beta seam. Suite: 1029 tests, 0 failures, 0 errors (+5 from BetaAnnotationDocumentationTest).
1 parent e28b562 commit 852d839

6 files changed

Lines changed: 204 additions & 10 deletions

File tree

CHANGELOG.md

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,39 @@ JitPack continue to resolve through the existing coordinates.
3535
Method-level `@since` backfill for the ~380 public methods in these
3636
packages is intentionally out of scope here and tracked separately.
3737

38+
### Public API
39+
40+
- **New `@Beta` annotation** (Track H2). Companion to the existing
41+
[`@Internal`](src/main/java/com/demcha/compose/document/api/Internal.java)
42+
marker:
43+
[`com.demcha.compose.document.api.Beta`](src/main/java/com/demcha/compose/document/api/Beta.java)
44+
signals an **Extension SPI** or **Experimental** surface — a
45+
deliberately-exposed seam library users can implement or call, but
46+
whose shape may still evolve between minor releases per the
47+
[API stability policy](docs/api-stability.md) § 1. First application:
48+
[`com.demcha.compose.document.layout.NodeDefinition`](src/main/java/com/demcha/compose/document/layout/NodeDefinition.java)
49+
— the canonical custom-node-type seam, carved out of the otherwise
50+
`@Internal` `document.layout` package. New
51+
`BetaAnnotationDocumentationTest` pins the annotation's retention /
52+
target / `@Documented`-ness / source-Javadoc contract in the same
53+
shape `InternalAnnotationDocumentationTest` already pins for
54+
`@Internal`. Additional Extension SPI surfaces (render-handler
55+
interfaces, fragment-payload interfaces) will gain the marker
56+
incrementally as their contract solidifies.
57+
58+
### Documentation
59+
60+
- **`docs/architecture/package-map.md` updated** alongside H2. A new
61+
intro paragraph documents the stability-marker convention (Stable
62+
default; engine packages are package-level `@Internal`; individual
63+
Extension SPI seams carved out of `@Internal` packages carry
64+
`@Beta`), and the `document.layout` row calls out `NodeDefinition`
65+
as the current `@Beta` seam.
66+
- **`docs/api-stability.md` revised** alongside H2 — `@Beta` annotation
67+
reference cells in §1 are no longer hedged as "pending"; the
68+
associated quote block lists both annotations side-by-side with the
69+
guard tests that pin them.
70+
3871
### Engine internals (no behaviour change)
3972

4073
- **`RowSlots` helper extracted** from `LayoutCompiler` and

docs/api-stability.md

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,18 +30,22 @@ matrix.
3030
|---|---|---|---|
3131
| **Stable** | _(default — no annotation)_ | The canonical authoring surface that user code is meant to call: `GraphCompose.document(...)`, `DocumentSession`, `DocumentDsl`, `RowBuilder` / `SectionBuilder` / `ParagraphBuilder` and friends, `DocumentInsets` / `DocumentColor` / `DocumentTextStyle`, the `BusinessTheme` and `CvTheme` factories, the recommended template presets in `cv.v2.*` and `coverletter.v2.*`. | **Major releases only.** |
3232
| **Supported** | _(no annotation; called out in the page's Javadoc)_ | A canonical surface that ships through 1.x but won't be in 2.0 — its replacement is already the Stable path. The `cv.presets.*` "classic" CV preset surface is the only Supported tier in 1.x today (replaced by `cv.v2.*` per [`which-template-system.md`](templates/which-template-system.md)). Bug fixes + behaviour-preserving refactors only. | **Minor releases for behaviour-preserving refactors; removed wholesale in 2.0.** |
33-
| **Extension SPI** | `@Beta` _(annotation arriving in a near-term 1.6.x release; this row already describes the policy that will apply)_ | Public extension points that authors are expected to **implement**, not only call: render-handler interfaces, `NodeDefinition`, custom `Theme` subtype contracts, fragment payload interfaces designed for extension. | Minor releases, with a one-minor deprecation window where possible. |
34-
| **Experimental** | `@Beta` _(same annotation as Extension SPI; the distinction lives in the docstring)_ | A brand-new public type shipping in its first minor release before its contract has stabilised. The contract is in active flux. | Any minor release, including removal. No deprecation window. |
33+
| **Extension SPI** | [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java) | Public extension points that authors are expected to **implement**, not only call: render-handler interfaces, [`NodeDefinition`](../src/main/java/com/demcha/compose/document/layout/NodeDefinition.java), custom `Theme` subtype contracts, fragment payload interfaces designed for extension. | Minor releases, with a one-minor deprecation window where possible. |
34+
| **Experimental** | [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java) _(same annotation as Extension SPI; the distinction lives in the docstring on the annotated element)_ | A brand-new public type shipping in its first minor release before its contract has stabilised. The contract is in active flux. | Any minor release, including removal. No deprecation window. |
3535
| **Internal** | [`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java) (per-element or per-package) | Engine surface: everything in `com.demcha.compose.document.layout.*`, `com.demcha.compose.engine.*`, render-pipeline payload records, `LayoutCompiler`, `NodeDefinitionSupport`, the placement / measure / split contracts. Technically `public` for cross-package collaboration; not part of the contract. Canonical list lives in [ADR-0003](adr/0003-api-stability-and-internal-marker.md) § *Coverage*. | **Any release.** No deprecation window, no CHANGELOG entry required. |
3636
| **Legacy** | _(no annotation today; flagged in [`which-template-system.md`](templates/which-template-system.md) § 4 and in CHANGELOG `### Deprecations`)_ | Pre-rebuild surface kept only so downstream callers from before the v1.6 rebuild keep compiling: `com.demcha.templates.*` (the original `MainPageCV` / `MainPageCvDTO` / `ModuleYml` / `TemplateBuilder` family), `com.demcha.compose.v2.*` (the original engine-direct builders). Frozen — bug fixes only. | **Removed in 2.0**; no patch / minor changes other than security fixes. |
3737

38-
> The repo's [`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java)
39-
> annotation already exists and is enforced by
40-
> [`InternalAnnotationCoverageTest`](../src/test/java/com/demcha/documentation/InternalAnnotationCoverageTest.java)
41-
> and `InternalAnnotationDocumentationTest`. The `@Beta` annotation is
42-
> still pending — until it lands, Extension SPI and Experimental tiers
43-
> are signalled in Javadoc prose only; this page describes the policy
44-
> the annotation will encode once it ships.
38+
> Both marker annotations
39+
> ([`@Internal`](../src/main/java/com/demcha/compose/document/api/Internal.java)
40+
> and [`@Beta`](../src/main/java/com/demcha/compose/document/api/Beta.java))
41+
> live in the public `document.api` package and are pinned by
42+
> [`InternalAnnotationCoverageTest`](../src/test/java/com/demcha/documentation/InternalAnnotationCoverageTest.java),
43+
> `InternalAnnotationDocumentationTest`, and `BetaAnnotationDocumentationTest`.
44+
> The Extension SPI seam currently carrying `@Beta` is
45+
> [`NodeDefinition`](../src/main/java/com/demcha/compose/document/layout/NodeDefinition.java);
46+
> additional Extension SPI surfaces (render-handler interfaces,
47+
> fragment-payload interfaces designed for extension) will gain the
48+
> marker incrementally as their contract solidifies.
4549
4650
### What each tier promises
4751

docs/architecture/package-map.md

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,17 @@
22

33
This document is the source of truth for production package ownership in the canonical engine branch.
44

5+
**Stability markers.** Public packages default to the **Stable** tier of the
6+
[API stability policy](../api-stability.md). Engine packages (`com.demcha.compose.engine.*`,
7+
`com.demcha.compose.document.layout`, render-pipeline internals) carry the
8+
[`@Internal`](../../src/main/java/com/demcha/compose/document/api/Internal.java)
9+
marker at the package level; individual types deliberately exposed as
10+
Extension SPI inside an `@Internal` package carry
11+
[`@Beta`](../../src/main/java/com/demcha/compose/document/api/Beta.java)
12+
on the type itself (`NodeDefinition` is the current example). The
13+
"Extension rule" column below names the extension seam where one is
14+
intended.
15+
516
## Public Authoring Surface
617

718
| Package | Responsibility | Extension rule |
@@ -20,7 +31,7 @@ This document is the source of truth for production package ownership in the can
2031

2132
| Package | Responsibility | Extension rule |
2233
| --- | --- | --- |
23-
| `com.demcha.compose.document.layout` | Semantic layout compiler, node definitions, fragments, split/measure contracts, and layout graph. | New node behavior must be deterministic and covered by layout or render tests. |
34+
| `com.demcha.compose.document.layout` (`@Internal` at package level) | Semantic layout compiler, node definitions, fragments, split/measure contracts, and layout graph. | `NodeDefinition` is `@Beta` — Extension SPI for custom node types. New node behavior must be deterministic and covered by layout or render tests. |
2435
| `com.demcha.compose.document.backend.fixed` | Backend-neutral fixed-layout rendering contract. | Keep it independent from PDFBox and semantic template data. |
2536
| `com.demcha.compose.document.backend.fixed.pdf` | Canonical fixed-layout PDF backend, fragment handlers, PDF-specific options, and PDF-backed measurement resources. | Keep PDFBox lifecycle internal; normal callers should use `DocumentSession` and default PDF convenience methods. |
2637
| `com.demcha.compose.document.dsl.internal` | Internal helpers for public DSL builders such as semantic name normalization and builder callback application. | Do not expose these helpers in examples; move reusable authoring concepts to public builder classes instead. |
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
package com.demcha.compose.document.api;
2+
3+
import java.lang.annotation.Documented;
4+
import java.lang.annotation.ElementType;
5+
import java.lang.annotation.Retention;
6+
import java.lang.annotation.RetentionPolicy;
7+
import java.lang.annotation.Target;
8+
9+
/**
10+
* Marks a public API element as an <strong>Extension SPI</strong> or
11+
* <strong>Experimental</strong> surface — a deliberately-exposed seam that
12+
* library users can implement or call, but whose shape may still change
13+
* between minor releases.
14+
*
15+
* <p>Elements annotated {@code @Beta} carry the policy spelled out in
16+
* <a href="https://github.com/DemchaAV/GraphCompose/blob/develop/docs/api-stability.md">
17+
* the API stability policy</a> § 1 — broadly:</p>
18+
*
19+
* <ul>
20+
* <li><strong>Extension SPI</strong> usage (the common case): the seam
21+
* is intentionally public so users can implement or call it, but
22+
* the shape may evolve in a minor release. When it does, the
23+
* previous shape ships {@code @Deprecated} for at least one minor
24+
* release first and the CHANGELOG entry calls out the migration
25+
* under {@code ### Public API}.</li>
26+
* <li><strong>Experimental</strong> usage: a brand-new public type
27+
* shipping in its first minor release before the contract has
28+
* stabilised. The contract is in active flux and may change or
29+
* disappear in any minor release without a deprecation window;
30+
* the docstring on the element says so explicitly.</li>
31+
* </ul>
32+
*
33+
* <p>The distinction between the two usages lives in the element's own
34+
* Javadoc, not in this annotation. The annotation itself just signals
35+
* "policy applies — read the page before depending on this".</p>
36+
*
37+
* <p>If your use case requires a stable contract over an
38+
* {@code @Beta}-marked element, please open an issue at
39+
* <a href="https://github.com/DemchaAV/GraphCompose/issues">the
40+
* GraphCompose tracker</a> so the stabilisation can be prioritised.</p>
41+
*
42+
* <p>Architecture guard tests may consume this annotation reflectively;
43+
* the runtime retention is intentional.</p>
44+
*
45+
* @see Internal
46+
* @since 1.6.6
47+
*/
48+
@Documented
49+
@Retention(RetentionPolicy.RUNTIME)
50+
@Target({
51+
ElementType.TYPE,
52+
ElementType.METHOD,
53+
ElementType.FIELD,
54+
ElementType.CONSTRUCTOR,
55+
ElementType.PACKAGE,
56+
})
57+
public @interface Beta {
58+
}

src/main/java/com/demcha/compose/document/layout/NodeDefinition.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,28 @@
11
package com.demcha.compose.document.layout;
22

3+
import com.demcha.compose.document.api.Beta;
34
import com.demcha.compose.document.node.DocumentNode;
45

56
import java.util.List;
67

78
/**
89
* Prepare, split, and fragment emission contract for a semantic node type.
910
*
11+
* <p><b>Extension SPI seam.</b> This interface is the deliberately-public
12+
* extension point inside the otherwise {@link com.demcha.compose.document.api.Internal
13+
* &#64;Internal} {@code document.layout} package: library users implementing
14+
* custom node types reach for it. The implementation contract — the
15+
* shape of {@link PrepareContext}, {@link FragmentContext},
16+
* {@link BoxConstraints}, {@link MeasureResult}, and friends — may
17+
* evolve in a minor release, with a one-minor deprecation window where
18+
* possible. See the
19+
* <a href="https://github.com/DemchaAV/GraphCompose/blob/develop/docs/api-stability.md">API stability policy</a>
20+
* § 1 (<em>Extension SPI</em> row) for the full contract.</p>
21+
*
1022
* @param <E> semantic node type
23+
* @since 1.0.0
1124
*/
25+
@Beta
1226
public interface NodeDefinition<E extends DocumentNode> {
1327

1428
/**
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.demcha.compose.document.api;
2+
3+
import static org.assertj.core.api.Assertions.assertThat;
4+
5+
import java.io.IOException;
6+
import java.lang.annotation.Documented;
7+
import java.lang.annotation.ElementType;
8+
import java.lang.annotation.Retention;
9+
import java.lang.annotation.RetentionPolicy;
10+
import java.lang.annotation.Target;
11+
import java.nio.file.Files;
12+
import java.nio.file.Path;
13+
14+
import org.junit.jupiter.api.Test;
15+
16+
/**
17+
* Pins the contract of the {@link Beta} annotation in the same shape
18+
* {@link InternalAnnotationDocumentationTest} pins {@link Internal}.
19+
* If the retention, target set, or Javadoc policy drifts, the build
20+
* fails here rather than during a future architecture refactor.
21+
*/
22+
class BetaAnnotationDocumentationTest {
23+
24+
@Test
25+
void annotationHasRuntimeRetentionForArchitectureGuards() {
26+
Retention retention = Beta.class.getAnnotation(Retention.class);
27+
assertThat(retention)
28+
.describedAs("@Beta must declare @Retention so guard tests can read it reflectively")
29+
.isNotNull();
30+
assertThat(retention.value()).isEqualTo(RetentionPolicy.RUNTIME);
31+
}
32+
33+
@Test
34+
void annotationIsDocumentedSoJavadocSurfacesIt() {
35+
assertThat(Beta.class.getAnnotation(Documented.class))
36+
.describedAs("@Beta must be @Documented so Javadoc surfaces the marker on annotated types")
37+
.isNotNull();
38+
}
39+
40+
@Test
41+
void annotationTargetsAllExpectedElementKinds() {
42+
Target target = Beta.class.getAnnotation(Target.class);
43+
assertThat(target).isNotNull();
44+
assertThat(target.value()).contains(
45+
ElementType.TYPE,
46+
ElementType.METHOD,
47+
ElementType.FIELD,
48+
ElementType.CONSTRUCTOR,
49+
ElementType.PACKAGE);
50+
}
51+
52+
@Test
53+
void annotationSourceJavadocStatesTheStabilityContract() throws IOException {
54+
Path source = Path.of("src/main/java/com/demcha/compose/document/api/Beta.java");
55+
String body = Files.readString(source);
56+
assertThat(body)
57+
.describedAs("Javadoc must name both Extension SPI and Experimental usages")
58+
.contains("Extension SPI")
59+
.contains("Experimental");
60+
assertThat(body)
61+
.describedAs("Javadoc must link to the API stability policy page")
62+
.contains("docs/api-stability.md");
63+
assertThat(body)
64+
.describedAs("Javadoc must direct users to the issue tracker for stabilisation requests")
65+
.contains("github.com/DemchaAV/GraphCompose/issues");
66+
}
67+
68+
@Test
69+
void betaAndInternalLiveInTheSameApiPackage() {
70+
assertThat(Beta.class.getPackage().getName())
71+
.describedAs("@Beta should live next to @Internal so IDE autocomplete surfaces both")
72+
.isEqualTo(Internal.class.getPackage().getName());
73+
}
74+
}

0 commit comments

Comments
 (0)