Skip to content

feat(component): define metadata.version semantics and the endpoint block - #27

Merged
justinmerrell merged 2 commits into
mainfrom
feat/component-metadata-and-endpoints
Aug 9, 2026
Merged

feat(component): define metadata.version semantics and the endpoint block#27
justinmerrell merged 2 commits into
mainfrom
feat/component-metadata-and-endpoints

Conversation

@justinmerrell

Copy link
Copy Markdown
Contributor

What changes

Closes the two component/v1/spec.md sections that were a heading and a TODO with no normative content: §4 Metadata and §5.2 Endpoints. Both were left open by #13, and #9 supplied an answer for neither.

§4 now states what metadata carries — version and nothing else, no slug, because a component is not the item and its name is the stem of the file holding it — and answers #15's three questions. §5.2 defines the endpoint block and answers #16's three, plus two things §5.2 turned out to owe that #16 does not name. platformDefault gains prose and an endpoint selector; it had no prose at all before.

Two commits, one per issue, so the scoping survives review.

Why

Closes #15. Closes #16.

metadata.version was already load-bearing in three rules — blueprint §4.1 deploys "the referenced document's own metadata.version", blueprint §3 and listing §3 pin an item's two halves to each other, and the published reference form carries its own componentVersion. The endpoint block was constrained from the outside (§5 forbids endpoints on a WORKER, JOB and CRON; §5.4 makes readiness REQUIRED for a SERVICE exposing a PUBLIC endpoint) and undefined on the inside.

