Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions GOVERNANCE.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,14 @@ Any change that would cause a previously valid document to fail validation is a
Adding a required field, narrowing an enum, tightening a pattern, and removing
a field are all breaking. Adding an optional field is not.

One narrow exception applies before a family's first release.
[ADR 0005](docs/adr/0005-platform-divergence-reconciliation.md) §1 sets it out:
while a family has no published version, requirements 2 and 3 do not apply,
because the compatibility guarantee is stated against a released version and
there is none to run from. Requirement 1 still applies, as does declaring the
change as breaking. The exception closes for a family the moment its first tag
is created.

## Changing a controlled vocabulary

A field whose value comes from a closed `enum` is a controlled vocabulary this
Expand Down
15 changes: 11 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -109,10 +109,17 @@ overwritten.

## Status

`v1` is **pre-stable**. The schemas here were seeded from the platform's
generated catalog schemas and are being brought up to specification quality.
The `$defs` names are now settled; the normative prose is not — see the `TODO`
sections in each `spec.md`, and the open issues.
`v1` is **pre-stable**, and nothing has been released — no tag exists and every
family reads `0.0.0`. The schemas here were seeded from the platform's
generated catalog schemas and have been brought up to specification quality:
the `$defs` names are settled, no `spec.md` carries a `TODO` section, and every
rule each one states is stated in prose with the schema implementing it.

What remains is recorded rather than outstanding. Each family's **Known debt**
section names its own gaps, and
[ADR 0005](docs/adr/0005-platform-divergence-reconciliation.md) §1 sets out the
window — open only until a family's first tag — in which a rule that rejects a
previously valid document can still be added without a new major.

## License

Expand Down
9 changes: 7 additions & 2 deletions conformance/blueprint/v1/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,9 +188,9 @@
"path": "semantic/010-well-formed-item"
},
{
"id": "semantic-011-three-node-cycle-reporting",
"id": "semantic-011-three-node-cycle",
"phase": "semantic",
"path": "semantic/011-three-node-cycle-reporting"
"path": "semantic/011-three-node-cycle"
},
{
"id": "semantic-012-connection-type-mismatch",
Expand Down Expand Up @@ -241,6 +241,11 @@
"id": "semantic-021-published-reference-suppresses-unbound",
"phase": "semantic",
"path": "semantic/021-published-reference-suppresses-unbound"
},
{
"id": "semantic-022-identical-input-redeclaration",
"phase": "semantic",
"path": "semantic/022-identical-input-redeclaration"
}
]
}
14 changes: 6 additions & 8 deletions conformance/blueprint/v1/semantic/002-connection-cycle/case.yaml
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
# Mutual discovery: `api` reads `web`'s address and `web` reads `api`'s. Every
# output here is a function of its own node, so a two-pass resolver would
# settle this without complaint — the graph is rejected because a
# specification that permits cycles obliges every implementation to be that
# resolver, and forecloses any later rule needing an order.
# Mutual discovery: `api` reads `web`'s address and `web` reads `api`'s. This
# is the composition §4.2 exists to permit, and the regression pin for it.
#
# The walk is reported from `api`, the lexicographically smallest node in the
# cycle, so that two implementations finding this cycle name it identically:
# api -> web -> api.
# Every output here is a function of its own node — component §6.2 — so both
# are resolvable before either edge is bound, and the cycle costs an
# implementation nothing. Reintroducing an acyclicity rule rejects this
# document, which is why doing so after publication is a major version.
specVersion: v1
kind: BLUEPRINT
metadata:
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"id": "semantic-002-connection-cycle",
"phase": "semantic",
"expected": "fail",
"expected": "pass",
"clause": "specifications/blueprint/v1/spec.md#connections",
"summary": "A cyclic connection graph is rejected, and the walk is reported from its smallest node."
"summary": "A cyclic connection graph validates: mutual service discovery is expressible."
}

