Repository navigation
1.1.0
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, and
propertyNamesrefusals, 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 in
agent-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); and
package.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 is expected to fail until the path patterns are rewritten without lookahead in the next
change, and is made a required check then.
Added
-
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 containing
src/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 carried
cccc…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_id
shape had started rejecting first for the wrong reason; both now carry a digest-shaped
workspace_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 with
commandandextensionToLanguagerequired,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(copyor
link);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-level
categorystay 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 two 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 eleven words (authorization,content,file,password,path,payload,
prompt,request,secret,token), split on non-alphanumerics and camelCase boundaries
exactly as the writer does;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-subdirpath
rule is nowallOfofnot/patternclauses in the RFC 9485 I-Regexp subset. 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 new
regex 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 now
https://cognitive-delivery.github.io/contract/1.x/<file>, served by GitHub Pages from the
schemas/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.jsoninto
cdfContract.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.
Fixed
- The published package runs its own
npm test. 1.1.0's tarball could not:conformance/run.mjs
importedidForfromtooling/sync-version.mjsandguard-tests.mjsimported
check-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), the
npm 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.
npm: https://www.npmjs.com/package/@cognitive-delivery/contract/v/1.1.0
Provenance: published from this workflow run with npm publish --provenance; verify with npm audit signatures after installing, or search Sigstore for pkg:npm/%40cognitive-delivery/contract@1.1.0.
Schemas: https://cognitive-delivery.github.io/contract/1.x/