Skip to content

feat(cli): freeze and version-stamp the --json contract (#19)#35

Merged
ben-laird merged 5 commits into
mainfrom
feat/issue-19-freeze-json-contract
Jun 8, 2026
Merged

feat(cli): freeze and version-stamp the --json contract (#19)#35
ben-laird merged 5 commits into
mainfrom
feat/issue-19-freeze-json-contract

Conversation

@ben-laird

Copy link
Copy Markdown
Owner

Closes #19.

Base: this PR targets the #18 branch (it embeds the Plan, which #18 extends with releaseNotes). Merge #18 (#34) first, then GitHub will retarget this to main automatically — or rebase onto main after #18 lands.

Every dv … --json output is now a frozen, versioned contract: it carries a schema URN and validates against a committed JSON Schema generated from a Zod source.

What changed

  • Central registry domain/schema-urns.ts (SCHEMA_URNS) — the single source of truth for every contract id. All the scattered hardcoded URN string literals (status/v1 Plan, validate, rename, migrate, plugin-list/verify/invoke, init) now reference it.
  • Closed the one stamping gap: dv release --json (RunReleaseResult) gained its missing envelope schema field; the no-op / dry-run / real paths now share one built result object.
  • Zod sources for all eight result envelopes in cli/schemas/json-contracts.ts, registered in the generator so these get committed + drift-gated:
    validation-report, release-result, rename-result, migrate-config-result, init-result, plugin-list-result, plugin-verify-result, plugin-invoke-result.
  • The freeze gate: assertVersionedSchemaUrns() runs inside the generator (so it fails both schemas:generate and schemas:check) and rejects any contract id missing the urn:dv:schema:vN: version prefix.
  • specs/schemas/README.md updated to document the envelope contracts and the generation/registry discipline.

Faithfulness note

The schemas mirror exactly what each command emits (nullable fields where the command writes ?? null, the dryRun field rename emits, etc.) — the committed schema, not the TS interface, is the frozen contract. The tests keep them aligned.

Tests

  • Freeze-gate test (schema-urns.test.ts).
  • URN ↔ Zod-schema ↔ committed-file correspondence for every envelope (json-contracts.test.ts).
  • dv release --json output validated against release-result schema across no-op / dry-run / real paths.
  • Manually verified real dv validate / dv plugin list output parses cleanly against the Zod schemas.

398 tests pass; schemas:check reports 13 files in sync; deno task verify green. A Record (feat@dv-cli/dv) is included.

🤖 Generated with Claude Code

ben-laird and others added 2 commits June 7, 2026 16:53
dv release runs after dv version consumed the Records, so it can't
re-render the notes. But dv owns the CHANGELOG format, so a first-party
extractor recovers them from the file dv itself wrote — instead of every
consumer rolling a fragile slice (the .github release script did exactly
that).

- New pure changelog/extract.ts (extractReleaseSection): the inverse of
  renderReleaseSection — slices the `## [version]` body back out.
- New required `releaseNotes` field on Plan.awaitingRelease entries
  (plan-schema.ts + regenerated specs/schemas/plan.json). Empty string
  when no section is found; never absent.
- release.ts populates it at the command edge (CHANGELOG IO stays out of
  the pure plan builder), so status/version Plans keep `""`.
- DRY: the duplicated resolveOutputPathFromTemplate in version.ts and
  v1.ts moves to the changelog subtool (changelog/path.ts), now shared by
  version, v1, and release.
- .github/scripts/release.ts drops its private readChangelogSection /
  findChangelogPath and reads entry.releaseNotes directly.

Tests: extract round-trip / EOF / missing-version; a release integration
test asserting notes are populated from the CHANGELOG (and "" when none).
deno task verify passes; 380 tests.

Closes #18.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Every dv --json output is now a frozen, versioned contract: it carries a
schema URN and validates against a committed JSON Schema generated from a
Zod source.

- New central registry domain/schema-urns.ts (SCHEMA_URNS) — the single
  source of truth for every contract id. All the scattered hardcoded URN
  string literals (status/v1 Plan, validate, rename, migrate, plugin-*,
  init) now reference it.
- dv release --json gained its previously-missing envelope schema stamp
  (RunReleaseResult.schema); the no-op / dry-run / real paths now share
  one built result object.
- New Zod sources for all eight --json result envelopes
  (cli/schemas/json-contracts.ts), registered in the generator so
  specs/schemas/{validation-report,release-result,rename-result,
  migrate-config-result,init-result,plugin-{list,verify,invoke}-result}.json
  are committed and covered by the schemas:check drift gate.
- Freeze gate: assertVersionedSchemaUrns() runs in the generator and
  rejects any contract id missing the urn:dv:schema:vN: version prefix.

Tests: freeze-gate test; URN ↔ Zod ↔ committed-file correspondence for
every envelope; release --json output validated against the release-result
schema. Verified real validate / plugin-list output conforms. 398 tests,
schemas in sync, deno task verify green.

Closes #19.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ben-laird ben-laird force-pushed the feat/issue-18-release-notes-json branch from e3d0b98 to 34c68e7 Compare June 8, 2026 01:42
Base automatically changed from feat/issue-18-release-notes-json to main June 8, 2026 01:44
ben-laird and others added 3 commits June 7, 2026 20:46
The new public export SCHEMA_URNS had an inferred (`as const`) type, which
`deno publish` rejects under the slow-types gate (missing-explicit-type).
Give it an explicit `SchemaUrns` interface, and make `urn()` return a
template-literal type so each member keeps its precise literal URN type —
consumers rely on those literals (`z.literal(SCHEMA_URNS.plan)`,
`schema: typeof SCHEMA_URNS.releaseResult`).

deno task publish:check now passes; full verify green (398 tests).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…19)

The slow-types fix made public result types (RunReleaseResult, Plan,
ValidationReport) reference `typeof SCHEMA_URNS` — a value — which
`deno doc --lint` rejects as a private-type-ref. Reference the public
`SchemaUrns` interface instead (`schema: SchemaUrns["releaseResult"]`),
export the interface + URN types from lib.ts so they're part of the
public API, and JSDoc each interface member (doc-lint requires it).

All CI gates pass locally: fmt, lint, check, test (398), schemas:check,
publish:check, doc:lint.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@ben-laird ben-laird merged commit 925d57c into main Jun 8, 2026
1 check passed
@ben-laird ben-laird deleted the feat/issue-19-freeze-json-contract branch June 8, 2026 14:23
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.

Freeze/version-stamp the --json contract

1 participant