This file was deleted.

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
# A three-node cycle written in an order that is not its reporting order. The
# nodes appear as queue, db, cache and the walk is required to begin at `cache`,
# the lexicographically smallest node in the cycle.
# A three-node cycle: queue -> db -> cache -> queue.
#
# This is the case semantic-002 cannot make: with two nodes, "smallest first"
# and "first declared" can coincide by luck. Here they do not, so an
# implementation that reports from wherever its traversal happened to start
# produces `queue -> db -> cache -> queue` and fails — which is the point.
# The node names in a diagnostic are comparable across implementations only
# because this rule fixes them.
# This is the case semantic-002 cannot make. A two-node cycle is legible on
# sight, and an implementation could conceivably permit one by accident. Three
# nodes, declared in an order that is not the order the edges run in, is a
# cycle only a real traversal finds — so this pins that §4.2 permits cycles as
# such rather than tolerating the smallest of them.
specVersion: v1
kind: BLUEPRINT
metadata:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"id": "semantic-011-three-node-cycle",
"phase": "semantic",
"expected": "pass",
"clause": "specifications/blueprint/v1/spec.md#connections",
"summary": "A cycle longer than two nodes validates, and no traversal order is imposed on it."
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"id": "semantic-022-identical-input-redeclaration",
"phase": "semantic",
"expected": "pass",
"clause": "specifications/blueprint/v1/spec.md#merge",
"summary": "An identical redeclaration is absorbed: ui, isRequired, key order and written-out defaults are not conflicts.",
"document": "acme-wiki/blueprint.yaml"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# The other half of §5.2. semantic-009 pins that a *differing* redeclaration is
# rejected; this pins that an identical one is absorbed in silence, and that the
# three things §5.2 says are not compared are in fact not compared.
#
# `api` and `db` both declare `adminPassword`. They differ in `ui` (a different
# label), in `isRequired`, in the order the schema's keys are written, and in
# whether the schema's defaults are written out or left implicit. None of that
# is a conflict: the two schemas are equal once defaults are applied, which is
# the test §5.2 states.
specVersion: v1
kind: BLUEPRINT
metadata:
slug: acme-wiki
version: 1
spec:
components:
api:
component: ./components/api.yaml
size: general.standard.small
connections: {}
db:
component: ./components/postgres.yaml
size: general.standard.small
connections: {}
parameters: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# `api` sorts first, so this is the declaration that stands. It leaves every
# schema default implicit.
specVersion: v1
kind: COMPONENT
metadata:
version: 1
spec:
workload:
kind: SERVICE
source:
type: IMAGE
ref: postgres:17.10-alpine
endpoints:
primary:
containerPort: 5432
protocol: TCP
visibility: PRIVATE
contract:
inputs:
adminPassword:
schema:
type: STRING
isSensitive: true
suppliedBy: USER
isRequired: true
ui:
label: Administrator password
outputs: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# The same declaration, written differently. `isSensitive` comes first here and
# `semanticType` is written out at the value it defaults to, so a byte-for-byte
# or key-order-sensitive comparison of the two schema blocks calls these
# different. §5.2's test is equality once defaults are applied, and by that test
# they are the same declaration.
#
# `ui` and `isRequired` both differ, and §5.2 excludes both: they describe how a
# value is asked for, not what it is.
specVersion: v1
kind: COMPONENT
metadata:
version: 1
spec:
workload:
kind: SERVICE
source:
type: IMAGE
ref: postgres:17.10-alpine
endpoints:
primary:
containerPort: 5432
protocol: TCP
visibility: PRIVATE
contract:
inputs:
adminPassword:
schema:
isSensitive: true
semanticType: null
type: STRING
suppliedBy: USER
isRequired: false
ui:
label: Database superuser password
outputs: {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
specVersion: v1
kind: LISTING
metadata:
slug: acme-wiki
version: 1
spec:
listingKind: BLUEPRINT
displayName: Acme Wiki
summary: A wiki backed by PostgreSQL
category: PRODUCTIVITY
lifecycleStage: STABLE
35 changes: 35 additions & 0 deletions conformance/component/v1/cases.json
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,31 @@
"phase": "structural",
"path": "structural/032-env-var-key-not-uppercase"
},
{
"id": "structural-033-service-without-endpoints",
"phase": "structural",
"path": "structural/033-service-without-endpoints"
},
{
"id": "structural-034-service-with-empty-endpoints",
"phase": "structural",
"path": "structural/034-service-with-empty-endpoints"
},
{
"id": "structural-035-public-address-default",
"phase": "structural",
"path": "structural/035-public-address-default"
},
{
"id": "structural-036-public-port-on-udp-endpoint",
"phase": "structural",
"path": "structural/036-public-port-on-udp-endpoint"
},
{
"id": "structural-037-public-hostname-default",
"phase": "structural",
"path": "structural/037-public-hostname-default"
},
{
"id": "semantic-001-floating-tag-image-reference",
"phase": "semantic",
Expand Down Expand Up @@ -236,6 +261,16 @@
"id": "semantic-011-env-key-claimed-twice",
"phase": "semantic",
"path": "semantic/011-env-key-claimed-twice"
},
{
"id": "semantic-012-public-address-on-http-endpoint",
"phase": "semantic",
"path": "semantic/012-public-address-on-http-endpoint"
},
{
"id": "semantic-013-platform-default-elects-primary",
"phase": "semantic",
"path": "semantic/013-platform-default-elects-primary"
}
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,3 +11,8 @@ spec:
source:
type: IMAGE
ref: ghcr.io/musher-dev/api:nightly
endpoints:
api:
containerPort: 8080
protocol: HTTP
visibility: PRIVATE
Original file line number Diff line number Diff line change
@@ -1,14 +1,18 @@
# Section 5 permits a SERVICE to declare no endpoint. A probe on one polls a
# port that does not exist: the election finds no primary here for the
# opposite reason it finds none among two PUBLIC endpoints, and rejects it
# just the same.
# A probe on a workload that declares no endpoint polls a port that does not
# exist: the election finds no primary here for the opposite reason it finds
# none among two PUBLIC endpoints, and rejects it just the same.
#
# The kind is WORKER because §5 now puts this shape beyond a SERVICE, which
# must declare at least one endpoint. The other three kinds declare none and
# may still carry a probe, so the case survives there — which is the whole
# reason §5.2 still has to say what happens when nothing is elected.
specVersion: v1
kind: COMPONENT
metadata:
version: 1
spec:
workload:
kind: SERVICE
kind: WORKER
source:
type: IMAGE
ref: nginx:1.29.4-alpine
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,11 @@ spec:
source:
type: IMAGE
ref: nginx:1.29.4-alpine
endpoints:
web:
containerPort: 8080
protocol: HTTP
visibility: PRIVATE
envVars:
- key: LOG_LEVEL
value:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,11 @@ spec:
source:
type: IMAGE
ref: postgres:17.10-alpine
endpoints:
db:
containerPort: 5432
protocol: TCP
visibility: PRIVATE
envVars:
- key: DATABASE_URL
value:
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# The mirror of semantic-009. PUBLIC_ADDRESS derives the host:port a TCP or UDP
# endpoint publishes on an allocated edge port; an HTTP-family endpoint is
# published through the shared ingress as a URL, so there is no such port to
# name.
#
# The endpoint is declared and PUBLIC and still cannot answer, which is the
# same shape semantic-009 has and the reason the two codes name the axis that
# failed rather than sharing one.
specVersion: v1
kind: COMPONENT
metadata:
version: 1
spec:
workload:
kind: SERVICE
source:
type: IMAGE
ref: nginx:1.29.4-alpine
endpoints:
api:
containerPort: 8080
protocol: HTTP
visibility: PUBLIC
health:
readiness:
path: /healthz
endpoint: api
contract:
inputs:
apiAddress:
schema:
type: STRING
ui:
label: API address
platformDefault:
source: PUBLIC_ADDRESS
endpoint: api
Loading