feat(policy): shared vocabulary + canonicalizer for daemon AND web (#45, C.1) - #68
Merged
Merged
Conversation
…, C.1) The web editor kept hand-copied duplicates of the daemon's policy schema fragments: the match-path/select regexes in compile.ts and a second, "canonical-ish" reimplementation of canonicalize. Parity was asserted by comment and enforced only at the boundary (the daemon rejecting not_canonical) — and every future chain would multiply the copies. Now both sides consume the SAME source: - src/core/policy/vocabulary.ts (new, PURE — no imports, no Node APIs): the closed vocabularies as shared patterns — MATCH_PATH, SELECT_FIELD, RULE_ID, CHAIN_ID — consumed by schema.ts's Ajv schema on the daemon side and imported directly by the editor's compiler on the web side. - src/core/canonical/jcs.ts is now PURE: canonicalSha256Hex (node: crypto) moves to src/core/canonical/hash.ts. The web editor imports THE daemon's canonicalize — its local reimplementation is deleted, so the pushed policyjson is byte-identical to what verifyStoredPolicy re-canonicalizes, by construction. - web: a "@sbx-core" vite alias + tsconfig paths point into ../src/core (leaf, dependency-free modules only); PushModal/EditorView keep their imports through compile.ts's re-export. New test/vocabulary.test.ts pins regex ↔ validator parity (accepted and rejected match paths agree with validatePolicy's actual behavior), so a drift cannot reappear silently. 235 tests green · web build green. No behavior change on either side (the web canonicalize was behaviorally equivalent on policy shapes; now it is the same function). Part of #45 — foundation for the lossless editor work (#38). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb
This was referenced Aug 5, 2026
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.
PR C.1 of the implementation plan (Wave 1, Track C) — first task of #45. Independent of #67 (zero file overlap — mergeable in any order).
The problem it kills
The web editor hand-duplicated the daemon's schema fragments —
MATCH_PATH_RE/SELECT_FIELD_REregex copies incompile.tsand a secondcanonicalizeimplementation — with parity asserted by comment only. Every future chain would multiply the copies (the exact coupling debt flagged in #45).Single source now
src/core/policy/vocabulary.ts(new, pure — no imports, no Node APIs): the closed vocabularies (MATCH_PATH,SELECT_FIELD,RULE_ID,CHAIN_ID) as shared patterns. The daemon's Ajv schema consumes them; the editor's compiler imports them directly.src/core/canonical/jcs.tsis now pure:canonicalSha256Hex(node:crypto) moved tocanonical/hash.ts(2 importers updated). The web editor now imports the daemon'scanonicalize— its local reimplementation is deleted. The pushedpolicyjsonis byte-identical to whatverifyStoredPolicyre-canonicalizes by construction, not by boundary rejection.@sbx-corevite alias + tsconfigpathsinto../src/core, restricted to leaf dependency-free modules. Consumers (PushModal,EditorView) are untouched — they keep importing throughcompile.ts's re-export.Drift-proofing
New
test/vocabulary.test.ts: the shared regexes must accept/reject exactly whatvalidatePolicyaccepts/rejects (6 accepted + 6 rejected match paths, ids, select fields). A silent divergence between editor validation and daemon validation can't reappear.Numbers
235/235 root tests · web
tsc -b && vite build✅ · typecheck ✅. No behavior change on either side — the web canonicalize was behaviorally equivalent on policy shapes; now it's the same function.Noted for C.2 (next)
vocabulary.tsdocuments thatauthorization.*and the 64-hex chain id are the XPR dialect's vocabulary — thePolicyDialectextraction moves them behind the chain module while keeping daemon/web in lockstep through this same shared module.🤖 Generated with Claude Code
https://claude.ai/code/session_01RJ7hqFnC9Nnyf2Db2h3WTb