Skip to content

fix(lock-ts): normative 512 nesting cap + verify() error contract (DSE-1527) - #102

Merged
ernestprovo23 merged 3 commits into
mainfrom
ernest/dse-1527-lock-ts-nesting-cap
Sep 5, 2026
Merged

fix(lock-ts): normative 512 nesting cap + verify() error contract (DSE-1527)#102
ernestprovo23 merged 3 commits into
mainfrom
ernest/dse-1527-lock-ts-nesting-cap

Conversation

@ernestprovo23

@ernestprovo23 ernestprovo23 commented Sep 4, 2026

Copy link
Copy Markdown
Member

Closes the two Low residuals from the CSO re-verification of #99 (DSE-1527).

N1 — the two implementations did not share a nesting bound. @mcp-warden/lock refused JSON nested past 512 levels; the Python reference accepted anything up to the interpreter's ~1000-frame recursion limit (probed on the builder: 513–990 levels canonicalized fine, ~1000 raised). Two conforming implementations disagreed on the same document.

N2 — DepthError could escape verify() untyped, contradicting the "throws LockFormatError" contract.

What changed

  • docs/SPEC.md §4: normative nesting bound = 512 (root = 0, each enclosing array/object adds one); relying on a host recursion limit is explicitly non-conformant. §12.1 wording aligned.
  • hashing.py: MAX_CANON_DEPTH (distinct from content_models.MAX_JSON_DEPTH = 16, the content-envelope profile's bound), typed DepthError(ValueError), iterative check_depth(); canon() checks before serializing.
  • lockfile.read_lock(): depth check before schema validation, intelligible ValueError message.
  • vectors/: canonical/depth-512-accepted (513 arrays → deepest at 512, MUST canonicalize) and malformed/depth-513-rejected (514 arrays, MUST be refused). Regenerated deterministically — only the two new cases + manifest.json changed. Count 84 → 87 (14 canonical / 9 digest / 50 drift / 14 malformed).
  • packages/lock-ts verify(): wraps buildFromSurface + computeDrift; throws only LockFormatError (a JcsError/DepthError on the observed side no longer leaks). README table updated.
  • Python vector harness: every malformed lock now goes through read_lock() (the public reader) instead of WardenLock.model_validate, as vectors/README.md step 4 asks of every implementation.
  • Unit tests: exact-bound tests in test_hashing.py (512 ok / 513 refused, leaf counts, objects count, iterative check survives 5000-deep) and test_lockfile.py (control read, hostile read refused with the depth message, at-bound read never trips the depth check); TS tests for the exact canonicalizer bound and the verify() error contract (deep surface + unpaired surrogate).

Acceptance criteria

  • SPEC §4 names 512 as the normative maximum nesting depth.
  • Python canon/read_lock reject depth > 512 with a typed error; TS unchanged (same constant).
  • Vectors: depth-512 accepted and depth-513 rejected by both harnesses; manifest count updated.
  • verify() never throws anything but LockFormatError; covered by a test.
  • Python vector harness routes malformed through read_lock.
  • CI conformance job green (pending this PR's run).

Verification (dse-builder-01, py3.12, node 22)

  • Targeted: test_hashing + test_lockfile + test_spec_vectors114 passed
  • Full suite (venv on PATH, so the fixture-spawning tests use the right interpreter): 1099 passed, 2 skipped, 0 failed
  • ruff check . clean
  • TS package: npm ci + the package test script → 97/97
  • vectors/tools/generate.py regeneration: byte-identical for all 84 pre-existing cases.

CSO review fold-ins (GREEN at 6007c40; folded at 4e2764e)

Finding Fix Test
read_lock() could surface a bare RecursionError for a file nested past the interpreter's ~1000-frame limit (3.11), before check_depth() ever ran except (json.JSONDecodeError, RecursionError) → the documented ValueError new vector malformed/lock-depth-1200-rejected (raw lock_text, 1200 deep) — Python via read_lock(), TS via parseLock/verify — both harnesses
Constant name collided in spirit with content_models.MAX_JSON_DEPTH (16, content-envelope profile) renamed to MAX_CANON_DEPTH; SPEC §4 gains a sentence scoping the 512 bound to lock/surface canonicalization test_hashing / test_lockfile updated
verify() catch was broad enough to mask programming errors; DepthError import in the TS test was already present but not visible in the diff catch narrowed to JcsError/DepthError, re-thrown as LockFormatError with { cause }; anything else propagates test asserts cause instanceof JcsError

🤖 Generated with Claude Code

https://claude.ai/code/session_01Lu4ZLyWWL7TXMGYpNFU5Dc

…E-1527)

The TypeScript verifier refused JSON nested past 512 levels while the Python
reference accepted anything up to the interpreter's ~1000-frame recursion limit,
so two conforming implementations disagreed on the same document (CSO re-verify
of #99, N1). SPEC.md §4 now names 512 as the normative bound; canon() and
read_lock() enforce it with an explicit iterative check raising DepthError
(a ValueError); vectors gain canonical/depth-512-accepted and
malformed/depth-513-rejected so both harnesses pin the exact bound.

verify() in @mcp-warden/lock now throws only LockFormatError (N2): a
canonicalizer or depth failure on the observed surface is wrapped rather than
leaking as JcsError/DepthError. The Python vector harness routes every malformed
lock through read_lock(), the public reader.
… MAX_CANON_DEPTH, narrowed verify() catch

- read_lock(): a >~1000-deep file raises RecursionError from json.loads on 3.11
  before the depth check runs; map it to the documented ValueError. New vector
  malformed/lock-depth-1200-rejected (raw text) pins that BOTH readers refuse it.
- hashing.MAX_JSON_DEPTH -> MAX_CANON_DEPTH, distinct from content_models.
  MAX_JSON_DEPTH (16, content-envelope profile); SPEC §4 scopes the 512 bound.
- verify() folds only JcsError/DepthError into LockFormatError, with `cause`;
  anything else is a programming error and is not masked.
@ernestprovo23
ernestprovo23 merged commit cc3a673 into main Sep 5, 2026
11 checks passed
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.

1 participant