feat(spec): v0.7.5 — signatures schema alignment + composition-pack file requirements#12
Conversation
There was a problem hiding this comment.
Pull request overview
Updates KP:1 v0.7.5 spec + conformance tooling to align signatures.yaml across prose/schema/implementations and to correctly validate composition packs that omit evidence.md.
Changes:
- Regenerates and starts enforcing
kp-signatures.schema.jsonfor anysignatures.yamlfound during conformance runs. - Adds explicit composition-pack file presence rules in the spec and updates the conformance runner to not hard-require
evidence.mdwhencomposition.yamlis present. - Adds new conformance fixtures: a minimal composition pack and a maximal signatures envelope for schema coverage.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| spec/SPEC.md | Documents composition-pack file requirements and reiterates the manifest extensions lane. |
| spec/CORE.md | Documents the optional extensions object as the sanctioned manifest compatibility lane. |
| spec/CHANGELOG.md | Adds the v0.7.5 entry describing signatures alignment + composition-pack rules + runner updates. |
| spec/ARCHIVE.md | Documents pack_id, pack_name, pack_version in signatures.yaml and clarifies required/optional policy. |
| lefthook.yml | Tightens deny-list regexes intended to block “process language” in staged content / commit messages. |
| conformance/run.py | Skips evidence.md requirement for composition packs; validates signatures.yaml against the signatures schema when present. |
| conformance/grammar/kp-signatures.schema.json | Aligns the signatures schema with ARCHIVE.md §4 (v0.7.3+ shape) and closes the root object. |
| conformance/grammar/kp-pack.schema.json | Adds extensions to the PACK.yaml schema (root remains closed elsewhere). |
| conformance/fixtures/valid/maximal.kpack/signatures.yaml | New signatures fixture exercising optional metadata + parent block. |
| conformance/fixtures/valid/composition.kpack/PACK.yaml | New minimal composition-pack manifest for conformance coverage. |
| conformance/fixtures/valid/composition.kpack/composition.yaml | New composition definition used to trigger composition-pack behavior in the runner. |
| conformance/fixtures/valid/composition.kpack/claims.md | New prose-only claims file to demonstrate minimum-shape composition packs. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| clean=$(echo "$msg" | sed -E '/^Co-Authored-By: (Claude|Codex|Gemini|Antigravity|Cursor) <noreply@[^>]+>$/d') | ||
|
|
||
| if echo "$clean" | grep -iE 'cross-model|multi-model (synthesis|analysis|consultation|review)|round [0-9]+ (review|verify|findings)|consulted (codex|gemini|claude)|(codex|gemini|claude) (feedback|suggested|reviewed)|adversarial review|reviewer [A-Z0-9]|session handover|model attributions' >/dev/null 2>&1; then | ||
| if echo "$clean" | grep -iE 'cross-model|multi-model (synthesis|analysis|consultation|review)|round [0-9]+ (review|verify|findings)|consulted (codex|gemini|claude)|(codex|gemini|claude) (feedback|suggested|reviewed)|adversarial review|reviewer [0-9]+\b|session handover|model attributions' >/dev/null 2>&1; then |
| try: | ||
| jsonschema.validate(sigs, signatures_schema()) | ||
| except jsonschema.ValidationError as e: | ||
| errs.append(Err("signatures", f"signatures.yaml: {e.message}")) |
| "pattern": "^[a-f0-9]+$", | ||
| "description": "Hex-encoded content hash for the file." | ||
| "pattern": "^[a-f0-9]{64}$" | ||
| }, |
| | `PACK.yaml` | REQUIRED | REQUIRED | | ||
| | `composition.yaml` | ABSENT | REQUIRED (defines the composition) | | ||
| | `claims.md` | REQUIRED | REQUIRED — contents are intentionally minimal: claims describe the composition context itself (who, when, why, what changed), not the topics being composed (see COMPOSITION.md §3) | | ||
| | `evidence.md` | RECOMMENDED — required in practice whenever any claim cites an evidence ID | OPTIONAL — may be omitted entirely; claims may leave the evidence position empty and use `↔` relations instead | |
|
|
||
| # Process language (the patterns that actually leaked) | ||
| if grep -inE 'cross-model (review|consultation|spec review)|multi-model (synthesis|analysis|consultation|spec review)|three-model consultation|two-model consultation|round [0-9]+ (review|verify|cross-model|findings)|consulted (codex|gemini|claude)|adversarial review|(codex|gemini|claude) feedback|reviewer [A-Z0-9]|session handover|model attributions' "$file" 2>/dev/null; then | ||
| if grep -inE 'cross-model (review|consultation|spec review)|multi-model (synthesis|analysis|consultation|spec review)|three-model consultation|two-model consultation|round [0-9]+ (review|verify|cross-model|findings)|consulted (codex|gemini|claude)|adversarial review|(codex|gemini|claude) feedback|reviewer [0-9]+\b|session handover|model attributions' "$file" 2>/dev/null; then |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: f5c5937d9d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| | `PACK.yaml` | REQUIRED | REQUIRED | | ||
| | `composition.yaml` | ABSENT | REQUIRED (defines the composition) | | ||
| | `claims.md` | REQUIRED | REQUIRED — contents are intentionally minimal: claims describe the composition context itself (who, when, why, what changed), not the topics being composed (see COMPOSITION.md §3) | | ||
| | `evidence.md` | RECOMMENDED — required in practice whenever any claim cites an evidence ID | OPTIONAL — may be omitted entirely; claims may leave the evidence position empty and use `↔` relations instead | |
There was a problem hiding this comment.
Remove unsupported empty-evidence rule for composition claims
This new normative row says composition claims may leave the evidence slot empty, but the conformance parser still rejects empty dense evidence fields with a parse error (empty evidence ref list). That means a pack can be valid by this spec text and still fail conformance/run.py, which creates a breaking spec/validator contradiction for composition-pack authors. Please either drop this allowance here or update the parser/semantic checks to accept it for composition packs.
Useful? React with 👍 / 👎.
| errs.append(Err("signatures", f"signatures.yaml parse error: {e}")) | ||
| else: | ||
| try: | ||
| jsonschema.validate(sigs, signatures_schema()) |
There was a problem hiding this comment.
Enforce signature timestamp format during schema validation
This validation call does not pass a format checker, so with jsonschema>=4 the schema's format: date-time on sealed_at is not actually enforced. As written, malformed timestamps in signatures.yaml can pass conformance despite the new schema claiming ISO 8601 validation. Use an explicit format checker when validating signatures to make the new checks effective.
Useful? React with 👍 / 👎.
…ile requirements
Aligns the spec and its normative grammar with what the current sealer
and relay already produce and consume. Also closes the composition-pack
validation hole.
Signatures schema (kp-signatures.schema.json):
- Regenerated from ARCHIVE.md §4. Required: algorithm, pack_hash,
files, sealed_at, sealed_by. Optional: pack_id, pack_name,
pack_version, parent, signature.
- Removed pre-v0.7.3 fields (signed_at, signing_key, and flat-string
signature) that no current sealer writes.
- Root closed with additionalProperties: false.
pack_id documented (ARCHIVE.md §4):
- Added to the field types table and required/optional table.
- Prose policy: OPTIONAL in schema for compatibility with archives
sealed before the field was documented; new archives SHOULD include
it. pack_name and pack_version also documented as optional
self-describing receipts.
Composition-pack file requirements (SPEC.md §2):
- New subsection specifying that packs with composition.yaml MAY omit
evidence.md. claims.md remains REQUIRED; its content is intentionally
minimal per COMPOSITION.md §3 (context about the composition itself).
Conformance runner (conformance/run.py):
- validate_pack() now detects composition packs and skips the
evidence.md presence check for them; evidence-ID extraction tolerates
a missing evidence.md.
- signatures.yaml is validated against kp-signatures.schema.json
when present. Previously the runner was silent on signatures.
Fixtures:
- composition.kpack (valid) — minimum-shape composition pack.
- maximal.kpack/signatures.yaml — exercises the refreshed schema with
the optional parent block and self-describing receipt fields.
Co-Authored-By: Claude <noreply@anthropic.com>
Pre-merge adjustments based on three independent review sessions.
Schema (kp-signatures.schema.json):
- Added parent.merge_parents as optional array. Documents a latent
capability declared in kp-forge's PackSignatures TypeScript shape
so the closed parent block does not reject valid merge-lineage
archives. Alternative considered and rejected: open parent with
additionalProperties: true (breaks the closed-schema pattern).
- Added propertyNames constraint to the files map forbidding the
key "signatures.yaml". The MUST NOT rule was previously prose only.
Runner (conformance/run.py):
- Enabled jsonschema.FormatChecker so sealed_at actually enforces
format: date-time. Previously the format annotation was advisory.
- Added None-check before schema validation so an empty signatures
file produces "signatures.yaml is empty" instead of an obscure
type error.
Spec (SPEC.md §2 composition-pack subsection):
- Removed "claims may leave the evidence position empty and use ↔
relations instead" from the evidence.md row. The validator does not
yet permit empty evidence slots in dense claims, so the prose
promised more than the parser delivers. That capability is deferred
to a later spec revision; removing the promise avoids a spec-vs-code
contradiction.
Spec (ARCHIVE.md §4):
- Added parent.merge_parents to the field types table and the
required/optional table to match the schema.
CHANGELOG: adjusted v0.7.5 Added/Changed sections to reflect the new
merge_parents entry, the propertyNames enforcement, the FormatChecker
wiring, and the removed evidence-slot sub-clause.
Verified locally: conformance suite 13/13; full repos/packs corpus
sweep 51/54 (3 pre-existing content failures, unchanged from pre-fix
baseline).
Co-Authored-By: Claude <noreply@anthropic.com>
7af0e88 to
8b4c7a4
Compare
Summary
Closes a three-way drift between the spec prose, the normative JSON Schema for
signatures.yaml, and the implementations that already produce and consume it. Also closes the composition-pack validation hole where the conformance runner hard-requiredevidence.mdfor every pack, rejecting composition packs that correctly use↔see_also relations instead.No downstream code needs to change — this is the spec catching up to reality.
What was drifting
ARCHIVE.md §4)algorithm,pack_hash,files,sealed_at,sealed_by, optionalparent, optionalsignature(object).kp-signatures.schema.json)algorithm+files, hassigned_at/signing_key/flat-stringsignature. Would reject any signatures.yaml the current sealer writes.pack_id(never documented anywhere in the spec).run.py)evidence.mdfor all packs.Changes in this PR
conformance/grammar/kp-signatures.schema.json— regenerated from ARCHIVE.md §4:algorithm(now"SHA-256"only),pack_hash,files,sealed_at,sealed_by.pack_id,pack_name,pack_version,parent(with requiredversion+pack_hash+ optionalmerge_parents),signature(with requiredmethod+value+key_id).additionalProperties: false(mirrors the manifest-schema pattern introduced in v0.7.4).filesmap carries apropertyNamesconstraint forbidding the key"signatures.yaml"— enforces the "MUST NOT include signatures.yaml itself" rule in schema, not prose.signed_at,signing_key, flat-stringsignature) that no current sealer writes.spec/ARCHIVE.md— documentspack_id,pack_name,pack_version,parent.merge_parents:pack_idmarked "New archives" (SHOULD be present from v0.7.5 onward) with the prose "Schema accepts absence for backwards compatibility with archives sealed beforepack_idwas documented. Readers MUST tolerate its absence."pack_nameandpack_versionmarked "Never" (purely optional self-describing receipts).parent.merge_parentsmarked "Never" — populated only when a version results from merging two or more lineages.spec/SPEC.md§2 — new "Composition-pack File Requirements" subsection:composition.yamlREQUIRED,claims.mdREQUIRED (intentionally minimal perCOMPOSITION.md§3),evidence.mdOPTIONAL.conformance/run.py:validate_pack()detects composition packs viacomposition.yamlpresence and skips theevidence.mdpresence check for them.evidence.md(empty set instead of read exception).signatures_schema()loads the signatures schema; when a pack containssignatures.yaml, its content is validated against that schema with a newsignatureserror category.jsonschema.FormatCheckeris enabled on signatures validation soformat: date-timeonsealed_atis actually enforced (without the format checker, that annotation is advisory only).None is not of type 'object'from jsonschema.Fixtures (
conformance/fixtures/valid/):composition.kpack(new) — minimum-shape composition pack: PACK.yaml + composition.yaml + claims.md (prose-only, no dense claim bullets). No evidence.md. Demonstrates the spec change end-to-end.maximal.kpack/signatures.yaml(new) — populated signatures envelope exercisingpack_id,pack_name,pack_version, and theparentblock. Drives the new schema-validation path inrun.py.Judgment calls + independent reads
Key decisions were consulted across two independent models (Gemini + a local code-oriented model; a third model got stuck on reasoning phase and was dropped). Consensus was:
pack_idrequired in schema? — OPTIONAL in schema, SHOULD-in-prose for new archives. Rationale: backward compatibility with archives sealed before the field was documented. The schema describes what is valid, the prose describes what is expected.COMPOSITION.mdfor semantics. Structural exceptions belong alongside structural rules.evidence.mdonly, or bothclaims.mdandevidence.md? — evidence.md only. COMPOSITION.md already assumesclaims.mdexists (minimal, about composition context). The concrete case that motivated this PR hasclaims.md. Narrower fix, fewer moving parts.Downstream impact — verified across consumers
src/pack/signatures.ts:11-44, 96-126)algorithm: "SHA-256"(uppercase),pack_idvia randomUUID, optionalpack_name/pack_version,pack_hash,files(lowercase hex via%02x),sealed_at(ISO 8601 no fractional),sealed_by, optionalparent{version, pack_hash}.src/server.ts:484, 520-528)claims.md(consistent with v0.7.5 SPEC.md §2, which keepsclaims.mdREQUIRED for composition packs too). Readspack_id,pack_hash,parent.pack_hash,sealed_at— all in the refreshed schema. Observer script doesn't touch signatures semantics.PackArchive.swift:163, 371-403;PackLoader.swift:44)pack_id. Readsevidence.mdas optional (already compliant with the composition-pack rule). Seal path writes lowercase hex (%02x). Minor product gap:IntegrityResulthas nopackName/packVersionfields, and nocomposition.yamlrenderer — both optional per spec, not violations.IntegrityResultpatch + future composition-yaml rendering) queued post-demo.repos/packs, 54 packs)sealed_at/sealed_bythat get overwritten during real sealing; 1× SC-03 orphan evidence ref). None caused by v0.7.5 changes.repos/packs.Three composition packs in the corpus (
bernard-investor-briefing.kpack,art-sanctions-roundtable-london-2026.kpack, and the new fixture) — all now validate. Pre-v0.7.5 they would have failed formissing evidence.md.Discovered drift that was NOT previously resolved, now addressed in this PR
parent.merge_parents— kp-forge'sPackSignaturesTypeScript declares it; the original closedparentschema rejected it. This PR now includes it as an optional array in both the schema and ARCHIVE.md §4. If kp-forge chooses to prune the unused type in a YAGNI cleanup later, the optional schema field simply becomes dead; no spec action needed.Deferred to a later revision (deliberately out-of-scope)
{0.95|o||2026-04-12}is currently a parse error inrun.py. Composition packs that express "evidence lives in the referenced pack" via↔see_also relations would benefit from this but don't strictly need it (bernard-investor-briefing uses[B###]claim IDs that the parser silently skips, sidestepping the issue). Left for a follow-up spec bump with a chosen placeholder syntax (e.g.,|-|or similar).COMPOSITION.md §3's example uses[M001]for meeting-pack claims; real composition packs use[B###]. The runner's_CLAIM_STARTregex only matches[C###], silently ignoring others. This PR's composition fixture intentionally uses a prose-onlyclaims.mdto avoid relying on that quirk. Decide in a follow-up: widen the regex to any single-letter prefix, or enforceConly.Verified locally
lefthook run pre-commit— all rules pass (secrets, deny-list, markdownlint, yaml-validate, large-files).python3 conformance/run.py— 13/13 passed (was 12/12;composition.kpackadded to the valid set).markdownlint '**/*.md'— clean.repos/packscorpus sweep — 51/54 pass, 3 pre-existing content failures unchanged from baseline.repos/kp-forge/src/pack/signatures.tsandrepos/kp-packs/src/server.ts— required/optional fields align with the refreshed schema.Dependency / stacking
Stacked on PR #9 (
devbranch, v0.7.4 manifest extensions). Until #9 merges, this diff shows both v0.7.4 and v0.7.5 content. After #9 merges to main, this narrows to the v0.7.5 delta.