Changelog
What changed in the governance contract, and what it means for anyone reading or writing these artefacts.
The compatibility promise is in README.md and enforced by check-additive.mjs: within a major
version, changes are additive only. A new optional field or a new member of an open vocabulary is
a minor or patch change. Renaming, removing, narrowing or redefining a field requires a major
version and a documented migration. An entry below that would break a consumer is, by that rule, a
major version — so if you are on 1.x, nothing below this line can break you.
The format is Keep a Changelog, and this project uses semantic versioning over the schema set, not over the tooling in this repository.
Unreleased
Added
- The documentation site at https://cognitive-delivery.github.io/contract/, generated on every
deploy by
tooling/build-site.mjsfrom the filesnpm testchecks: a front page, the schema reference (tooling/site-reference.mjs, one page per schema with every property's description, constraints and stability), the SPEC, README, GOVERNANCE, CONTRIBUTING, SECURITY and CHANGELOG as pages, the guides, conformance, proposals and versions;conformance/site-check.mjsinnpm test(links, anchors, schema bytes, no script or external resource, determinism);marked18.1.0 pinned as a devDependency;pages.ymlcalls the generator after its unchanged schema layout (harness DR-185). docs/implementing.md(the adapter interface in one place, with a worked example from the installed package),docs/upgrading-1.0-to-1.2.md(what the allow-listed tightenings refuse and what a 1.2 writer owes),docs/decisions.md(each release to the harness decision records and the three review documents),docs/conformance/cdf-harness.md(the reference implementation's conformance report), and five proposal records underdocs/proposals/for the 1.2 semantic changes (four accepted, host ports rejected for 1.x), written after the fact under harness DR-184.
Changed
- Every statement current at 1.2.1 (harness DR-184, a read-only audit of 6 October 2026): the 1.1.0
self-test defect is attributed to the release candidate (
mainat e91afb0) rather than the published tarball; "eleven words" is ten in the CHANGELOG and in thedetailsdescriptions ofaudit-eventandcdi-signal(description text only; the suite export regenerated, the lock and the index unchanged);allow.tool_argsis described as a$commentofstability: development; the 1.1.0 section carries oneAdded, oneChangedand oneFixed; SPEC §9 names schema set 1.2 and rule L3 (clauses 9-1 and 9-3 re-affirmed inconformance/traceability.json, a §13 editorial row); CONTRIBUTING lists everythingnpm testchecks and which guard findings need which lock format; GOVERNANCE's release step saysrelease.ymldispatchespages.ymlonmain; SECURITY's lease-escape surface nameslease-record; the README names/1.2.1/, both development-stability fields and rule L3; header comments inconformance/runner.mjs,ids.mjs,schema-checks.mjs,check-additive.mjs,generate-schemas-lock.mjs,generate-contract-types.mjsand the two workflows say what the code does today. - The site carries the cognitivedelivery.co.uk brand (harness DR-186): the website's design tokens
in
tooling/site.css(three:rootblocks: the website's palette, the hoisted header and footer values, and the dark scheme; no literal colour outside them), Source Sans 3 and JetBrains Mono self-hosted as variable woff2 files under the SIL Open Font Licence with its text beside them, the teal and white logos and the three favicons undertooling/site/, every page's header and footer carrying the logo, andtooling/site/BRAND-NOTICE.md, rendered at/brand-notice/and linked from every footer, stating that the name and logo are the owner's marks outside the Apache-2.0 licence.conformance/site-check.mjsnow admits a<link>(stylesheet,iconorapple-touch-icononly), an<img src>, a<source srcset>and a stylesheeturl()only when site-internal and resolving to a written file, the one eyebrow-mark data URI by exact match, refuses@importand any other host, and reports the distinct targets asassets. npm testchecks the numbers the documents state (harness DR-187, the fourth review of 6 October 2026).conformance/claims-check.mjsholds a declarative list of claims, each a file, a pattern capturing the stated value and a function computing the true one from the repository, and fails on a difference or on a statement that is no longer there. It covers a new "corpus in numbers" paragraph in the README (fixtures valid, invalid and by rule; suite tests and cases; canonical, RFC 8785, narrowing and signature vectors; SPEC clauses and exclusions; patterns; declared properties andpropertieskeys; allow-list entries), the schemas carryingschema_versionin the README and the SPEC, the vector and event counts in the README, the SPEC anddocs/, the guard's counts between v1.1.0 and v1.2.0 in CONTRIBUTING and this file, the 1.2.0 and 1.2.1 property and clause counts, and the tagged commits indocs/decisions.md. A claim whose source is not in the package, or that needs the release tags, is reported NOT CHECKED; CI's conformance job now checks out full history so the tags are there.run.mjsprintsClaims: N checked, F failure(s).; the README's Layout block and CONTRIBUTING describe it.- The site reference shows every enumeration in full: past ten values the count is followed by
every value inside a
<details>element (no script), where it used to show three examples (tooling/site-reference.mjs, with self-test assertions that every hook event and everycdi-signalevent type is on the page). The versions page links a 1.0.x release to its tag, labelled "no GitHub Release", because GitHub Releases start at 1.1.0 (a constant intooling/build-site.mjsread from this file's 1.1.0 entry, since the build makes no network request); the front and versions pages say/1.x/servesmain.
Fixed
- Twenty-two statements the fourth review found the artefacts contradicting (harness DR-187),
each re-verified against the artefact before it was changed. The upgrading note had the
compatibility direction backwards (a 1.2 reader reads what a conformant 1.0 writer produced; a
1.0 reader may reject 1.2 output, and the 1.0.2 schemas reject two current valid fixtures). The
detailskey filter is described as approximating the reference writer's rule, not mirroring it, with the differences listed (case-sensitive; a digit ends a camelCase segment) in the README, SECURITY, the 1.1.0 entry below and thepropertyNamesdescriptions ofaudit-eventandcdi-signal; a later task tightens it. "110 entries cover the 115 tightenings" is 58 of the 110.schema_versionis carried by seven schemas, not every one (README, SPEC §6.2, the upgrading note). The pattern dialect is an ECMA-262 subset that compiles under RE2, applied as an unanchored search, not RFC 9485 I-Regexp (SPEC §4.4, the host, path and credential descriptions, two proposals, the 1.1.0 entry and two code comments). ThereasonCodedescription says an issuer MUST NOT mint R7 and above, which the pattern admits./1.x/servesmain, not the 1.2.1 tag (the 1.2.1 entry, the site's front and versions pages, the README, the suite export's labels).docs/decisions.mdgives the 1.0.x tagged commits rather than the annotated tag objects, DR-182 as Approved, and a summary of each review in place of links to private documents. 666propertieskeys are 651 declared properties and 15 condition keys. The corpus runs under "six validators", Ajv among them (the CI job keeps its required-check name). Three CDF extensions, not two. The CI paragraph says which job runs on which Node version and when. The push baseline is the pushed-from commit. The two real-record counts are two provenance populations (front-matter blocks and journal lines). From the installed package three lines report a repository-only check, not two. The site reference shows every enumeration. The versions page labels the 1.0.x links as tags. The harness's real-artefact test passes an empty lease journal. The site stylesheet has three:rootblocks. The SPEC header dates its editorial revisions (a §13 row) and the journal carries ten distinctactor.runtimevalues, not eleven. The CI comment says the narrowing vectors are checked for shape only. An empty code span in the entry above is$commentagain (lost to shell interpolation). Description text only in the schemas: the guard reports no finding, the lock is unchanged, the suite export and the inlined copies regenerated. - The Source Sans 3 licence named the wrong copyright holder. The bundled OFL text said "Google
Inc."; the font is Adobe's, with the Reserved Font Name "Source". Both font licence texts are now
the upstream projects' own (
adobe-fonts/source-sansLICENSE.md,JetBrains/JetBrainsMonoOFL.txt), andtooling/site/BRAND-NOTICE.mdrecords where each came from and what differed.
Security
- The type generator could be made to write code into a consumer's source tree. Found by an
internal security review.
generate-contract-types.mjscopied schemadescriptiontext into/** ... */doc comments without escaping the block-comment terminator, and wrote enum values between single quotes without escaping them. A description carrying the terminator closed its comment, so the rest of the text became a top-level statement in the consumer'ssrc/generated/contractTypes.ts, which the consumer compiles into its product;npm teststayed Conformant. A merged schema change could therefore run code in every repository that regenerated its types. Now every schema-sourced string leaves the generator through one of three functions: description text is flattened to one line with the terminator escaped, enum values are emitted as JSON string literals (numbers only when finite), and every type name derived from a definition key or a$refis refused unless it is a plain identifier; a$refoutside#/definitions/and a version that is not a semantic version are refused too.conformance/generator-tests.mjsrenders a schema carrying the review's payload at every emission site, and enum values carrying quotes, backslashes and line breaks, and reads the output back as code;conformance/schema-checks.mjsrefuses anydescription,titleor$commentcontaining the terminator, and is seen firing on a sample each run. Both run innpm test, and each was seen failing with its protection undone. For a consumer: the generated types for the current schemas are unchanged except that enum members are now double-quoted, so regenerate once after updating (--checkreports the file stale until you do). No schema changed.
1.2.1 — 2026-10-06
Tooling only; the SPEC's schema set stays 1.2 (no normative change) and /1.x/ served the 1.2.1
schemas when it was tagged, additive over 1.2.0 by one field (/1.x/ serves the schemas on main,
which may carry description-only changes ahead of the next tag; /1.2.1/ is the frozen copy); schemaSetVersion and index.json's schema_set
follow the package version, of which only the major is load-bearing. Found by pinning the
reference implementation to 1.2.0: the type generator, the Pages deploy on a tag, and the narrowing
vectors being the reference's own bytes. One additive field, capabilities.tool_args on the plugin
schemas, so capabilities stays the lease allow shape property for property.
Fixed
- The type generator reads
.schema.jsononly, nameslease-record, and says what a type cannot. It tripped overschemas/index.jsonand had no name for the record; and it silently dropped every keyword TypeScript has no words for. The generated file's header now lists them, per keyword with a count and an example site (allOfwithif/then,propertyNames,pattern, the bounds), so a reader of the types knows to validate with the schema as well. capabilities.tool_argson both plugin schemas: the leaseallowshape, property for property, now thatallowcarriestool_args(development stability, as there).- The narrowing vectors are the reference's bytes.
tool-args-droppedwas added by hand in 1.2.0; the file is now regenerated from the reference implementation (CDF Harness) as the others always were, which placed the vector second and gave it thediffthe generator records. - A valid lease-record fixture's reason no longer contains the word "secret", which the reference deployment's corpus hygiene test bans as a marker.
- The frozen copy deploys after a release. The github-pages environment admits
mainonly, so the tag push that was meant to lay out/1.2.0/was refused at the deploy step;release.ymlnow dispatchespages.ymlonmainafter publishing (it fetches every tag), and the tag trigger is gone. 1.2.0's copy was deployed by hand the same way and is byte-identical to the tag.
1.2.0 — 2026-10-05
Schema set 1.2 (CDF spec contract-batch-two-implement-all, DR-183): the second review's fourteen
improvements, from a package that could not run its own test to a corpus that runs under six
validators, Ajv among them. Additive within 1.x by the contract's own rule, mechanically checked: against 1.1.0
the guard reports 115 allow-listed tightenings, covered by 58 of the allow-list's 110 entries (an
entry at a definition's path covers every property in its schema that refers to it; the other 52
entries are 1.1.0's), each naming the exact value it
admits and the fixture or recorded check that proves no conformant writer ever produced what it now refuses, and
every real journal in the reference deployment validates with zero rejections (33,608 audit
records, 19,231 CDI signals, 995 provenance front-matter blocks, 2 assessments, the six-line lease journal, the
workspace config). No byte of any existing hash or signature changes. One new schema,
lease-record; one field at development stability, allow.tool_args; everything else stable.
Added
lease-record, the decision side of the lease (SPEC §6.1). One line of the lease journal: nine events (granted,refused,narrowed,heartbeat,attached,revoked,stopped,completed,expired) with what each must carry, enforced by conditional requirements; a refusal'sreasonsare reserved codes (R<n>,runtime_error:<name>,vendor:<vendor>:<code>) with the prose inreason;declared_hashandgranted_hashtie a record to the exact bytes decided; arevokedrecord namesby(an ancestor lease orissuer) and rule L3 refuses any other, given the journal. The schema carries the manifest and the lease inlined, held identical to their sources (conformance/inlined-copies.mjs). Eleven valid fixtures, five invalid, one by rule. The reference deployment's real journal validates unchanged; a 1.1grantedrecord carries nogranted_hashand a reader may compute it.
Changed
- Hosts, commands, tools and approvals have one identity rule each, enforced by the schemas
(SPEC §4.4). A host is a lower-case DNS name (IPv4 literals and
localhostincluded), with at most a single leading*.label, or*; a scheme, a port, a path, whitespace, upper case, a trailing dot and an interior wildcard are refused. A command is the executable's basename: no path, no arguments, no shell operator. A tool or approval is an identifier of at most 128 characters that is never*and carries no whitespace. The same rules apply to the plugin schemas'capabilities, which is the leaseallowshape. Eighteen invalid fixtures, one per refused form, and one valid fixture carrying every admitted form.agent.nameis capped at 120 characters and described as never a person's name;intent.purposeat 500.tooling/inline-granted-manifest.mjsrewrites the inlined copy inagent-lease.schema.jsonfrom its source, so the identity check has a tool to satisfy it. - Lease rules L1 and L2, and one timestamp form inside the signed bytes (SPEC §6). A schema
cannot say "
expires_atis afterissued_at" or "not its own parent", soconformance/lease-rules.mjsdoes, the runner applies it to every valid lease and tofixtures/invalid-by-rule/(Expired,TooEarly,SelfParent, named after the UCAN 1.0 fixture errors where one exists), and an adapter proves its own rules through aruleshook, reported unchecked when absent.issued_atandexpires_atadmit only UTC with milliseconds andZ, because an offset form hashes the same instant to different bytes; every real lease already uses it.attestation.signatureis 64 hex like the lease's own;budget.depth≤ 16 andbudget.fan_out≤ 256. - An allow-list entry admits one tightening, not every later one at the same path. Every guard
finding now carries
after, the exact value it admits (pattern=…,maximum=16,enum=[…]), and an entry must name it. Found while loweringdepth's ceiling: batch one's entry for the 2^53maximumwould have covered it silently. Every existing entry gained itsafter; three guard scenarios prove the match is exact. - Inline plugin hooks and MCP servers are typed, and a manifest cannot carry a credential.
An inline
hooksobject validates as the event map (thirty-three events, five handler types with their required fields, after the Claude Code settings schema of 2026-10-05) and an inlinemcpServersobject as a map of server configs (stdiorequirescommand;http,sse,wsandstreamable-httprequireurl). A value in a hook header, an MCPenvor an MCPheadersthat matches one of seven credential shapes is refused; a marketplace entry'sheadersrefuses anauthorizationkey in any case; a contributed provider'sbaseUrlishttps://orhttp://to loopback only. Nine invalid fixtures, one valid fixture with every admitted inline form; Anthropic's bundled-plugin manifest and the reference deployment's own manifests validate unchanged. - The lock follows local
$refs (lock format 4). A property re-pointed from one definition to a stricter one used to change only itsrefstring, which the guard never compared, so the typed hook and MCP shapes above would have landed unseen; and a value that became a$reflost its recorded type and read asTYPE_CHANGED. The referenced definition is now digested at the referring path, withrefrecorded beside it; a cycle stops at its second visit. - Evidence hygiene (SPEC §6.2).
audit-event.event_typeis two or more lower-case dotted segments, with the reference writer's first segments reserved and a vendor name for anyone else;summaryandreasoningrefuse any control character; adetailsstring value is at most 200 characters;actor.runtime_agent(optional, the closed vocabulary) is added whileactor.runtimestays open, because the real journal carries ten distinct values of it;schema_versionismajor.minoron every schema that carries it (the lease schemas widen from the literal1.0);provenance.specis a slug; a CDI assessment has exactly six dimensions, each id once, with integer scores; a sealed config path is dotted lower-case and the runner checks it names a field the fixture carries. Ten invalid fixtures, one valid. Every real audit record, signal, provenance record and assessment in the reference deployment validates. allow.tool_args(SPEC §4.4), a per-tool argument-schema declaration at development stability (a$commentofstability: development): an issuer MAY omit it from the grant and MUST NOT treat it as authority, because the reference gate does not yet evaluate argument schemas and a rule without an enforcing gate is a claim the corpus cannot test. The vectortool-args-droppedshows the reference dropping it. The stability marker is a$comment(draft-07 defines it): Bowtie showed Ajv's strict mode in another harness refusing a customx-stabilitykeyword, and a schema only this repository can compile is not portable. Python'srelikewise rejected\p{Cc}, so the control-character class is written as literal characters, which every engine reads the same way.- Every normative clause of the SPEC has a named test (
conformance/traceability.json, checked bynpm test).conformance/spec-clauses.mjsextracts the 87 clauses with stable ids and a drift key; each is mapped to the fixtures, vectors, checks or rules that test it, or excluded with a reason (16 are: runtime behaviour, SHOULDs, definitions). Seven fixtures were added where a clause had nothing to point at: a manifest with a bare-majorschema_version, aruntime_agentoutside the vocabulary, a model withoutfamily, anallowordenymissing a list, an attestation with an unknown issuer, and a valid home-relative deny path. - The corpus runs under six validators.
conformance/suite/draft7/is the corpus in the official JSON-Schema-Test-Suite format (one file per schema, every fixture a test, written bynpm run lockand checked current bynpm test), and CI runs it through Bowtie againstgo-jsonschema,rust-jsonschema,python-jsonschema,java-json-schema,dotnet-jsonschema-netandjs-ajv, failing on any disagreement. A case is kept under 60 KB as one line, chunking a schema's tests across cases where needed, because a harness that reads a case as a line (the Go one) errors above 64 KiB. A second job runs Sourcemeta'sjsonschema metaschemaandlint(six style rules excluded by name, each with its reason in the workflow). Two orphancomponentSourcedefinitions the typed hook and MCP shapes had left behind are removed, and the marketplace's emptyrelevance.signalsschema gained a description, both found by that lint. - Every property says what it promises. All 664
propertieskeys (666 at 1.2.1, withcapabilities.tool_argson both plugin schemas), which are 649 declared properties (651 at 1.2.1) and 15 keys insideifandcontainsconditions, carry a$commentofstability: stableorstability: development(onlyallow.tool_argsis development), with; deprecated: <replacement>for retiring a field.conformance/metaschema.jsonholds that and the other conventions (dialect,$id, title, description, noformat) andnpm testvalidates every schema against it. Lock format 5 recordsstability,deprecatedand the names anallOfif/thenmakes required (the lease record's per-event requirements were invisible to earlier formats); the guard reportsSTABILITY_LOWEREDandCONDITIONAL_REQUIRED_ADDEDas breaking and treats deprecation as additive. Two new guard scenarios. - Governance written down.
GOVERNANCE.md: one maintainer (@datajace, the sole CODEOWNER, stated rather than padded), a proposal underdocs/proposals/before any semantic change, how a change lands and how a release is cut. A repository-ownedDCOcheck refuses a pull request with an unsigned commit (the third-party app was not used because a suspended app's check disappears silently). Issue templates for a defect and a proposal, a pull-request template with the checks the automation cannot see, Dependabot for npm and Actions weekly, and the proposal template with Backward compatibility and Security sections and a status lifecycle. - Every release frozen at its own URL, and an index.
pages.ymlnow lays out/<version>/for everyv1.*tag beside the/1.x/alias, byte for byte as tagged and never rewritten, and runs on the tag push so the frozen copy appears with the release (1.2.1: the tag trigger is gone;release.ymldispatchespages.ymlonmain);release.ymlchecks it serves the tagged bytes (patiently, and reported rather than failed while the deploy is still landing).schemas/index.json, written bynpm run lockand validated bynpm test, lists every schema withfile,$id,title,dialectandfileMatch; it is served beside the schemas. A SchemaStore catalogue entry is proposed for**/.cdf/config.yaml, pointing at the servedconfig-coreschema.
1.1.0 — 2026-10-05
The first release after the review of 4 October 2026 (CDF spec contract-fix-batch-one-4,
DR-182). Five of the review's ten improvements, in the order the guard first so every tightening
that follows is classified and allow-listed by name. Additive within 1.x by the contract's own
rule, mechanically checked: against 1.0.2 the guard reports 46 allow-listed tightenings, each
with the fixture or real-data count that proves no conformant writer ever produced what it now
refuses, and every real journal in the reference deployment (33,557 audit records, 5,652 signals,
4,535 provenance lines) validates with zero rejections. No existing hash or signature changes.
Added
The additive-only guard sees tightenings (
schemas.lock.jsonis now lock format 3). The lock records, per property path,pattern, theallOf[].not.patternset,minLength,maxLength,minimum,maximum,additionalPropertiesand the number ofanyOfbranches, and per schema whether its root is open or closed; enum values keep their JSON type instead of being stringified.check-additive.mjsreports named findings (PATTERN_TIGHTENED,BOUND_TIGHTENED,CONTENT_MODEL_CLOSED,UNION_CHANGEDbeside the four it already knew) and says plainly when a baseline predates the format and those four cannot be compared. A--baseline-refbaseline is digested from the schemas as they were at that ref with the current generator, so two lock formats are never compared; the committed lock stays the drift record for its own commit. Format 3 records a union's branch types and patterns on the parent entry, andpropertyNamesrefusals, both of which format 2 lost.Until now a pattern could be tightened inside 1.x and the guard would print "additive only", which is the class of change the 4 October 2026 review found it blind to. Nothing in the schemas changed in this entry; the guard learned to see.
compat-allowlist.json: the one way a tightening passes within a major. Each entry names the finding, the schema, the path, a reason, a date and an existing evidence file; the guard refuses an entry with no evidence and refuses a change that drops an entry the baseline had. Documented in CONTRIBUTING.The guard's own tests run in
npm test(conformance/guard-tests.mjs): 27 scenarios in which every finding is seen to fire, every additive change is seen to pass, and the allow-list is seen to refuse an entry without evidence.Three checks the contract's own CI now holds (
conformance/schema-checks.mjs, innpm test): every schema compiles under Ajv strict mode; the granted manifest inlined inagent-lease.schema.jsonis byte-for-byte the manifest schema dereferenced (this check used to live only in the consuming harness, so the contract could not fail on its own drift); andpackage.json'scdfContract.schemaSetVersionmatches the package version.A regex-portability job.
tooling/regex-portabilitycompiles everypatternin every schema with Go'sregexp(RE2: no lookahead, no backreferences), because Go validators use it unconditionally and a pattern RE2 rejects is a schema set a Go implementation cannot load. The job failed until the path patterns were rewritten without lookahead later in this release (see Changed below), and is now a required check.Narrowing vectors (
conformance/narrowing-vectors.json). SPEC §9 called narrowing "the heart of the specification" and left it to each implementation's own tests. The corpus now carries declared-and-parent pairs with the granted manifest or the refusal codes narrowing must produce: every row of the §5 table, every containment row of §5.1 (includingsrc/*.tsnot containingsrc/a.ts), every refusal of §5.2,unknownaccepted forexternaland refused fornative, an escaping path refusing the whole manifest, and a root manifest narrowed against a root policy. The vectors were generated from the reference implementation'snarrowManifestand committed. An adapter suppliesnarrow(declared, parent)and the runner compares granted manifests by canonical bytes and refusal sets exactly; one that does not is reported as not checked. Every adapter has the vectors checked for shape. §5.2's six conditions gain stable identifiers R1 to R6.A published test key, verifiable signed fixtures and signature vectors (
conformance/test-key.txt,conformance/signature-vectors.json). The lease fixtures carriedcccc…anddddd…fordeclared_hashandsignature, so no verifier could be tested against the corpus. They are re-signed under a public test key (which a verifier must refuse outside a conformance run), a root lease fixture is added, and an adapter offeringhashandverifyis checked for matchingdeclared_hash, acceptance, and refusal when one byte of the signature or of the granted manifest changes. The reference adapter implements the reference HMAC-SHA256.Every invalid fixture carries an
.expect.jsonnaming the instance path the rejection must be reported at, and the runner checks it when the adapter reports its errors. Adding them found two fixtures (cdi-signal.bad-outcome,cdi-signal.unknown-event) that this release'sworkspace_idshape had started rejecting first for the wrong reason; both now carry a digest-shapedworkspace_idso they fail only for the reason their.reasonstates. The unknown-field round trip now plants the field inside the first nested object as well as at the top level.Every key the Claude Code plugin and marketplace references document (read 2026-10-05) is modelled:
$schema,icon,documentationUrl,supportUrl,privacyPolicyUrl,termsOfServiceUrl,dependencies(string,name@marketplaceor object),settings,userConfig(strict options:type,title,descriptionrequired;required,default,options,multiple,sensitive,min,max),types,channels(strict),commandsas an object map ofsource-or-contententries,hooks/mcpServers/lspServersas path, inline or a mixed array (with.mcpb,.dxtandhttps://bundles), strictlspServersentries withcommandandextensionToLanguagerequired,outputStyles,workflows, top-levelthemes(deprecated, still loaded) andexperimental(themes,monitorsas strict entries,evals); marketplaceforceRemoveDeletedPlugins, entryrelevanceanddependenciesand every manifest field an entry may carry;commandsourcetimeout(1 to 600) andmode(copyorlink);archivesha256in either case; and the if/then rule thatheadersHelperrequires"strict": false. Names follow Claude Code's rule (letters, digits,.,_,-, leading alphanumeric) instead of kebab-case only. Where Claude Code's object is strict the contract's is too, because honouring a key "with the same meaning" there means refusing an unknown one.Two fixtures carry the evidence: the manifest reference's own example manifest, and Anthropic's marketplace for its bundled plugins (
anthropics/claude-codeat a pinned commit, author emails removed). Four invalid fixtures pin the strict shapes. Thelocalsource form and plugin-levelcategorystay as CDF extensions, named as such in the schema descriptions and, in the next change, the README.
Changed
The README no longer claims the plugin schemas contain every key Claude Code has. The sentence was true at 1.0.0 and stopped being true as Claude Code grew; a standing superlative about a moving target is the kind of sentence this contract exists to refuse. The README now says what is modelled and as of which date, names the three CDF extensions (
cdf; thelocalsource form; plugin-levelcategory) so nobody mistakes them for Claude Code's, and the old wording is a banned claim in the reference implementation's documentation gate.Canonical bytes are declared to be RFC 8785. SPEC §7 now says normatively that canonical bytes are the RFC 8785 (JSON Canonicalization Scheme) serialisation after removing absent members, with the field-by-field rules kept as an informative restatement. The restatement was already RFC 8785 (the reference canonicaliser passes RFC 8785's own vectors unchanged), so no existing hash or signature changes; what changes is that an implementer in Go, Java, Python, Rust or .NET can use an existing JCS library and check it against the corpus, which now carries RFC 8785's six reference vectors (
conformance/jcs/, vendored at a pinned commit under Apache-2.0 with its source recorded) beside the contract's nine. Everyintegerfield is bounded at 2^53 − 1 because RFC 8785 presumes I-JSON; the tenBOUND_TIGHTENEDfindings are allow-listed with a fixture.The privacy properties are enforced by shape, not prose.
request_hash,prompt_hash,steering_hashandcontent_hashrequire a 64-character lower-case hex digest;detailson the audit event and the CDI signal refuses bypropertyNamesany key whose segment is one of the reference writer's ten words (authorization,content,file,password,path,payload,prompt,request,secret,token), split on non-alphanumerics and camelCase boundaries, which approximates the writer's rule (the schema is case-sensitive where the writer is not; see thepropertyNamesdescriptions for the differences);summaryis capped at 300 characters andreasoningat 500, the writer's own caps. Before this a raw prompt inrequest_hashvalidated, which SECURITY.md itself calls a security issue. Each tightening is allow-listed with its fixture; the check against the reference deployment's journals (33,538 audit records, 5,634 signals, 4,535 provenance lines) rejects nothing.workspace_idis widened, and its description corrected. It accepts a SHA-256 digest or a lower-case UUID, because the collector writes a per-checkout UUID when the workspace has no git remote and 4,910 of the reference deployment's 5,634 signals carry one. The description said "SHA-256 of the git remote URL" and was false against real artefacts.schema_versionstays required and the prose stops saying otherwise. The README and the provenance description said an absent value "is read as 1.0"; the schemas required it, and zero real records lack it. A writer always writes it; a reader may read a pre-contract artefact as 1.0.Path rules are written without lookahead, and refuse three forms they used to accept. Every
read_paths,write_paths,deny.paths, pluginworker,docPacksandgit-subdirpathrule is nowallOfofnot/patternclauses written without lookahead in the ECMA-262 subset RE2 also compiles. Go'sregexp(RE2) and the validators built on it could not load the old(?!…)rule at all, so the README's "an implementation in another language needs nothing else" was false for Go; the newregex portability (RE2)CI job now passes and is required. Anallowpath additionally refuses a leading~, a drive-letter prefix and any backslash, which SPEC §5.2 already required and the reference implementation already did; six new invalid fixtures prove each form. Adenypath may be home-relative (~/.ssh/**), because the reference implementation's own root policy denies exactly that and refusing a path outside the workspace is meaningful. Each tightening is allow-listed with its fixture as evidence; no real lease in the reference deployment carried a refusedallowform.The push baseline is the pushed-from commit. The additive guard compared a push against
HEAD^, so a three-commit push whose first commit broke the rule was compared only against its own second commit and passed. It now compares againstgithub.event.before, falling back to the merge base withmainon a brand-new branch. Pull requests still compare against the base branch.
Fixed
Every
$idresolves. The schemas namedhttps://cognitivedelivery.co.uk/contract/1.0.0/…, which redirected towwwand returned 404 for the life of 1.0.x: a dangling identifier in a contract about recording things verifiably. Each$idis nowhttps://cognitive-delivery.github.io/contract/1.x/<file>, served by GitHub Pages from theschemas/directory at the deployed commit (pages.yml; no copy is committed, so nothing can drift) and checked byte for byte against the tag on every release.1.x, not1.0.3: an identifier that changed on every minor release would be a version number with extra steps, and the version a document was written against is its ownschema_version. Nothing resolved the old URL, so the change costs no reader anything. Pages must be enabled on the repository (source "GitHub Actions") for the URLs to serve; until then the release job warns rather than fails.One version source.
tooling/sync-version.mjswrites the version frompackage.jsonintocdfContract.schemaSetVersion, the README's version line, every schema's$idmajor and any fixture's$schema;npm run lockruns it first, andnpm testchecks all of them plus the CHANGELOG, the lock and the SPEC header, and refuses any remaining reference to the old host.package.jsonsaidschemaSetVersion1.0.0. It had been stale since 1.0.1, and the version currency check added in 1.0.2 did not read it. It now reads it.The published package runs its own
npm test. The 1.1.0 release candidate (e91afb0, before the tag) could not:conformance/run.mjsimportedidForfromtooling/sync-version.mjsandguard-tests.mjsimportedcheck-additive.mjs, and neither is infiles, so the installed package failed with ERR_MODULE_NOT_FOUND before validating a single fixture (review of 5 October 2026, defect 1).idFornow lives inconformance/ids.mjs, which ships; the guard is loaded dynamically and reported not present from the tarball rather than passed; and a CI job packs the tarball, installs it into an empty directory withajv, and runs the installed package's test on Node 20 and 22, because no check run from a clone can see what the clone has and the tarball lacks.exportsexposes every file underconformance/andschemas.lock.json, so@cognitive-delivery/contract/conformance/narrowing-vectors.jsonresolves from a consumer; the two explicit module entries stay as they were.The README's Layout block is checked (
conformance/layout-check.mjs, innpm test): every entry at the top level and underconformance/,fixtures/andtooling/must have a line, and every line a file. It had fallen eleven files behind. SECURITY.md now names the published test key and the allow-list as in-scope surfaces.A GitHub Release for every tag. Until now no release had been cut on GitHub: the tags and the npm versions existed and the repository's Releases page said none.
release.ymlnow creates (or, on a re-run, edits) the release for the tag with this CHANGELOG section as its notes (tooling/changelog-section.mjs, whose extractionnpm testalso checks is non-empty), thenpm packtarball attached, and links to the npm version page and how to verify its provenance.A daily
$idwatch (.github/workflows/id-watch.yml). The release workflow checks every$idonce, at the tag; this fetches each one every day and fails when it is not 200 or serves bytes other than the file onmain. A red run is the notification; it changes nothing.
1.0.2 — 2026-09-19
Fixed
The published 1.0.1 tarball shipped a stale
schemas.lock.json. It recordedschemaSetVersion: "1.0.0"because the correction landed after the tag, so the artefact on npm disagreed with the repository. Onlymajoris load-bearing — the additive-only rule is scoped to it, and it read1throughout — so nothing downstream was at risk, but a governance contract publishing a record of itself that is out of date is the wrong thing to leave standing.Both of this repository's gates had said it was fine:
check:additivecompares schema shapes and never looks at the version field, so it printed "Lock is current" over a stale one. It took a downstream consumer's test to notice.npm testnow checks the lock's version alongside the README's and the CHANGELOG's.
Changed
CONTRIBUTING.mdsays whatnpm testactually runs — four checks now, including the canonical-bytes vectors — and whatcheck:additivedeliberately does not. It also has a section on changing the specification, because §7 cannot be edited casually: every hash in this contract is taken over those bytes, so a change there invalidates every signature anyone has produced.
1.0.1 — 2026-09-19
The first release published from CI, and therefore the first carrying a provenance attestation — 1.0.0 went out by hand because npm trusted publishing must be configured on a package that already exists. A contract that asks other people to record what produced an artefact should be able to show what produced its own; from here it can.
No schema changed. This release is the specification, the vectors and the tooling around them. The published tarball is 91 files.
Added
A normative specification for the Agent Lease Manifest (
SPEC-agent-lease-manifest.md). The schemas said what an artefact looks like; they did not say what an implementation must do. The specification states the narrowing algebra, the six conditions that require a refusal, the threat model, and what an implementation must satisfy to claim conformance.It closes one gap that made independent implementation impossible.
declared_hashand everysignatureare computed over "canonical bytes", and that phrase appeared in six schema descriptions without ever being defined. Two implementations could both validate against these schemas and still produce different hashes for the same declaration. §7 now specifies canonicalisation exactly: UTF-8, no insignificant whitespace, members ordered by key ascending by UTF-16 code unit, absent members omitted rather than serialised asnull, and non-finite numbers a serialisation failure rather than a placeholder.The specification also records two limitations plainly rather than leaving them to be discovered: signatures are symmetric, so a third party cannot verify a lease without the issuer's key; and path containment is deliberately conservative, so a parent granted
src/*.tsdoes not contain a child declaringsrc/a.ts.Canonicalisation test vectors (
conformance/canonical-vectors.json), and a fourth check in the conformance runner that executes them. Nine vectors — member ordering, recursion, absent versus null, empty containers, the escape set, the literal set, unpaired surrogates, number forms, and the UTF-16 versus code-point key ordering that is the one most likely to diverge — each with its expected byte string and the SHA-256 of those bytes, plus two values that must fail to serialise rather than produce a placeholder.Supply a
canonicalise(value)on your adapter and the runner checks them. Omit it and the run reports NOT CHECKED rather than passing quietly, because a silent skip on the one check that decides whether two implementations can verify each other's signatures is worse than no check. The file is pure ASCII, every character above U+007E escaped, so nothing in it depends on an editor or a transfer preserving bytes it might not.The reference canonicaliser now lives in
conformance/ajv-adapter.mjsas an exported function, so the repository demonstrates the rules rather than only describing them.
Fixed
publishConfig.provenance: truemade the first release impossible to publish. It requires a CI provider, so a publish from a workstation fails withAutomatic provenance generation not supported for provider: null— but npm trusted publishing is configured per package and needs the package to exist, so CI cannot create it either. The two settings contradicted each other.--provenancenow sits on the release workflow's publish step, so CI still attests every release and a manual publish works when it has to.
1.0.0 — 2026-09-19
First published release. Nine schemas, a conformance corpus of 20 valid and 26 invalid fixtures, and the additive-only lock.
Added
- Schemas for the nine governed artefact shapes:
agent-lease-manifest,agent-lease,audit-event,cdi-assessment,cdi-signal,config-core,plugin-manifest,plugin-marketplace,provenance. - A conformance corpus. Every invalid fixture carries a
.reasonfile stating what the rejection is for, so a second implementation can check that it refuses the same things for the same reasons. A schema alone is not a conformance test: two implementations can both validate against it and still disagree about what they refuse, and that disagreement is the one that matters. schemas.lock.jsonandcheck-additive.mjs, which enforce the compatibility promise in CI against a baseline rather than merely stating it in prose.- Two deliberately closed vocabularies,
agent.kindandagent.runtime_agent, because a value nobody recognises is worse than an explicitunknown. - Public CI — the conformance corpus on Node 20 and 22, the additive-only guard, and a line-ending check.
Notes on this release
- 1.0.0 carries no provenance attestation. It was published by hand because npm trusted publishing must be configured on a package that already exists. Releases from 1.0.1 onward are published from CI and attested.
- The published tarball is 88 files.
package.json'sfilesfield is the authority on what ships; the generators and the CI configuration stay in the repository and are not published.