`: anything else, namespaced by whoever defines it. Prose goes in `reason`, never here, so two implementations can compare refusals by code.",
+ "description": "A refusal reason in one of three reserved forms (SPEC §5.2). `R`: the specification's own conditions, R1 to R6 today; R7 and above are reserved for the specification to assign, and an issuer MUST NOT mint one, although the pattern admits R7 to R99. `runtime_error:`: the issuer could not decide (no signing key, parent unknown, schema invalid), named by the issuer. `vendor::`: anything else, namespaced by whoever defines it. Prose goes in `reason`, never here, so two implementations can compare refusals by code.",
"anyOf": [
{
"pattern": "^R[1-9][0-9]?$"
diff --git a/conformance/suite/draft7/plugin-manifest.json b/conformance/suite/draft7/plugin-manifest.json
index 7490d0f..3daf90d 100644
--- a/conformance/suite/draft7/plugin-manifest.json
+++ b/conformance/suite/draft7/plugin-manifest.json
@@ -1,6 +1,6 @@
[
{
- "description": "CDF Plugin Manifest (plugin-manifest.schema.json, @cognitive-delivery/contract 1.2.1): fixtures, part 1 of 2",
+ "description": "CDF Plugin Manifest (plugin-manifest.schema.json, exported from a tree at @cognitive-delivery/contract 1.2.1; on main its descriptions may be ahead of that tag): fixtures, part 1 of 2",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://cognitive-delivery.github.io/contract/1.x/plugin-manifest.schema.json",
@@ -88,7 +88,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -166,7 +166,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -902,7 +902,7 @@
},
"credentialFree": {
"type": "string",
- "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses — a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT — each as an I-Regexp without lookahead or word boundaries. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
+ "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses (a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT), each a pattern without lookahead or word boundaries, which RE2 compiles. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
"allOf": [
{
"not": {
@@ -3033,127 +3033,11 @@
}
},
"valid": false
- },
- {
- "description": "plugin-manifest.mcp-remote-without-url is refused",
- "data": {
- "name": "cdf-data-checks",
- "displayName": "CDF Data Checks",
- "version": "1.2.0",
- "description": "Native data-quality checks, a tracker strategy and a model provider, contributed to the harness as data.",
- "author": {
- "name": "Cognitive Delivery",
- "email": "contact@example.com",
- "url": "https://example.com"
- },
- "homepage": "https://example.com/plugins/cdf-data-checks",
- "repository": "https://example.com/plugins/cdf-data-checks.git",
- "license": "PolyForm-Noncommercial-1.0.0",
- "keywords": [
- "cdf",
- "data",
- "quality"
- ],
- "category": "Productivity",
- "metadata": {
- "supportChannel": "https://example.com/support"
- },
- "defaultEnabled": false,
- "skills": "./skills/",
- "commands": [
- "./commands/",
- "./extra-commands/"
- ],
- "agents": "./agents/",
- "hooks": "./hooks/hooks.json",
- "mcpServers": {
- "remote": {
- "type": "http",
- "headersHelper": "./auth.sh"
- }
- },
- "lspServers": {
- "example-lsp": {
- "command": "example-lsp",
- "args": [
- "--stdio"
- ],
- "extensionToLanguage": {
- ".example": "example"
- }
- }
- },
- "cdf": {
- "schemaVersion": "1.0",
- "contributes": {
- "providers": [
- {
- "id": "example-inference",
- "kind": "openai-compatible",
- "baseUrl": "https://api.example.com/v1",
- "model": "example-medium",
- "prices": {
- "input_per_million": 0.5,
- "output_per_million": 1.5
- },
- "extraHosts": [
- "cdn.example.com"
- ]
- }
- ],
- "trackers": [
- {
- "id": "examplecorp",
- "label": "ExampleCorp Work Items",
- "readAction": "examplecorp.readItemStatus",
- "commentAction": "examplecorp.commentCdfStatus",
- "transitionAction": "examplecorp.transitionItem",
- "createAction": "examplecorp.createItemForSpec",
- "externalIdLabel": "item id",
- "externalIdPlaceholder": "e.g. 1234"
- }
- ],
- "docPacks": [
- "./docs/data-quality-pack.json"
- ],
- "checks": [
- {
- "id": "null-rate",
- "worker": "./workers/null-rate.mjs"
- }
- ]
- },
- "capabilities": {
- "tools": [
- "cdf_lint_artefacts"
- ],
- "read_paths": [
- "models/**",
- ".cdf/specs/**"
- ],
- "write_paths": [
- ".cdf/checks/**"
- ],
- "hosts": [
- "api.example.com"
- ],
- "commands": [
- "node"
- ]
- },
- "attestation": {
- "issuer": "plugin",
- "signature": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
- "key_fingerprint": "ab12cd34ef56"
- }
- }
- },
- "valid": false
}
]
},
{
- "description": "CDF Plugin Manifest (plugin-manifest.schema.json, @cognitive-delivery/contract 1.2.1): fixtures, part 2 of 2",
+ "description": "CDF Plugin Manifest (plugin-manifest.schema.json, exported from a tree at @cognitive-delivery/contract 1.2.1; on main its descriptions may be ahead of that tag): fixtures, part 2 of 2",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://cognitive-delivery.github.io/contract/1.x/plugin-manifest.schema.json",
@@ -3241,7 +3125,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -3319,7 +3203,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -4055,7 +3939,7 @@
},
"credentialFree": {
"type": "string",
- "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses — a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT — each as an I-Regexp without lookahead or word boundaries. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
+ "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses (a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT), each a pattern without lookahead or word boundaries, which RE2 compiles. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
"allOf": [
{
"not": {
@@ -4855,6 +4739,122 @@
"additionalProperties": true
},
"tests": [
+ {
+ "description": "plugin-manifest.mcp-remote-without-url is refused",
+ "data": {
+ "name": "cdf-data-checks",
+ "displayName": "CDF Data Checks",
+ "version": "1.2.0",
+ "description": "Native data-quality checks, a tracker strategy and a model provider, contributed to the harness as data.",
+ "author": {
+ "name": "Cognitive Delivery",
+ "email": "contact@example.com",
+ "url": "https://example.com"
+ },
+ "homepage": "https://example.com/plugins/cdf-data-checks",
+ "repository": "https://example.com/plugins/cdf-data-checks.git",
+ "license": "PolyForm-Noncommercial-1.0.0",
+ "keywords": [
+ "cdf",
+ "data",
+ "quality"
+ ],
+ "category": "Productivity",
+ "metadata": {
+ "supportChannel": "https://example.com/support"
+ },
+ "defaultEnabled": false,
+ "skills": "./skills/",
+ "commands": [
+ "./commands/",
+ "./extra-commands/"
+ ],
+ "agents": "./agents/",
+ "hooks": "./hooks/hooks.json",
+ "mcpServers": {
+ "remote": {
+ "type": "http",
+ "headersHelper": "./auth.sh"
+ }
+ },
+ "lspServers": {
+ "example-lsp": {
+ "command": "example-lsp",
+ "args": [
+ "--stdio"
+ ],
+ "extensionToLanguage": {
+ ".example": "example"
+ }
+ }
+ },
+ "cdf": {
+ "schemaVersion": "1.0",
+ "contributes": {
+ "providers": [
+ {
+ "id": "example-inference",
+ "kind": "openai-compatible",
+ "baseUrl": "https://api.example.com/v1",
+ "model": "example-medium",
+ "prices": {
+ "input_per_million": 0.5,
+ "output_per_million": 1.5
+ },
+ "extraHosts": [
+ "cdn.example.com"
+ ]
+ }
+ ],
+ "trackers": [
+ {
+ "id": "examplecorp",
+ "label": "ExampleCorp Work Items",
+ "readAction": "examplecorp.readItemStatus",
+ "commentAction": "examplecorp.commentCdfStatus",
+ "transitionAction": "examplecorp.transitionItem",
+ "createAction": "examplecorp.createItemForSpec",
+ "externalIdLabel": "item id",
+ "externalIdPlaceholder": "e.g. 1234"
+ }
+ ],
+ "docPacks": [
+ "./docs/data-quality-pack.json"
+ ],
+ "checks": [
+ {
+ "id": "null-rate",
+ "worker": "./workers/null-rate.mjs"
+ }
+ ]
+ },
+ "capabilities": {
+ "tools": [
+ "cdf_lint_artefacts"
+ ],
+ "read_paths": [
+ "models/**",
+ ".cdf/specs/**"
+ ],
+ "write_paths": [
+ ".cdf/checks/**"
+ ],
+ "hosts": [
+ "api.example.com"
+ ],
+ "commands": [
+ "node"
+ ]
+ },
+ "attestation": {
+ "issuer": "plugin",
+ "signature": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
+ "key_fingerprint": "ab12cd34ef56"
+ }
+ }
+ },
+ "valid": false
+ },
{
"description": "plugin-manifest.mcp-stdio-without-command is refused",
"data": {
diff --git a/conformance/suite/draft7/plugin-marketplace.json b/conformance/suite/draft7/plugin-marketplace.json
index 0b70d92..afbcdda 100644
--- a/conformance/suite/draft7/plugin-marketplace.json
+++ b/conformance/suite/draft7/plugin-marketplace.json
@@ -1,6 +1,6 @@
[
{
- "description": "CDF Plugin Marketplace (plugin-marketplace.schema.json, @cognitive-delivery/contract 1.2.1): every fixture of the conformance corpus",
+ "description": "CDF Plugin Marketplace (plugin-marketplace.schema.json, exported from a tree at @cognitive-delivery/contract 1.2.1; on main its descriptions may be ahead of that tag): every fixture of the conformance corpus",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://cognitive-delivery.github.io/contract/1.x/plugin-marketplace.schema.json",
@@ -55,7 +55,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -133,7 +133,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -1182,7 +1182,7 @@
},
"credentialFree": {
"type": "string",
- "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses — a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT — each as an I-Regexp without lookahead or word boundaries. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
+ "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses (a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT), each a pattern without lookahead or word boundaries, which RE2 compiles. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
"allOf": [
{
"not": {
diff --git a/conformance/suite/draft7/provenance.json b/conformance/suite/draft7/provenance.json
index ac8bc65..e4c42b2 100644
--- a/conformance/suite/draft7/provenance.json
+++ b/conformance/suite/draft7/provenance.json
@@ -1,6 +1,6 @@
[
{
- "description": "CDF provenance (provenance.schema.json, @cognitive-delivery/contract 1.2.1): every fixture of the conformance corpus",
+ "description": "CDF provenance (provenance.schema.json, exported from a tree at @cognitive-delivery/contract 1.2.1; on main its descriptions may be ahead of that tag): every fixture of the conformance corpus",
"schema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"$id": "https://cognitive-delivery.github.io/contract/1.x/provenance.schema.json",
diff --git a/docs/conformance/cdf-harness.md b/docs/conformance/cdf-harness.md
index 4470355..1310486 100644
--- a/docs/conformance/cdf-harness.md
+++ b/docs/conformance/cdf-harness.md
@@ -46,8 +46,9 @@ the adapter reaches no editor code.
## Real artefacts
-`test/unit/contractRealArtefacts.test.ts` validates the harness's own journals, and a zero count
-fails. At the pin, 2026-10-06: 33,665 audit events, 19,287 CDI signals, 4,588 provenance records,
+`test/unit/contractRealArtefacts.test.ts` validates the harness's own journals. A zero count fails
+for the audit events, CDI signals, provenance records and assessments; an empty or absent lease
+journal passes, because a workspace that has never issued a lease has none. At the pin, 2026-10-06: 33,665 audit events, 19,287 CDI signals, 4,588 provenance records,
2 assessments, 6 lease records and 1 config; zero rejected.
## Narrowing vectors
diff --git a/docs/decisions.md b/docs/decisions.md
index 918602e..49564c3 100644
--- a/docs/decisions.md
+++ b/docs/decisions.md
@@ -3,16 +3,17 @@
The contract is Apache-2.0 and lives in `Cognitive-Delivery/contract`. The decisions that shaped it
were taken in the reference implementation's repository, `Cognitive-Delivery/cdf-harness` (PolyForm
Noncommercial 1.0.0), as decision records under `docs/governance/decisions/`, and the three reviews
-that drove 1.1 and 1.2 are Claude documents. A reader here cannot follow a CHANGELOG entry back to
+that drove 1.1 and 1.2 are the maintainer's private working documents, which a reader here cannot
+open; this page summarises what each found instead of linking to it. A reader here cannot follow a CHANGELOG entry back to
its decision without crossing that licence boundary, so this page is the bridge: every release from
1.0.0 to 1.2.1, the records and reviews behind it, and one line on what each decided. Proposals
accepted or rejected since 1.2.0 are recorded in [`docs/proposals/`](proposals/).
## Releases
-| Release | Date | Tag commit | Shaped by | What was decided |
+| Release | Date | Tagged commit | Shaped by | What was decided |
|---|---|---|---|---|
-| 1.0.0, 1.0.1 and 1.0.2 | 2026-09-19 | 3ad8832, 3150336, e26142f | DR-146, DR-156 | The schema set extracted from the harness under Apache-2.0 with the additive rule and the lock (first); the SPEC and the canonical-bytes vectors (second); the lock's version checked after the second tarball shipped it stale (third) |
+| 1.0.0, 1.0.1 and 1.0.2 | 2026-09-19 | f575139, df0b1f8, 54c134b (annotated tags 3ad8832, 3150336, e26142f) | DR-146, DR-156 | The schema set extracted from the harness under Apache-2.0 with the additive rule and the lock (first); the SPEC and the canonical-bytes vectors (second); the lock's version checked after the second tarball shipped it stale (third) |
| 1.1.0 | 2026-10-05 | c04f53c | DR-182; the first review | Five of the first review's ten improvements: the guard sees tightenings, the allow-list, narrowing and signature vectors, `.expect.json` paths, RFC 8785 named, every `$id` served, the privacy properties enforced by shape |
| 1.2.0 | 2026-10-05 | 8cd038d | DR-183; the second review | All fourteen of the second review's improvements: identity rules, lease rules and `lease-record`, evidence hygiene, typed plugin components, `tool_args`, traceability, the suite under six validators, stability markers, governance written down, every release frozen at its URL |
| 1.2.1 | 2026-10-06 | 3088e44 | DR-183 (its closing tasks); the third review; DR-184 | Found by pinning the reference implementation to 1.2.0: the type generator, the Pages deploy on a tag, the narrowing vectors as the reference's own bytes, and `capabilities.tool_args`. The third review's findings are parked for batch three; DR-184 re-based every document on 1.2.1 |
@@ -23,14 +24,18 @@ accepted or rejected since 1.2.0 are recorded in [`docs/proposals/`](proposals/)
|---|---|---|
| DR-146, "The governance contract has a second home, and phases are not in it" | 2026-07-25 | The contract is extracted into its own repository under Apache-2.0; within a major, changes are additive only; lifecycle phases are an open string the contract does not define |
| DR-156, "The harness consumes the contract as a submodule, not from npm" | 2026-09-19 | The reference implementation pins the contract as a git submodule at `contract/` and runs the published runner against itself |
-| DR-182, "Contract fix batch one, and what the evidence changed" | 2026-10-04 | The five improvements taken from the first review into 1.1.0, and where real journals changed the design (its header still reads Proposed, owner review pending) |
+| DR-182, "Contract fix batch one, and what the evidence changed" | 2026-10-04 | The five improvements taken from the first review into 1.1.0, and where real journals changed the design (Approved) |
| DR-183, "Contract batch two, and where the evidence overruled the review" | 2026-10-05 | All fourteen of the second review's improvements into 1.2.0, with eight decisions D1 to D8 where the evidence overruled the review: among them D2 (IP literals admitted as hosts; no port form in 1.x), D5 (refusal reasons as codes), D6 (`tool_args` a declaration) and D7 (lease cross-field rules in the runner, not the schema) |
| DR-184, "Every document that describes the contract reflects 1.2.1" | 2026-10-06 | Every document is re-based on one fact sheet at 1.2.1, with a gate that fails on a stale statement; this page and the proposals directory come from it |
## The reviews
-| Review | Date | Document | Outcome |
+Each review is a private working document of the maintainer's; the summaries below are the record
+here.
+
+| Review | Date | What it found | Outcome |
|---|---|---|---|
-| First | 2026-10-04 | | Ten improvements; five taken in 1.1.0 (DR-182) |
-| Second | 2026-10-05 | | Fourteen improvements, all taken in 1.2.0 (DR-183) |
-| Third | 2026-10-06 | | Twenty-four findings, none pre-empted by the 1.2.1 fact sheet; parked for batch three (Linear COGDEV-271) |
+| First | 2026-10-04 | The design sound and four headline promises false: privacy enforced by shape, a reader in any language needing nothing else, a strict superset of Claude Code's plugin format, and the Index computed from the closed signal vocabulary; also path forms the SPEC refuses accepted by the schema, no narrowing or signature vectors, and an additive guard blind to a tightened pattern | Ten improvements; five taken in 1.1.0 (DR-182) |
+| Second | 2026-10-05 | At `main` e91afb0: the published package unable to run its own test, the lease vocabulary under-specified, the decision side of the lease unspecified, and the repository short of the practices an implementing organisation looks for first | Fourteen improvements, all taken in 1.2.0 (DR-183) |
+| Third | 2026-10-06 | At 1.2.1: further keywords the lock does not record, pattern semantics that differ between regular-expression engines, and statements in the documents to make precise | Twenty-four findings, none pre-empted by the 1.2.1 fact sheet; parked for batch three (Linear COGDEV-271) |
+| Fourth | 2026-10-06 | Twenty-two statements the artefacts contradicted, a font licence naming the wrong copyright holder, the reference implementation signing different bytes from the ones the contract verifies, a lease journal any governed agent can append a revocation to, and attestations re-signed without verification | Six recommendations, all taken in batch three (DR-187); the statements and the licence corrected first |
diff --git a/docs/implementing.md b/docs/implementing.md
index af3009d..b797b61 100644
--- a/docs/implementing.md
+++ b/docs/implementing.md
@@ -79,10 +79,13 @@ field behind it and the absence that produces it.
| `rejection paths NOT CHECKED (adapter reports no errors)` | `errorPathsChecked: false` | `lastErrors` is not an array after a rejection |
An absent `roundTrip` prints nothing and has no field: the planted fixture is still validated, but
-nothing checks that the field survives. Two further lines from the installed package are about the
-tarball, not the adapter: `Additive guard: NOT PRESENT in this package` and `Layout: NOT CHECKED
-(published package; the repository check runs from a clone)`, because `check-additive.mjs` and the
-repository layout ship with the repository only.
+nothing checks that the field survives. Three further lines from the installed package are about
+the tarball, not the adapter: `Additive guard: NOT PRESENT in this package`, `Layout: NOT CHECKED
+(published package; the repository check runs from a clone)` and `Site: NOT CHECKED (tooling/ is not
+in the package)`, because `check-additive.mjs`, the repository layout and the site generator ship
+with the repository only. The claims line, `Claims: N checked, F failure(s).`, is printed from the
+package too, with each claim whose source is not in the package (CONTRIBUTING, `docs/`, the
+allow-list, the git history) listed as NOT CHECKED rather than passed.
The reference adapter (`conformance/ajv-adapter.mjs`) offers every hook but `narrow`, so the
contract's own CI prints `behaviour NOT CHECKED` for narrowing; the reference implementation runs
diff --git a/docs/proposals/2026-10-05-identity-rules.md b/docs/proposals/2026-10-05-identity-rules.md
index b1146b0..b5f4780 100644
--- a/docs/proposals/2026-10-05-identity-rules.md
+++ b/docs/proposals/2026-10-05-identity-rules.md
@@ -29,8 +29,8 @@ of at most 253 characters, which admits IPv4 literals and `localhost`; a command
`^[A-Za-z0-9][A-Za-z0-9._+-]{0,127}$`; a tool or approval is `^[A-Za-z0-9_][A-Za-z0-9_.:/-]{0,127}$`,
never `*`. Applied to `allow` and `deny`, to the inlined copies in `agent-lease` and `lease-record`,
and to the plugin schemas' `capabilities`. `agent.name` is capped at 120 and described as never a
-person's name; `intent.purpose` at 500. Every pattern is in the I-Regexp subset and compiles under
-RE2 in CI.
+person's name; `intent.purpose` at 500. Every pattern is an ECMA-262 regular expression without
+lookaround or backreferences, applied as an unanchored search, and compiles under RE2 in CI.
## Backward compatibility
diff --git a/docs/proposals/2026-10-05-tool-args-declaration.md b/docs/proposals/2026-10-05-tool-args-declaration.md
index 6c4e2e2..ce6a725 100644
--- a/docs/proposals/2026-10-05-tool-args-declaration.md
+++ b/docs/proposals/2026-10-05-tool-args-declaration.md
@@ -54,5 +54,6 @@ nowhere to live and a later rule has no history. A custom keyword for stability:
Accepted, 2026-10-05 (DR-183 D6). Shipped: SPEC §4.4, §11 and §13; the manifest schema and its
inlined copies; the vector; the two fixtures; `conformance/metaschema.json`, which requires a
-stability comment on every declared property (666 at 1.2.1). At its 1.2.1 pin the harness's
+stability comment on every declared property (at 1.2.1, 666 `properties` keys carry one: the 651
+declared properties the site reference lists and 15 keys inside `if` and `contains` conditions). At its 1.2.1 pin the harness's
`CapabilityLists` excludes `tool_args`: a declaration, never a grant.
diff --git a/docs/proposals/TEMPLATE.md b/docs/proposals/TEMPLATE.md
index 3c99b45..d12cf02 100644
--- a/docs/proposals/TEMPLATE.md
+++ b/docs/proposals/TEMPLATE.md
@@ -27,8 +27,9 @@ would be nice.
The shape, rule or check, concretely: a field with its type and constraints; a rule with its
code and the fixture that would refuse it; a vector with the grant or refusal it must produce.
-Patterns are RFC 9485 I-Regexp without lookahead, and must read the same under Python's `re`, RE2
-and ECMAScript (the corpus runs under six validators).
+Patterns are ECMA-262 regular expressions in the subset that compiles under RE2 (no lookaround, no
+backreferences), applied as an unanchored search per JSON Schema, and must read the same under
+Python's `re`, RE2 and ECMAScript (the corpus runs under six validators).
## Backward compatibility
diff --git a/docs/upgrading-1.0-to-1.2.md b/docs/upgrading-1.0-to-1.2.md
index 9e32cc7..931085a 100644
--- a/docs/upgrading-1.0-to-1.2.md
+++ b/docs/upgrading-1.0-to-1.2.md
@@ -2,11 +2,18 @@
For an implementation built against schema set 1.0 (contract 1.0.0, 1.0.1 and 1.0.2, all of
2026-09-19). Every change from 1.0.2 to 1.2.1 is additive within 1.x by the contract's own rule, and
-mechanically checked: a 1.0 reader keeps reading every artefact a 1.2 writer produces. What did
-change is the set of values the schemas refuse. Each tightening is allow-listed in
-`compat-allowlist.json` with the fixture or recorded check proving no conformant writer produced
-what it refuses, and the reference deployment's journals validate under 1.2 with zero rejections.
-Each item below cites its CHANGELOG entry.
+mechanically checked in one direction: a 1.2 reader reads every artefact a conformant 1.0 writer
+produced. The other direction does not hold. A 1.0 reader validating with the 1.0.2 schemas may
+reject what a 1.2 writer produces: a `lease-record` line (no 1.0 schema describes it), a
+`schema_version` of `"1.2"` on the lease schemas (1.0 required the literal `1.0`), and plugin forms
+1.0 did not admit, such as `commands` as an object map in a plugin manifest (1.0 typed it as a path
+or a list of paths) or a marketplace source form 1.0 did not list. The corpus shows it: the 1.0.2
+schemas reject the valid fixtures `plugin-manifest.claude-code-reference` (at `/commands`) and
+`plugin-marketplace.populated` (at `/plugins/3/source`). Beyond those additions, what changed is
+the set of values the schemas refuse. Each tightening is allow-listed in `compat-allowlist.json`
+with the fixture or recorded check proving no conformant writer produced what it refuses, and the
+reference deployment's journals validate under 1.2 with zero rejections. Each item below cites its
+CHANGELOG entry.
## Part A: what the schemas now refuse
@@ -21,10 +28,10 @@ Each item below cites its CHANGELOG entry.
| `agent.name` over 120 characters; `intent.purpose` over 500 | the manifest and its inlined copies | | same |
| A lease timestamp in any form but UTC with milliseconds and `Z` | `agent-lease` `issued_at` and `expires_at` | `^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$` | 1.2.0 "Lease rules L1 and L2, and one timestamp form inside the signed bytes" |
| `budget.depth` above 16; `budget.fan_out` above 256; any integer above 2^53 - 1 | the manifest and its inlined copies; every integer field | | 1.2.0, the same entry; 1.1.0 "Canonical bytes are declared to be RFC 8785" for the 2^53 bound |
-| A `schema_version` that is not `major.minor` | every schema; the lease schemas widen from the literal `1.0` | `^\d+\.\d+$` | 1.2.0 "Evidence hygiene" |
+| A `schema_version` that is not `major.minor` | the seven schemas that carry it (all but `config-core` and the two plugin schemas); the lease schemas widen from the literal `1.0` | `^\d+\.\d+$` | 1.2.0 "Evidence hygiene" |
| A control character in `summary` or `reasoning` | `audit-event` | | same |
| A `details` string value over 200 characters; `summary` over 300; `reasoning` over 500 | `audit-event` | | 1.2.0 "Evidence hygiene" for the value cap; 1.1.0 "The privacy properties are enforced by shape, not prose" for the two caps |
-| A `details` key with a sensitive segment | `audit-event.details`, `cdi-signal.details` | by `propertyNames`: a key is refused when any segment, split on non-alphanumerics and camelCase boundaries, is `authorization`, `content`, `file`, `password`, `path`, `payload`, `prompt`, `request`, `secret` or `token` | 1.1.0 "The privacy properties are enforced by shape, not prose" |
+| A `details` key with a sensitive segment | `audit-event.details`, `cdi-signal.details` | by `propertyNames`, approximating the writer's rule: a key is refused when a lower-case segment (split on non-alphanumerics) or a camelCase segment is `authorization`, `content`, `file`, `password`, `path`, `payload`, `prompt`, `request`, `secret` or `token` | 1.1.0 "The privacy properties are enforced by shape, not prose" |
| An `event_type` that is not two or more lower-case dotted segments, or over 120 characters | `audit-event` | `^[a-z][a-z0-9_]*(\.[a-z][a-z0-9_]*)+$`; the reference writer's first segments are reserved and any other writer leads with its vendor name (SPEC §6.2) | 1.2.0 "Evidence hygiene" |
| A `spec` that is not a slug of at most 80 characters; an assessment without exactly six integer-scored dimensions; a `sealed` path that is not dotted lower-case | `provenance`, `cdi-assessment`, `config-core` | | same |
| A plugin name outside Claude Code's rule; a `command` source `mode` other than `copy` or `link`, or a `timeout` outside 1 to 600; a credential-shaped value in a hook header, an MCP `env` or MCP `headers`; an `authorization` key in a marketplace entry's `headers`; a provider at plain `http://` off loopback | `plugin-manifest`, `plugin-marketplace` | | 1.1.0 "Every key the Claude Code plugin and marketplace references document"; 1.2.0 "Inline plugin hooks and MCP servers are typed, and a manifest cannot carry a credential" |
@@ -46,7 +53,7 @@ Each item below cites its CHANGELOG entry.
`gemini-code-assist`, `github-copilot`, `cdf-native`, `unknown`), beside `actor.runtime`, which
stays an open label. Optional in the schema; a 1.2 writer records it. CHANGELOG 1.2.0 "Evidence
hygiene".
-- **`schema_version` as `major.minor`** everywhere, compared on the major. The same entry.
+- **`schema_version` as `major.minor`** wherever it is carried, compared on the major. The same entry.
- **One timestamp form** in `issued_at` and `expires_at`, and L1 and L2 held before signing.
CHANGELOG 1.2.0 "Lease rules L1 and L2, and one timestamp form inside the signed bytes".
- **`allow.tool_args`** may be declared; an issuer may omit it from the grant and must not treat it
@@ -69,6 +76,6 @@ Each item below cites its CHANGELOG entry.
declared to be RFC 8785".
- **The closed vocabularies**: `cdi-signal.event_type` and the six dimension ids. `phase` stays open.
- **`$id`.** The 1.0 identifiers never resolved; since 1.1.0 each is
- `https://cognitive-delivery.github.io/contract/1.x/`, and every release from 1.1.0 is also
- frozen at `//`. A reader that resolved by `$id` lost nothing it had. CHANGELOG 1.1.0
+ `https://cognitive-delivery.github.io/contract/1.x/`, and every `v1.*` tag, 1.0.0 included,
+ is also frozen at `//`. A reader that resolved by `$id` lost nothing it had. CHANGELOG 1.1.0
"Every `$id` resolves".
diff --git a/generate-schemas-lock.mjs b/generate-schemas-lock.mjs
index fd7b1ef..1417977 100644
--- a/generate-schemas-lock.mjs
+++ b/generate-schemas-lock.mjs
@@ -58,7 +58,7 @@ function compareJson(a, b) {
/**
* The patterns a value must NOT match, expressed as `allOf: [{ not: { pattern } }, …]`.
- * This is how a path rule is written without lookahead (RFC 9485 I-Regexp has none), so the
+ * This is how a path rule is written without lookahead (RE2 has none), so the
* lock has to see it as the constraint it is: adding one tightens, removing one loosens.
*/
function notPatternsOf(node) {
diff --git a/schemas/agent-lease-manifest.schema.json b/schemas/agent-lease-manifest.schema.json
index 548de7a..bb18efc 100644
--- a/schemas/agent-lease-manifest.schema.json
+++ b/schemas/agent-lease-manifest.schema.json
@@ -137,7 +137,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -215,7 +215,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -299,7 +299,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
}
diff --git a/schemas/agent-lease.schema.json b/schemas/agent-lease.schema.json
index 18df023..f08d2d4 100644
--- a/schemas/agent-lease.schema.json
+++ b/schemas/agent-lease.schema.json
@@ -161,7 +161,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -239,7 +239,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -324,7 +324,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
}
diff --git a/schemas/audit-event.schema.json b/schemas/audit-event.schema.json
index 05ccab7..da242cb 100644
--- a/schemas/audit-event.schema.json
+++ b/schemas/audit-event.schema.json
@@ -149,7 +149,7 @@
},
"details": {
"type": "object",
- "description": "Flat scalars only, and keys are filtered by name: a key whose segment is authorization, content, file, password, path, payload, prompt, request, secret or token is refused by the schema, mirroring the reference writer, which drops it before write. Do not defeat this by renaming a sensitive field.",
+ "description": "Flat scalars only, and keys are filtered by name: a key with a lower-case or camelCase segment that is authorization, content, file, password, path, payload, prompt, request, secret or token is refused by the schema. This approximates the reference writer's rule, which drops such a key before write; the schema is case-sensitive where the writer is not, and `propertyNames` lists the differences. Do not defeat this by renaming a sensitive field.",
"additionalProperties": {
"type": [
"string",
@@ -160,7 +160,7 @@
"description": "A flat scalar; a string value is at most 200 characters, the reference writer's own cap, because `details` reaches a permanent record."
},
"propertyNames": {
- "description": "Mirrors the reference writer's rule, which splits a key on non-alphanumerics and camelCase boundaries and drops it when any segment is one of ten words: authorization, content, file, password, path, payload, prompt, request, secret, token. The three clauses refuse the lower-case segment form (`api_token`, `file-path`), the camelCase interior form (`filePath`, `apiToken`) and the camelCase leading form (`tokenCount`). `estimated_files_touched` is legal: `files` is not `file`. No lookahead, so RE2 validators load it.",
+ "description": "Approximates the reference writer's rule, which splits a key at camelCase boundaries, lower-cases it, splits it on non-alphanumerics and drops it when any segment is one of ten words: authorization, content, file, password, path, payload, prompt, request, secret, token. The three clauses refuse the lower-case segment form (`api_token`, `file-path`), the camelCase interior form (`filePath`, `apiToken`) and the camelCase leading form (`tokenCount`). The differences: the schema is case-sensitive, so it admits `Authorization`, `API_TOKEN`, `Token`, `TokenCount`, `PATH` and `x-Token`, all of which the writer drops; and it ends a camelCase segment at a digit, so it refuses `apiToken2` and `myFile2`, which the writer keeps. `estimated_files_touched` is legal: `files` is not `file`. No lookahead, so RE2 validators load it.",
"allOf": [
{
"not": {
diff --git a/schemas/cdi-signal.schema.json b/schemas/cdi-signal.schema.json
index f4f5e4d..6ce8aa1 100644
--- a/schemas/cdi-signal.schema.json
+++ b/schemas/cdi-signal.schema.json
@@ -224,7 +224,7 @@
},
"details": {
"type": "object",
- "description": "Flat scalars only, and keys are filtered by name exactly as on the audit event: a key whose segment is authorization, content, file, password, path, payload, prompt, request, secret or token is refused. Must never contain secrets, personal data or file contents.",
+ "description": "Flat scalars only, and keys are filtered by name as on the audit event: a key with a lower-case or camelCase segment that is authorization, content, file, password, path, payload, prompt, request, secret or token is refused, which approximates the reference writer's rule (`propertyNames` lists the differences). Must never contain secrets, personal data or file contents.",
"additionalProperties": {
"type": [
"string",
@@ -233,7 +233,7 @@
]
},
"propertyNames": {
- "description": "Mirrors the reference writer's rule, which splits a key on non-alphanumerics and camelCase boundaries and drops it when any segment is one of ten words: authorization, content, file, password, path, payload, prompt, request, secret, token. The three clauses refuse the lower-case segment form (`api_token`, `file-path`), the camelCase interior form (`filePath`, `apiToken`) and the camelCase leading form (`tokenCount`). `estimated_files_touched` is legal: `files` is not `file`. No lookahead, so RE2 validators load it.",
+ "description": "Approximates the reference writer's rule, which splits a key at camelCase boundaries, lower-cases it, splits it on non-alphanumerics and drops it when any segment is one of ten words: authorization, content, file, password, path, payload, prompt, request, secret, token. The three clauses refuse the lower-case segment form (`api_token`, `file-path`), the camelCase interior form (`filePath`, `apiToken`) and the camelCase leading form (`tokenCount`). The differences: the schema is case-sensitive, so it admits `Authorization`, `API_TOKEN`, `Token`, `TokenCount`, `PATH` and `x-Token`, all of which the writer drops; and it ends a camelCase segment at a digit, so it refuses `apiToken2` and `myFile2`, which the writer keeps. `estimated_files_touched` is legal: `files` is not `file`. No lookahead, so RE2 validators load it.",
"allOf": [
{
"not": {
diff --git a/schemas/lease-record.schema.json b/schemas/lease-record.schema.json
index c18fcb7..e373c3e 100644
--- a/schemas/lease-record.schema.json
+++ b/schemas/lease-record.schema.json
@@ -161,7 +161,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -239,7 +239,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -324,7 +324,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
}
@@ -613,7 +613,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -691,7 +691,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the agent may reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -776,7 +776,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host refused outright. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
}
@@ -937,7 +937,7 @@
},
"reasonCode": {
"type": "string",
- "description": "A refusal reason in one of three reserved forms (SPEC §5.2). `R`: the specification's own conditions, R1 to R6 today; R7 and above are reserved for the specification to assign, so an issuer cannot mint one. `runtime_error:`: the issuer could not decide (no signing key, parent unknown, schema invalid), named by the issuer. `vendor::`: anything else, namespaced by whoever defines it. Prose goes in `reason`, never here, so two implementations can compare refusals by code.",
+ "description": "A refusal reason in one of three reserved forms (SPEC §5.2). `R`: the specification's own conditions, R1 to R6 today; R7 and above are reserved for the specification to assign, and an issuer MUST NOT mint one, although the pattern admits R7 to R99. `runtime_error:`: the issuer could not decide (no signing key, parent unknown, schema invalid), named by the issuer. `vendor::`: anything else, namespaced by whoever defines it. Prose goes in `reason`, never here, so two implementations can compare refusals by code.",
"anyOf": [
{
"pattern": "^R[1-9][0-9]?$"
diff --git a/schemas/plugin-manifest.schema.json b/schemas/plugin-manifest.schema.json
index a3038ef..5d141cb 100644
--- a/schemas/plugin-manifest.schema.json
+++ b/schemas/plugin-manifest.schema.json
@@ -85,7 +85,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -163,7 +163,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -899,7 +899,7 @@
},
"credentialFree": {
"type": "string",
- "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses — a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT — each as an I-Regexp without lookahead or word boundaries. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
+ "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses (a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT), each a pattern without lookahead or word boundaries, which RE2 compiles. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
"allOf": [
{
"not": {
diff --git a/schemas/plugin-marketplace.schema.json b/schemas/plugin-marketplace.schema.json
index 7d1dcac..b575eee 100644
--- a/schemas/plugin-marketplace.schema.json
+++ b/schemas/plugin-marketplace.schema.json
@@ -52,7 +52,7 @@
},
"read_paths": {
"type": "array",
- "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2 or I-Regexp validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
+ "description": "Workspace-relative POSIX globs the agent may read. Refused here, without lookahead so any RE2-based validator can load the rule: a leading `/`, any `..` segment, a leading `~`, a drive-letter prefix and any backslash. The registry re-checks the resolved path against the workspace root.",
"items": {
"type": "string",
"minLength": 1,
@@ -130,7 +130,7 @@
"minLength": 1,
"maxLength": 253,
"pattern": "^(\\*|(\\*\\.)?[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?(\\.[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?)*)$",
- "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which is RFC 9485 I-Regexp with no lookahead: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
+ "description": "A host the plugin asks to reach. A lower-case DNS name of one or more labels, an IPv4 literal or `localhost` (both are DNS-name shaped), with an optional single leading `*.` label, or the bare `*`. Refused by the pattern, which uses no lookahead or backreference and compiles under RE2: a scheme, a port, a path, whitespace, upper case, a trailing dot and a wildcard anywhere but the first label. A port is not a host: the egress proxy matches hostnames, and a rule nothing enforces is a claim. IP literals are admitted because a local model provider (Ollama, LM Studio) lives at 127.0.0.1 and the root policy derives its hosts from provider URLs."
},
"$comment": "stability: stable"
},
@@ -1179,7 +1179,7 @@
},
"credentialFree": {
"type": "string",
- "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses — a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT — each as an I-Regexp without lookahead or word boundaries. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
+ "description": "A string that is not a credential. Refuses the seven shapes the reference evidence sanitiser refuses (a private key block, an AWS access key id, a GitHub token, an OpenAI key, a Slack token, a literal bearer token and a JWT), each a pattern without lookahead or word boundaries, which RE2 compiles. A detector, not a guarantee: it catches these shapes and nothing else, and the documented route for a secret remains the host's own environment (`$VAR` interpolation) or `headersHelper`.",
"allOf": [
{
"not": {
diff --git a/tooling/build-site.mjs b/tooling/build-site.mjs
index 220494c..68a5997 100644
--- a/tooling/build-site.mjs
+++ b/tooling/build-site.mjs
@@ -454,7 +454,7 @@ async function frontPage(ctx) {
${ways.map(([label, path, blurb]) => `${label} ${blurb} `).join('\n')}
-- Current version
- ${esc(ctx.version)}
+- Current release
- ${esc(ctx.version)}
- Specification
- ${link(site('/spec/'), 'The Agent Lease Manifest')}: ${specInline(specHeader)}
- Licence
- ${esc(ctx.licence)}
- Repository
- ${link(REPO_URL, 'github.com/Cognitive-Delivery/contract')}
@@ -462,13 +462,13 @@ ${ways.map(([label, path, blurb]) => `
= 0;
+}
+
+/** A release's GitHub Release, or its tag when it has none, labelled as what it is. */
+function releaseLink(version, { long = false } = {}) {
+ return hasGithubRelease(version)
+ ? link(`${REPO_URL}/releases/tag/v${version}`, long ? `Release v${version}` : `release v${version}`)
+ : link(`${REPO_URL}/tree/v${version}`, long ? `Tag v${version} (no GitHub Release)` : `tag v${version} (no GitHub Release)`);
+}
+
+/** `/versions/`: every frozen directory, its GitHub Release or tag and its changelog section. */
function versionsPage(ctx) {
const rows = ctx.frozen.map((v) => `${esc(v.version)} `
+ `${link(site(`/${v.version}/`), `/${v.version}/`)} `
- + `${link(`${REPO_URL}/releases/tag/v${v.version}`, `v${v.version}`)} `
+ + `${releaseLink(v.version)} `
+ `${link(`${site('/changelog/')}#${v.version}`, `CHANGELOG ${v.version}`)} `);
const body = `Versions
-The current schema set is ${esc(ctx.version)}, served at each schema's $id under /${esc(ctx.major)}.x/ and listed on the ${link(site('/'), 'front page')}. Each release is also served frozen under its own directory, byte for byte as tagged and never rewritten.
-${rows.length === 0 ? 'No frozen copies were laid out in this build.
' : `\nVersion Frozen copy Release Changelog \n\n${rows.join('\n')}\n\n
`}
+The current release is ${esc(ctx.version)}. Each schema's $id under /${esc(ctx.major)}.x/ serves the schema as it is on main, listed on the ${link(site('/'), 'front page')}; main may carry description-only changes ahead of the next tag. Each release is also served frozen under its own directory, byte for byte as tagged and never rewritten. Releases from ${esc(FIRST_GITHUB_RELEASE)} have a GitHub Release; the 1.0.x releases were tagged before the release workflow created GitHub Releases, so they link to their tags.
+${rows.length === 0 ? 'No frozen copies were laid out in this build.
' : `\nVersion Frozen copy GitHub Release or tag Changelog \n\n${rows.join('\n')}\n\n
`}
`;
return { path: '/versions/', title: `Versions · ${SITE_NAME}`, body };
}
@@ -629,7 +648,7 @@ function versionPage(ctx, v) {
${files}
-${link(`${REPO_URL}/releases/tag/v${v.version}`, `Release v${v.version}`)} and its ${link(`${site('/changelog/')}#${v.version}`, 'changelog section')}.
+${releaseLink(v.version, { long: true })} and its ${link(`${site('/changelog/')}#${v.version}`, 'changelog section')}.
`;
return { path: `/${v.version}/`, title: `contract ${v.version} · ${SITE_NAME}`, body };
}
@@ -757,6 +776,15 @@ function selfTest() {
try { siteHref('docs/missing.md', 'README.md', ctx); } catch (error) { refused = error instanceof SiteError; }
check(refused, true, 'a link to a path not in the tree is refused');
check(pageFor(BRAND_NOTICE), '/brand-notice/', 'the brand notice has its page');
+
+ // A release before the first GitHub Release links to its tag and says so; later ones to the release.
+ check(hasGithubRelease('1.0.2'), false, '1.0.2 has no GitHub Release');
+ check(hasGithubRelease('1.1.0'), true, '1.1.0 has a GitHub Release');
+ check(hasGithubRelease('1.10.0'), true, 'versions compare numerically');
+ const versions = versionsPage({ version: '1.2.1', major: '1', frozen: [{ version: '1.0.2', files: [] }, { version: '1.2.1', files: [] }] }).body;
+ check(versions.includes(`tag v1.0.2 (no GitHub Release)`), true, 'a 1.0.x row links its tag, labelled as one');
+ check(versions.includes(`release v1.2.1`), true, 'a later row links its GitHub Release');
+ check(versions.includes('releases/tag/v1.0.2'), false, 'no 1.0.x row claims a GitHub Release');
console.log(`build-site self-test: ${count} assertions passed`);
}
diff --git a/tooling/regex-portability/main.go b/tooling/regex-portability/main.go
index 7559423..cf0057f 100644
--- a/tooling/regex-portability/main.go
+++ b/tooling/regex-portability/main.go
@@ -4,9 +4,11 @@
// load, and a schema set that carries one is not "usable from another language with
// nothing else", whatever the README says.
//
-// RE2 syntax is a superset of RFC 9485 I-Regexp, which is the portability target the
-// specification names, so passing here is necessary and not quite sufficient; it catches
-// the construct implementers actually hit.
+// The specification's portability target (SPEC section 4.4) is an ECMA-262 regular
+// expression in the subset that also compiles under RE2, applied as an unanchored search.
+// Passing here is necessary and not quite sufficient: it catches lookaround and
+// backreferences, the constructs implementers actually hit, but not a construct both
+// engines accept with different meanings.
//
// Usage, from this directory: go run . ../../schemas
package main
diff --git a/tooling/site-reference.mjs b/tooling/site-reference.mjs
index 9ffb655..32d9887 100644
--- a/tooling/site-reference.mjs
+++ b/tooling/site-reference.mjs
@@ -127,13 +127,16 @@ function refName(node) {
return node.$ref.slice('#/definitions/'.length);
}
-/** `enum` in full up to ten values; beyond that a count and three examples. */
+/**
+ * `enum` in full, always. Up to ten values inline; beyond that the count, then every value inside
+ * a `` element, which the browser opens without a script. An earlier version showed three
+ * examples past ten, which dropped a constraint the index page says is shown.
+ */
function enumText(values, what = 'one of') {
- const shown = values.length <= ENUM_IN_FULL ? values : values.slice(0, 3);
- const list = shown.map((v) => json(v)).join(', ');
+ const list = values.map((v) => json(v)).join(', ');
return values.length <= ENUM_IN_FULL
? `${what} ${list}`
- : `${what} ${values.length} values, e.g. ${list}`;
+ : `${what} ${values.length} values all ${values.length}
${list}`;
}
// ---------------------------------------------------------------------------------------------
@@ -644,7 +647,7 @@ function indexPage(walkedAll, { base, major, siteName, stabilityLegend }) {
});
const total = walkedAll.reduce((n, w) => n + w.propertyCount, 0);
const body = `Schema reference
-One page per schema of set ${esc(major)}.x, read from the schema files themselves: every declared property with its path, type, requirement, description, constraints and stability, and every definition a property refers to. The pages are generated from schemas/*.schema.json at every deploy, so they say what the schemas say and nothing else; the JSON each page links to is the file served at the schema's $id.
+One page per schema of set ${esc(major)}.x, read from the schema files themselves: every declared property with its path, type, requirement, description, constraints and stability, and every definition a property refers to. An enumeration longer than ten values is shown in full under a disclosure; inside the one-line summary of a union branch, a deeper union or allOf is counted rather than listed, and the schema's JSON, linked from each page, is authoritative. The pages are generated from schemas/*.schema.json at every deploy, so they say what the schemas say and nothing else; the JSON each page links to is the file served at the schema's $id.
Schema Title Description Properties Raw
@@ -837,9 +840,16 @@ export async function selfTestReference() {
const reasonCode = leaseRecord.definitions.find((d) => d.name === 'reasonCode');
includes(reasonCode.rows[0].constraints.join('\n'), 'any of: (1) pattern ^R[1-9][0-9]?$; (2) pattern', 'a pattern-only branch is summarised without a type');
- // Enum collapse past ten values: the hooks event map's key list.
+ // An enum past ten values is rendered in full inside : the hooks event map's key list.
const hooksMap = byName['plugin-manifest'].definitions.find((d) => d.name === 'hooksMap');
- includes(hooksMap.rows[0].constraints.join('\n'), 'keys one of 33 values, e.g.', 'an enum past ten values collapses to a count');
+ const hooksKeys = hooksMap.rows[0].constraints.join('\n');
+ includes(hooksKeys, 'keys one of 33 values all 33
', 'an enum past ten values is counted and disclosed');
+ const hookEvents = inputs.schemas.find((x) => x.file === 'plugin-manifest.schema.json').schema.definitions.hooksMap.propertyNames.enum;
+ check(hookEvents.length, 33, 'the hooks event map lists thirty-three events');
+ for (const event of hookEvents) includes(hooksKeys, json(event), `every hook event is shown, ${event} among them`);
+ const eventTypes = byName['cdi-signal'].rows.find((r) => r.path === 'event_type');
+ const eventTypeEnum = inputs.schemas.find((x) => x.file === 'cdi-signal.schema.json').schema.properties.event_type.enum;
+ for (const value of eventTypeEnum) includes(eventTypes.constraints.join('\n'), json(value), 'every cdi-signal event_type value is shown');
const dimensions = byName['cdi-assessment'].rows.find((r) => r.path === 'dimensions');
includes(dimensions.constraints.join('\n'), 'must contain an item: object, with id = "provenance-integrity", requires id', 'contains is rendered as a constraint');
diff --git a/tooling/site/BRAND-NOTICE.md b/tooling/site/BRAND-NOTICE.md
index 12010ec..e9de246 100644
--- a/tooling/site/BRAND-NOTICE.md
+++ b/tooling/site/BRAND-NOTICE.md
@@ -11,5 +11,14 @@ the font files: Source Sans 3 under [OFL-source-sans-3.txt](fonts/OFL-source-san
JetBrains Mono under [OFL-jetbrains-mono.txt](fonts/OFL-jetbrains-mono.txt). That licence permits
redistribution with its text alongside, and that is how they are carried here.
+Both texts are the upstream projects' own, copied on 6 October 2026: Source Sans 3's from
+`adobe-fonts/source-sans` (`LICENSE.md` on its `release` branch) and JetBrains Mono's from
+`JetBrains/JetBrainsMono` (`OFL.txt` on `master`). The Source Sans 3 text first carried here named
+"Google Inc." as the copyright holder; the font is Adobe's, with the Reserved Font Name "Source",
+and the upstream text says so. The JetBrains Mono text differed only in a duplicated copyright
+phrase and the older licence URL, and was replaced with the upstream text for exactness. A modified
+Source Sans 3 file may not carry the name "Source" (condition 3 of the licence); the site serves
+the files as obtained and does not modify them.
+
Everything else on this site is under the repository's Apache-2.0 licence: the schemas, the
fixtures, the conformance runner, the specification and the pages themselves.
diff --git a/tooling/site/fonts/OFL-jetbrains-mono.txt b/tooling/site/fonts/OFL-jetbrains-mono.txt
index 8f7ed67..5ceee00 100644
--- a/tooling/site/fonts/OFL-jetbrains-mono.txt
+++ b/tooling/site/fonts/OFL-jetbrains-mono.txt
@@ -1,8 +1,8 @@
-Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono) JetBrainsMono-Italic[wght].ttf: Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
+Copyright 2020 The JetBrains Mono Project Authors (https://github.com/JetBrains/JetBrainsMono)
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
+https://openfontlicense.org
-----------------------------------------------------------
@@ -18,7 +18,7 @@ with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
+fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
diff --git a/tooling/site/fonts/OFL-source-sans-3.txt b/tooling/site/fonts/OFL-source-sans-3.txt
index 046fc66..22c601b 100644
--- a/tooling/site/fonts/OFL-source-sans-3.txt
+++ b/tooling/site/fonts/OFL-source-sans-3.txt
@@ -1,8 +1,8 @@
-Google Inc.
+Copyright 2010-2024 Adobe (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
-This license is copied below, and is also available with a FAQ at:
-http://scripts.sil.org/OFL
+
+This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
@@ -18,7 +18,7 @@ with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
-fonts, including any derivative works, can be bundled, embedded,
+fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The