Skip to content

feat(spec): v0.7.5 — signatures schema alignment + composition-pack file requirements#12

Merged
tymofiy merged 2 commits into
mainfrom
feat/spec-v0.7.5
Apr 18, 2026
Merged

feat(spec): v0.7.5 — signatures schema alignment + composition-pack file requirements#12
tymofiy merged 2 commits into
mainfrom
feat/spec-v0.7.5

Conversation

@tymofiy
Copy link
Copy Markdown
Owner

@tymofiy tymofiy commented Apr 18, 2026

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-required evidence.md for 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

Surface State before this PR
Spec prose (ARCHIVE.md §4) v0.7.3 shape: algorithm, pack_hash, files, sealed_at, sealed_by, optional parent, optional signature (object).
Normative schema (kp-signatures.schema.json) Pre-v0.7.3 shape: required algorithm+files, has signed_at/signing_key/flat-string signature. Would reject any signatures.yaml the current sealer writes.
Implementations (kp-forge sealer + kp-packs relay) v0.7.3 shape + emit pack_id (never documented anywhere in the spec).
Conformance runner (run.py) Never invoked the signatures schema; hard-required evidence.md for all packs.

Changes in this PR

conformance/grammar/kp-signatures.schema.json — regenerated from ARCHIVE.md §4:

  • Required: algorithm (now "SHA-256" only), pack_hash, files, sealed_at, sealed_by.
  • Optional: pack_id, pack_name, pack_version, parent (with required version + pack_hash + optional merge_parents), signature (with required method + value + key_id).
  • Root closed with additionalProperties: false (mirrors the manifest-schema pattern introduced in v0.7.4).
  • files map carries a propertyNames constraint forbidding the key "signatures.yaml" — enforces the "MUST NOT include signatures.yaml itself" rule in schema, not prose.
  • Removed pre-v0.7.3 fields (signed_at, signing_key, flat-string signature) that no current sealer writes.

spec/ARCHIVE.md — documents pack_id, pack_name, pack_version, parent.merge_parents:

  • Added to the example YAML block and the field-types table.
  • In the required/optional table: pack_id marked "New archives" (SHOULD be present from v0.7.5 onward) with the prose "Schema accepts absence for backwards compatibility with archives sealed before pack_id was documented. Readers MUST tolerate its absence." pack_name and pack_version marked "Never" (purely optional self-describing receipts). parent.merge_parents marked "Never" — populated only when a version results from merging two or more lineages.

spec/SPEC.md §2 — new "Composition-pack File Requirements" subsection:

  • Table lists the file-presence rules for standard vs. composition packs.
  • Composition packs: composition.yaml REQUIRED, claims.md REQUIRED (intentionally minimal per COMPOSITION.md §3), evidence.md OPTIONAL.
  • Explicit rule: composition packs MUST NOT redefine the claims or evidence of referenced packs.

conformance/run.py:

  • validate_pack() detects composition packs via composition.yaml presence and skips the evidence.md presence check for them.
  • Evidence-ID extraction tolerates a missing evidence.md (empty set instead of read exception).
  • New helper signatures_schema() loads the signatures schema; when a pack contains signatures.yaml, its content is validated against that schema with a new signatures error category.
  • jsonschema.FormatChecker is enabled on signatures validation so format: date-time on sealed_at is actually enforced (without the format checker, that annotation is advisory only).
  • Empty-file check before schema validation so the user sees "signatures.yaml is empty" instead of an obscure 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 exercising pack_id, pack_name, pack_version, and the parent block. Drives the new schema-validation path in run.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_id required 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.
  • Subsection location? — SPEC.md §2 (File Structure), with a pointer into COMPOSITION.md for semantics. Structural exceptions belong alongside structural rules.
  • Relax evidence.md only, or both claims.md and evidence.md?evidence.md only. COMPOSITION.md already assumes claims.md exists (minimal, about composition context). The concrete case that motivated this PR has claims.md. Narrower fix, fewer moving parts.

Downstream impact — verified across consumers