§4 — metadata.version (#15)

Question Answer
Must it increase? Yes. Each publication MUST carry a version strictly greater than the lineage's highest. Gaps are permitted.
May a version be reused? No — the same rule covers it.
What does it relate to in the item? Nothing. Stated as a decision rather than left as a gap.

The phase is capability, because deciding it needs the catalog and §7 forbids the earlier phases from reaching the network. Offline validation is therefore exactly as strict as it was, which is what makes this a minor release rather than a narrowing one. Whether a registry treats an identical re-submission as a no-op rather than a publication stays outside this contract — that sidesteps having to define when two YAML files are the same document.

ERR_VERSION_NOT_MONOTONIC can have no fixture for the same reason ERR_UNKNOWN_COMPONENT cannot, and is recorded in the runner's UNCOVERED list with that reason.

This does not answer listing §3's COMPONENT-item question. That is #21 and its TODO stands.

§5.2 — Endpoints (#16)

Question Answer Phase
Port range 165535. Below 1024 is a SHOULD NOT, not a rejection. structural
Protocol × visibility PUBLIC MUST be HTTP/HTTPS/WS/GRPC; TCP/UDP MUST be PRIVATE. structural
More than one PUBLIC Permitted. Each publishes its own URL, so anything naming a public address MUST name the endpoint.

A privileged port stays advice because whether the container can bind one depends on a capability the runtime grants, which this document cannot see — and a rule that rejects on a fact it cannot check is guessing. structural-026-privileged-container-port is what keeps that a SHOULD NOT rather than drifting into a MUST.

Permitting several PUBLIC endpoints is what forces platformDefault from a bare enum to an object carrying source and endpoint.

Three findings that differ from what the issues assume

  1. self.publicUrl does not exist in this repository. component §5.2: endpoint port range, protocol/visibility interaction, multiple PUBLIC endpoints #16 cites it as component §6.2, but §6.2 only says "its public URL" in prose. The real surface was ComponentInput.platformDefault, which had no prose in any spec.md and whose description said "the component's own public endpoint" — singular.
  2. Capping PUBLIC at one would not have been structural. JSON Schema 2020-12 has no keyword that counts mapping members matching a condition (maxContains is arrays only). component §5.2: endpoint port range, protocol/visibility interaction, multiple PUBLIC endpoints #16 says all three answers are structural; only the port bound and protocol × visibility are.
  3. Two adjacent things in §5.2 were undefined, and both are narrowing, so the window is the same one:
    • "The primary endpoint" was used by §5.4 and by ComponentProbe.endpoint and defined nowhere. It is now the sole endpoint, failing that the sole PUBLIC one, failing that nothing — an error rather than a sort-order tiebreak, because a tiebreak lets a new endpoint named api silently re-point a probe that has worked for a year.
    • Endpoint names were bare mapping keys, so web.api was legal despite being referenced by probes and platform defaults and becoming a DNS label. They now take the slug grammar blueprint §4.1 quotes.

One rejection neither issue asked for

Defining the primary endpoint means a probe on a workload declaring no endpoint now fails — it polls a port that does not exist. §5 permits a SERVICE to declare no endpoint and that is unchanged; a probe on one is what stops validating. It has explicit prose in §5.2 and its own fixture rather than being left for a reader to derive.

Compatibility

  • Breaking — a previously valid document now fails.

Four narrowings: bounding containerPort, restricting a PUBLIC endpoint's protocol, constraining endpoint names, and reshaping platformDefault.

No new v<N> directory, and the reason is that v1 has never been published. git tag -l is empty and .github/release-please/manifest.json reads 0.0.0 for all three families. §3's guarantee — "a document that validated against v1.0.0 MUST validate against every later v1.x.y" — has no released v1.0.0 to run from, so there is no document anywhere that was validated against a published v1 and would now fail. A migration note would have nothing to migrate from.

That argument expires when #1, #2 and #3 merge. #24 sets the same deadline for its divergence 3. If the release PRs land first, this becomes a v2 change.

The §4 half is additive on its own: ERR_VERSION_NOT_MONOTONIC is capability, so no document that validates offline today stops validating.

Corpus impact — verified

Nothing in the repository breaks. Ports in use were only 8080 and 5432; endpoint names only web and primary; no example or fixture paired PUBLIC with TCP/UDP, declared more than one endpoint, or used platformDefault at all.

Checklist

  • task check passes locally
  • schemas/dist/ regenerated with task bundle and committed (never edited by hand)
  • Conformance fixtures added for every behavioural change, each citing a clause
  • Normative prose updated in the affected spec.md — schema descriptions are explanatory, not normative
  • Commit messages are Conventional and correctly scoped (the scope drives release-please)
  • Commits are DCO signed off (git commit -s)

12 new fixtures, 76 → 88 cases, 0 skipped. ERR_AMBIGUOUS_ENDPOINT, ERR_ENDPOINT_NOT_PUBLIC and the broadened ERR_UNKNOWN_ENDPOINT are each exercised; ERR_VERSION_NOT_MONOTONIC is in UNCOVERED with a reason. 9 examples validate, including a new multi-endpoint.yaml — the only place a reader sees the per-endpoint addressing.

🤖 Generated with Claude Code

justinmerrell and others added 2 commits August 9, 2026 09:54
Section 4 was a heading and a TODO, while metadata.version was already
load-bearing in three rules: blueprint §4.1 deploys "the referenced
document's own metadata.version", blueprint §3 and listing §3 pin an
item's two halves to each other, and the published reference form
carries its own componentVersion.

Answers the three questions #15 asks.

Monotonicity: each publication MUST carry a version strictly greater
than the highest already published for that component. Gaps are
permitted. The phase is capability, because deciding it needs the
catalog and §7 forbids the earlier phases from reaching the network —
so offline validation is exactly as strict as it was and this is a
minor release rather than a narrowing one.

Reuse: a version is used once. componentVersion: 3 is the whole of what
a published node deploys, and a registry that let 3 mean two documents
would make the pin name nothing. Whether an identical re-submission is
a no-op rather than a publication stays outside this contract, which
orders publications without defining when two YAML files are the same
document.

Relationship to the item: none. The two numbers count different things,
and in the published form one component is deployed by many items at
once. A component release SHOULD be accompanied by an item release,
without a diagnostic — the disagreement is visible only across two
revisions and a validator is handed one.

ERR_VERSION_NOT_MONOTONIC is the family's first capability code, so §8's
closing paragraph no longer says the semantic codes alone are its own.
It can have no fixture for the same reason ERR_UNKNOWN_COMPONENT cannot,
and is recorded in the runner's UNCOVERED list with that reason.

Section 4 also states what metadata carries — version and nothing else,
no slug — which nothing said before.

Refs #15

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
…point

Section 5.2 was a heading and a TODO, while the endpoint block was
already constrained from the outside: §5 forbids endpoints on a WORKER,
JOB and CRON, and §5.4 makes readiness REQUIRED for a SERVICE exposing
a PUBLIC endpoint. Answers the three questions #16 asks, and the two
things §5.2 turned out to owe that #16 does not name.

Port range. containerPort is an integer from 1 to 65535, structural.
A port below 1024 is a SHOULD NOT and not a rejection: whether the
container can bind one depends on a capability the runtime grants,
which this document cannot see, and a rule that rejects on a fact it
cannot check is guessing. structural-026 is what keeps that a SHOULD
NOT rather than drifting into a MUST.

Protocol x visibility. A PUBLIC endpoint is published at a URL by
something that speaks the HTTP family, so its protocol MUST be HTTP,
HTTPS, WS or GRPC and a TCP or UDP endpoint MUST be PRIVATE. §5.4 is
the second argument: a probe polls an HTTP path, so a PUBLIC TCP
endpoint would compel a readiness probe it has no way to express.

Multiple PUBLIC endpoints are permitted, and each publishes its own
URL. The consequence is a rule rather than a caveat — anything naming
a public address MUST name the endpoint it means — so platformDefault
becomes an object carrying `source` and `endpoint` rather than a bare
enum. That field had no prose at all before; §6.1 now has it. Note
this is not what #16 assumed: capping PUBLIC at one is not expressible
structurally, since JSON Schema has no keyword that counts mapping
members matching a condition.

Two things §5.2 owed and nothing had paid. "The primary endpoint" was
used by §5.4 and by ComponentProbe.endpoint and defined nowhere; it is
now the sole endpoint, failing that the sole PUBLIC one, failing that
nothing — an error rather than a sort-order tiebreak, because a
tiebreak lets a new endpoint named `api` silently re-point a probe that
already works. And endpoint names were bare mapping keys, so `web.api`
was legal despite being referenced by probes and platform defaults and
becoming a DNS label; they now take the slug grammar blueprint §4.1
quotes.

Three semantic codes, all fixture-covered: ERR_AMBIGUOUS_ENDPOINT,
ERR_ENDPOINT_NOT_PUBLIC, and ERR_UNKNOWN_ENDPOINT broadened past
probes. checkProbeEndpoints becomes checkEndpointReferences, which
walks probes and platform defaults through one resolver.

BREAKING CHANGE: bounding containerPort, restricting a PUBLIC
endpoint's protocol, constraining endpoint names, and reshaping
platformDefault each reject documents that validate today. No v1.0.0
has been published — every family reads 0.0.0 in the release-please
manifest and no tag exists — so §3's guarantee has no released version
to run from and this lands free. It does not once #1/#2/#3 merge.

Refs #16

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Justin Merrell <merrelljustin@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

1 participant