Release: v2.5.0#264
Merged
Merged
Conversation
…ruction New specs/behaviors/contracts.md: a sheet may declare `implements` on named, versioned, immutable JSON Schema contracts. Contracts are vendored in canonical TOML under .gitsheets/contracts/ — the vendored bytes are simultaneously the lock state and the enforced artifact (composed via allOf into write-time validation), so producer conformance is by construction and identity is a content hash, not a recorded claim. Consumers verify via a two-rung ladder: declared identity (fast, guarantees future writes) falling back to structural record validation (works against any sheet, guarantees the present). Amendments: validation.md layer-1 composition; repository.md openSheet contract option + verification modes; cli.md `git sheet contracts` command group + exit code 67; errors.md ContractError + codes + ValidationIssue contract/record fields; concepts.md Contract entry; deferred.md entries for source shorthand/vanity resolution, registry-as-gitsheet + succession metadata, $ref closure vendoring, and sheet-level assertions. Motivating consumer pair: a generic toolkit module reading a meal-bank sheet from a personal repo via instance configuration — cross-system sheet consumption as a checked interface instead of a hopeful convention. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Three plans bridging specs/behaviors/contracts.md to code: contracts-core (declaration parsing, vendored store, allOf enforcement, canonical-hash primitive) unblocking contracts-cli (adopt/verify/test/sync/export/prune + sources sidecar) and contracts-consumer-verify (openSheet two-rung ladder, conformance report, advisory drift) in parallel. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
specs+plans: schema contracts (implements, vendored canonical-TOML lock, two-rung consumer verification)
Author
Changelog- specs+plans: schema contracts (implements, vendored canonical-TOML lock, two-rung consumer verification) [#263] @themightychris
- feat(core): schema contracts — implements, vendored store, composed enforcement [#265] @themightychris
- feat: consumer-side contract verification — the two-rung ladder in openSheet [#267] @themightychris
- feat(cli): git sheet contracts — adopt, verify, test, sync, export, prune [#268] @themightychris
- docs: schema contracts guide + contract attribution on write-failure output [#269] @themightychris
- chore(napi): sync manifests to published @gitsheets/core-napi 0.4.0 [#270] @themightychris |
…osition Core mechanics of specs/behaviors/contracts.md in gitsheets-core: - `gitsheet.implements` config key, validated against the name rules (host-qualified, path-segment character rules, no `.`/`..`, no trailing slash) — a new `contract` module owns name validation, the derived `.gitsheets/contracts/<name>.toml` path, loading + document-requirement checks (Draft-07 strict compile, self-contained, open, TOML-data-model-only, `$id`↔path, canonical bytes), and the `canonical_contract_hash` identity primitive (data / JSON text / TOML text → canonical TOML → SHA-256 hex). - Effective-schema composition: `allOf: [<each declared contract>, [gitsheet.schema]]`, compiled once per sheet exactly like a bare `[gitsheet.schema]` when `implements` is empty (byte/behavior-identical to before contracts existed). `ValidationIssue` gained a `contract` field, attributed from the failing `allOf` branch index. - `ContractError` variants (`contract_missing`, `contract_invalid`, and the reserved `contract_unsatisfied` code for the later consumer-verification plan) in the core error taxonomy. - The unknown-keyword strict-mode walker in `validation.rs` is refactored into a generic `walk_schema` so the contract document-requirement checks (self-containment, openness, null-bearing keywords) reuse its exact "never misread a data position as a keyword" descent rules. Also pins `rust` via asdf (`asdf set rust 1.94.1`) since `rust/.tool-versions` had no entry. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Adds the napi surface for schema contracts, riding the existing generic structured-error marshalling (no new per-class plumbing needed beyond the `contract` payload field on both the top-level error and each `ValidationIssue`): - `canonicalContractHash(input, format?)` — the identity primitive, accepting already-parsed data or a string (`format: 'json' | 'toml'` required for a string input). - `contract` on `JsValidationIssue` and on thrown structured errors. - `binding.cjs` gains a `ContractError` class and wraps the new function. test/contracts.mjs proves sheet-open `contract_missing`/`contract_invalid`, `allOf` composition naming the failing contract on write, and `canonicalContractHash` agreement across data/JSON/TOML input. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
The Python analogue of the napi contracts surface: a `ContractError` exception class wired into the existing `raise_core_error` mapping (which already carries `issues`/`conflicting_paths`; now also `contract` on both the exception and each issue dict), and `canonical_contract_hash(input, format=None)` — parsed data or a `str` with `format='json'|'toml'`. tests/test_contracts.py covers sheet-open contract_missing/contract_invalid, allOf composition naming the contract, and hash agreement across input forms. test_cross_binding.py gains a Node↔Python canonical_contract_hash parity proof, extending _node_writer.mjs with a `contract-hash` op. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Extends the `gitsheets` npm package's error taxonomy and public surface for schema contracts: - `ContractError` (codes `contract_missing`/`contract_invalid`/ `contract_unsatisfied`), carrying optional `contract` and `issues` — mapped from the core's structured `gitsheetsClass: 'ContractError'` in `core.ts`. - `ValidationIssue.contract`, threaded straight through from the core (no extra mapping needed — `ValidationError.issues` already passes the raw per-issue object through unchanged). - `canonicalContractHash(input, options?)` in a new `contracts.ts` — the one new public entry point contracts add; `implements` declaration and enforced composition ride the existing `openSheet`/`upsert` path with no new API. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
feat(core): schema contracts — implements, vendored store, composed enforcement
Adds `verify_sheet_contract(repo, tree, open_root, sheet, document, mode)` (rust/gitsheets-core/src/contract.rs), implementing specs/behaviors/contracts.md "Consumer verification": - Rung 1 (declared identity): the sheet's `implements` names the document's contract (derived from its `$id`) AND the vendored bytes at the derived path are canonical-hash-identical to the consumer's document. Pass ⇒ `Rung::Declared`, zero records read. - Rung 2 (structural): every record validates against the consumer's document ALONE (never composed with the sheet's own schema/contracts — pure duck typing). Pass ⇒ `Rung::Structural`. - Modes: `Verify` (rung 1 → rung 2, default), `Declared` (rung 1 only, fails fast, never reads records), `Structural` (rung 2 only, ignores declarations). - Both rungs miss ⇒ `Error::ContractUnsatisfied` carrying the conformance report — per-record, per-field `ValidationIssue`s for a rung-2 miss, empty `issues` for a `Declared`-mode miss. `ValidationIssue` gains a `record: Option<String>` field (the record's sheet-relative path in a multi-record conformance report) — `None` for every existing single-record write-time issue. `canonical_contract_hash` is refactored to share its parse step (`resolve_contract_document`) with the new verification path, so both build on the identical canonicalize-then-hash primitive. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Exposes `gitsheets_core::verify_sheet_contract` at the napi boundary:
- `verifySheetContract(gitDir, treeRef, sheetName, configPath, openRoot,
prefix, schema, format?, mode?)` — opens the sheet read-only (config read +
effective-schema compile, same as any other read) and runs the two-rung
ladder against `schema` (parsed data, or text with `format` naming which
form — mirrors `canonicalContractHash`'s no-auto-detection input handling).
Returns `{ name, rung, conforming, issues }`; a verification failure
surfaces as the existing generic `ContractError(contract_unsatisfied)`
marshalling, now carrying `issue.record` alongside `issue.contract`.
- `JsValidationIssue` gains `record`; `throw_structured_error` and the shared
`issue_to_js` marshal it through.
- `binding.cjs` wraps and re-exports the new function for the boundary suite.
test/contracts.mjs adds direct napi-level coverage: rung-1 pass, declared
mode fails fast without reading records, structural mode duck-types a
contract-unaware sheet, and rung-2 failure issue quality (record + field +
contract).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Wires the two-rung ladder into the public library surface:
- `openSheet(name, { contract: { schema, format?, mode?, onDrift? } })` —
verifies the sheet against a contract document the consumer holds before
the handle is returned; throws `ContractError('contract_unsatisfied')` on
failure. `schema` accepts parsed data or JSON/TOML text (`format` required
for text, matching `canonicalContractHash`'s no-auto-detection rule — a
minimal spec amendment to specs/api/repository.md, which named `schema`
but not the text-format companion).
- `sheet.contractVerification` — `{ name, rung, tree, conforming, issues }`
on success; `undefined` when no `contract` option was given.
- Advisory drift: for rung-2 (structural) verified sheets with an `onDrift`
callback, `Sheet.rebindReadTree` (the freshness rebind path) schedules a
lazy, fire-and-forget re-verification against the new tree; a regression
invokes `onDrift` with the regressed report. Reads are never gated on
this — a non-`ContractError` from the re-check (e.g. a transient read
issue) is swallowed, not surfaced. No `onDrift` registered ⇒ no
re-validation work.
- `ValidationIssue.record` — the record path in a multi-record conformance
report.
- New types in contracts.ts: `ConformanceReport`, `ContractVerificationMode`,
`OpenSheetContractOptions`.
contract-verification.test.ts covers the plan's validation list: rung-1 pass
with zero record reads (a garbage record file that would blow up parsing if
touched), rung-1 miss falling through to a rung-2 pass (a newer,
data-compatible producer version), rung-2 failure report quality (record +
field + contract), `declared` mode failing fast, `structural` mode
duck-typing a contract-unaware sheet, the drift callback firing after a
non-conforming commit while reads still succeed, and the motivating
end-to-end meal-bank scenario.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
specs/behaviors/contracts.md and the plan's Approach both say the consumer document input "match[es] canonicalContractHash's input handling" — but that primitive requires an explicit `format: 'json' | 'toml'` when the input is a string (no auto-detection), and api/repository.md's `opts.contract` example never listed it. Adds `format?` alongside `schema`, and expands `sheet.contractVerification`'s documented shape to the full conformance-report fields (`conforming`/`issues`) the implementation returns, matching what `onDrift` receives on a regression. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
feat: consumer-side contract verification — the two-rung ladder in openSheet
…contract_load, delete_file The `git sheet contracts` CLI (adopt/verify/test/sync/export/prune) needs a few thin surfaces the core doesn't expose yet, reusing the exact mechanics gitsheets-core already implements rather than re-checking anything host-side: - `check_contract_document` (core, contract.rs): wraps the existing private `check_document_requirements` + `reject_unknown_keywords` for a NOT-yet- vendored candidate document — `adopt`'s pre-vendor gate. - napi wrappers: `validateContractName`, `contractPath`, `checkContractDocument`, `contractLoad` (the last runs `load_contract` end-to-end: byte-canonical + document-requirement + $id-path checks against the committed tree). - `checkContractDocument` takes `Either<String, JsValue> + format`, mirroring `canonicalContractHash` — a JSON-text input is parsed directly via `serde_json::from_str` (preserving a literal `null`), deliberately NOT via the `JsValue` marshalling boundary, which silently drops null-valued keys per the documented type-fidelity rules. Requirement 4 (no null-bearing keywords) can only ever be violated by a JSON-sourced document, so routing through the null-dropping path would make that check unreachable for exactly the input shape it exists to catch. - `value_to_json` (validation.rs) widened from `pub(crate)` to `pub` so the napi crate can do this conversion for TOML-text/already-parsed-data input. - `CoreTransaction.delete_file` / `Transaction.deleteFile` (napi + JS): the inverse of the existing `write_file`, for `contracts prune` to remove vendored documents. Deep paths go through `MutableTree::delete_child_deep`. - `gitsheets-napi` gained a direct `serde_json` dependency (previously only transitive via gitsheets-core) for the above. Regenerated `rust/gitsheets-napi/index.d.ts`/`index.js` via `napi build`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
…rune Implements the `git sheet contracts <subcommand>` command group per specs/api/cli.md and specs/behaviors/contracts.md, riding contracts-core's loader/validation primitives (PR #265) plus the new thin napi surfaces added in the prior commit: - `adopt <source> [--sheet <name>]...` — reads a local file, an `https://` URL (one-shot fetch, 15s timeout), or `-` (stdin); JSON or TOML. Derives the contract name from `$id`, validates it, checks the document requirements, canonicalizes through the same encoder records use, and vendors it at the derived path. Records provenance in `.gitsheets/contracts/sources.toml` (omitted for stdin input — non-load-bearing, no loss of function). With `--sheet`, builds the would-be effective schema (existing declared contracts + the candidate + the sheet's local schema) and validates every existing record against it, streaming per-record issues to stderr and refusing to write anything on any failure. - `verify [<sheet>]...` — the offline CI gate: every declared contract resolves + is document-valid + canonical + $id-path-consistent (via `contractLoad`/`load_contract`), every record validates against the effective schema, and a closed local schema (`additionalProperties: false`) warns without failing. - `test <sheet> --against <file-or-name>` — rung-2 structural check: records against the target document ALONE, not the effective schema. Works on sheets that declare nothing. - `sync [<name>]...` — re-fetches each contract's recorded source and reports match/drift/unsyncable/missing; never rewrites the vendored file. - `export <name>` — vendored contract as interchange JSON on stdout. - `prune [--dry-run] [--yes]` — lists (git ls-tree over `.gitsheets/contracts`) vendored documents no sheet declares; removal prompts for confirmation unless `--yes` (added for scriptability; not in the original spec text — the spec only says "needs confirmation" without dictating the mechanism). - `ContractError` wired to exit 67 (was missing from `exitCodeForError`); `reportError` now also prints a `ContractError`'s attached `issues`. - `GlobalArgs`/`buildTxOpts` extracted to a new `cli/shared.ts` (used by both `index.ts` and `contracts.ts`) to avoid a circular import between them. The one piece of "logic" assembled host-side rather than in the core is the effective-schema `allOf` array — a documented, mechanical formula (specs/behaviors/contracts.md "Composition and enforcement"), validated through the existing `validateBatch`, not a re-implementation of anything. Tests (packages/gitsheets/src/cli/cli-contracts.test.ts) cover every Validation bullet in plans/contracts-cli.md: adopt from a local JSON file and a local TOML file producing byte-identical vendored bytes, adopt over HTTPS with `fetch` mocked (no real network in unit tests — see the plan's Risks note), `--sheet` refusal leaving the tree untouched, `verify` passing + contract_missing + non-canonical-vendored (contract_invalid) + a record violation + the closed-local-schema warning, `test` passing and failing against a contract-unaware sheet, `sync` drift detection without rewriting, `export | adopt -` byte-identical round-trip, and `prune --dry-run`/`--yes`. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
feat(cli): git sheet contracts — adopt, verify, test, sync, export, prune
New docs/contracts.md — the full producer→consumer tour (declare + adopt, composed enforcement, the two-rung verification ladder, evolution patterns, housekeeping), with every command output pasted from a live run against the shipped CLI. Touch-ups: cli.md command group + exit 67, api.md surface/errors/ canonicalContractHash, concepts.md Contract entry, mkdocs nav. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
The core tags a failing allOf branch with its contract name (specs/behaviors/contracts.md "a failing write says which obligation it violated") and the ContractError printer already rendered it, but the ValidationError printer dropped it — an upsert rejected by a contract looked identical to one rejected by the local schema. Append the [contract-name] tag when present; verified live against a declaring sheet. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
docs: schema contracts guide + contract attribution on write-failure output
Post-publish sync per the release rules in CLAUDE.md (napi versions are tag-stamped at publish, never pre-committed). Generated by: npm pkg set version=0.4.0 (+ platform optionalDependencies) in rust/gitsheets-napi npm install @gitsheets/core-napi@^0.4.0 -w gitsheets Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KJCxogamrSUZ7etFGsnoD1
…sync chore(napi): sync manifests to published @gitsheets/core-napi 0.4.0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Schema contracts: sheets can now declare
implementson named, versioned, immutable JSON Schema contracts — vendored into the repo as the canonical lock artifact, enforced by construction on every write, and mechanically verifiable by cross-system consumers (openSheet({ contract }),gitsheets contracts). See the new contracts guide.Improvements
Technical