Consumer State with v0.7.5 Code change required
kp-forge sealer (src/pack/signatures.ts:11-44, 96-126) Writes exactly what the refreshed schema requires — algorithm: "SHA-256" (uppercase), pack_id via randomUUID, optional pack_name/pack_version, pack_hash, files (lowercase hex via %02x), sealed_at (ISO 8601 no fractional), sealed_by, optional parent{version, pack_hash}. None — spec catches up to sealer.
kp-packs relay (src/server.ts:484, 520-528) Hard-requires claims.md (consistent with v0.7.5 SPEC.md §2, which keeps claims.md REQUIRED for composition packs too). Reads pack_id, pack_hash, parent.pack_hash, sealed_at — all in the refreshed schema. Observer script doesn't touch signatures semantics. None.
kp-viewer macOS binary (PackArchive.swift:163, 371-403; PackLoader.swift:44) Tolerates missing pack_id. Reads evidence.md as optional (already compliant with the composition-pack rule). Seal path writes lowercase hex (%02x). Minor product gap: IntegrityResult has no packName/packVersion fields, and no composition.yaml renderer — both optional per spec, not violations. None for compliance; optional viewer UX work (10-line IntegrityResult patch + future composition-yaml rendering) queued post-demo.
intel / providers Doesn't touch signatures surface. None.
Existing packs (repos/packs, 54 packs) 51/54 validate against the updated v0.7.5 validator. 3 pre-existing failures are content issues (2× pre-seal stubs with null sealed_at/sealed_by that get overwritten during real sealing; 1× SC-03 orphan evidence ref). None caused by v0.7.5 changes. Content cleanup in a separate PR against 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 for missing evidence.md.

Discovered drift that was NOT previously resolved, now addressed in this PR

  • parent.merge_parents — kp-forge's PackSignatures TypeScript declares it; the original closed parent schema 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)

  • Empty evidence slots in dense claims{0.95|o||2026-04-12} is currently a parse error in run.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).
  • Non-C claim ID prefixesCOMPOSITION.md §3's example uses [M001] for meeting-pack claims; real composition packs use [B###]. The runner's _CLAIM_START regex only matches [C###], silently ignoring others. This PR's composition fixture intentionally uses a prose-only claims.md to avoid relying on that quirk. Decide in a follow-up: widen the regex to any single-letter prefix, or enforce C only.

Verified locally

  • lefthook run pre-commit — all rules pass (secrets, deny-list, markdownlint, yaml-validate, large-files).
  • python3 conformance/run.py13/13 passed (was 12/12; composition.kpack added to the valid set).
  • markdownlint '**/*.md' — clean.
  • Full repos/packs corpus sweep — 51/54 pass, 3 pre-existing content failures unchanged from baseline.
  • Cross-references in SPEC.md still resolve (§3.1 Channels, §3.2 Manifest Extensions, new Composition-pack subsection in §2).
  • Sanity check against repos/kp-forge/src/pack/signatures.ts and repos/kp-packs/src/server.ts — required/optional fields align with the refreshed schema.

Dependency / stacking

Stacked on PR #9 (dev branch, 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.

Copilot AI review requested due to automatic review settings April 18, 2026 12:18
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.json for any signatures.yaml found during conformance runs.
  • Adds explicit composition-pack file presence rules in the spec and updates the conformance runner to not hard-require evidence.md when composition.yaml is 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.

Comment thread lefthook.yml
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
Comment thread conformance/run.py Outdated
Comment on lines +183 to +186
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}$"
},
Comment thread spec/SPEC.md Outdated
| `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 |
Comment thread lefthook.yml

# 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
Copy link
Copy Markdown

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment thread spec/SPEC.md Outdated
| `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 |
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment thread conformance/run.py Outdated
errs.append(Err("signatures", f"signatures.yaml parse error: {e}"))
else:
try:
jsonschema.validate(sigs, signatures_schema())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

tymofiy and others added 2 commits April 18, 2026 08:41
…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>
@tymofiy tymofiy merged commit e8a265b into main Apr 18, 2026
1 check passed
@tymofiy tymofiy deleted the feat/spec-v0.7.5 branch April 18, 2026 12:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants