From 14bc1ff97af7de9ce0330a1723f452ec89741fbd Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 09:55:05 -0700 Subject: [PATCH 01/16] docs: set v18 Continuum compatibility bearing --- docs/BEARING.md | 216 ++++++++++++++++++++++-------------------------- 1 file changed, 99 insertions(+), 117 deletions(-) diff --git a/docs/BEARING.md b/docs/BEARING.md index 791daa29..01215f5e 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -1,6 +1,6 @@ # BEARING -Updated at cycle boundaries. Not mid-cycle. +Updated at cycle boundaries and before the final commit of each v18 slice. Scope note: @@ -14,62 +14,55 @@ Scope note: ## Where are we -`git-warp` is executing the v17 release-blocker DAG. The current work is -not a broad RuntimeHost rewrite; it is one bounded release blocker at a -time, with the DAG status and SVG kept current after each completed -cycle. +`git-warp` has shipped `v17.0.0`. The release work is now behind us in +repo history, npm, and JSR; the active direction is `v18.0.0`. + +The v18 hill is not generic graph-substrate cleanup. It is Continuum +compatibility: + +> Make `git-warp` a Continuum-compatible sibling WARP runtime: consume +> Wesley-generated artifacts for Continuum-owned contract families, map +> git-warp's append-only Git substrate into honest WARP Optic evidence, and +> give `warp-ttd` generated-family facts instead of handwritten adapter +> folklore. + +The long-term compatibility target is the WARP Optic shape described in +`~/git/blog/aion-paper-07/dist/aion-paper-07.txt`, plus the Continuum +contract families authored in `~/git/continuum/schemas/` and compiled by +Wesley. Echo and `git-warp` are sibling runtime implementations. `git-warp` +must not pretend to be Echo's durable half, and it must not emit +Continuum-shaped values as native Continuum witnesses until that witnesshood is +actually proven. Current branch state at this boundary: -- Branch: `release/v17.0.0` -- Push state: local branch remains ahead of `origin/release/v17.0.0`; - push only after explicit approval. -- DAG map: - [0124-v17-release-blocker-dag.md](design/0124-v17-release-blocker-dag.md) +- Branch: `main` +- Release tag: `v17.0.0` +- Latest remote head inspected: `origin/main` at `5afdd3eb` +- Latest package version: `17.0.0` - Latest closed cycle: - `0144-release-preflight-and-rc` -- Latest full unit gate shape: - `npm run test:local` is green with `438` files and `6771` tests. -- Latest validation shape: - lint, anti-sludge shell checks, source/test typecheck, consumer - typecheck, markdown lint, markdown code-sample lint, high-level npm - audit, release preflight, and whitespace diff checks are green at this - boundary. - -The current release ladder remains: - -- `v17.0.0`: TypeScript migration, public API honesty, - materialization-frontdoor deletion, readings/optics direction, and - query read-model groundwork -- `v18.0.0`: graph substrate convergence + `0144-release-preflight-and-rc`; `0145-push-pr-review-merge` still needs + closeout bookkeeping. +- Local git status note: + `core.fsmonitor=true` currently emits `fsmonitor_ipc__send_query` warnings + during status/diff commands. + +The release ladder is now: + +- `v17.0.0`: shipped TypeScript migration, public API honesty, + materialization-frontdoor deletion, readings/optics direction, and query + read-model groundwork. +- `v18.0.0`: Continuum/WARP Optic compatibility for the cold Git substrate, + through Wesley-generated contract-family artifacts and honest evidence + posture. - `v19.0.0`: observation, doctrine, and slice-first runtime convergence - `v20.0.0`: slice-first read execution - `v21.0.0`: distributed observer geometry and admission reality -Recent work narrowed v17 honestly, removed public materialization -frontdoor docs, fixed runtime read guidance, made checkpoint schema `5` -the single runtime checkpoint contract, removed the checkpoint, patch, -subscription, and sync controller private materialization dependencies, -retired stale materialize-spy expectations, pinned default observer -readings, and aligned remaining checkpoint/materialize unit tests with -the current checkpoint contract, and replaced plain sync HMAC credential -flow with an opaque `SyncSecret`. The sync server now fails closed for -non-local unauthenticated serving and requires an explicit unsafe option -for unauthenticated localhost serving. It also applies per-key token-bucket -rate limiting for configured sync auth and requires an explicit rate-limit -budget for non-local enforced sync auth. The package upgrade command now has -a real checkpoint upgrade path for retired checkpoint envelopes. Unexpected -HTTP sync `500` responses are now sanitized to `E_SYNC_INTERNAL`, with -internal details kept in structured logs. - -The runtime is still partially state-first in important places. The -important current truth is narrow: the non-security `test:local` blockers -from the v17 materialization cleanup and the direct sync security hardening -nodes are closed. File-level anti-sludge quarantines are also graduated, and -the full gate matrix is green, and the release cut/version/changelog node is -closed. Final local release preflight is also green. The remaining blocker is -release coordination: push, PR, review, green CI, and an explicit merge -decision. +The v18 compatibility work is bigger than ten slices. The first ten slices are +the opening campaign. Slice 11 is an explicit re-plan point after the repo has +real evidence from generated artifact ingestion, evidence posture, and the +first receipt-family projection. ## Invariants @@ -109,80 +102,69 @@ mapping, and concrete checks live in `docs/invariants/`. ## What just shipped -Cycles `0132-subscription-controller-reading-basis` through -`0144-release-preflight-and-rc`: - -- Removed `_materializeGraph()` from subscription/watch and sync - controller read paths. -- Made default sync metadata-only unless callers explicitly request - `materialize: true`. -- Rewrote stale auto-materialize and materialize-spy tests around the v17 - reading-basis contract. -- Pinned default `graph.observer()` reads to the caller's fresh reading - basis. -- Aligned remaining checkpoint/materialize tests with schema `5` or - explicit retired-schema upgrade rejection. -- Added `SyncSecret` so sync auth secrets redact in string, JSON, and - inspect output while still signing HMAC requests. -- Hardened sync serve defaults: non-local bind hosts require enforced - auth, and local unauthenticated serving must opt into unsafe localhost - mode. -- Added per-key token-bucket sync auth rate limiting and required explicit - `auth.rateLimit` for non-local enforced sync hosts. -- Sanitized unexpected HTTP sync `500` responses and routed internal error - detail through `LoggerPort`. -- Graduated the anti-sludge file-level quarantine manifests to empty - `files` lists and narrowed remaining legacy hits to owning-cycle inline - suppressions. -- Recorded the full gate matrix green after quarantine graduation. -- Cut the v17.0.0 changelog section for May 5 and aligned the release note with - the honest 0123 bounded-query scope. -- Cleared the local release preflight from a clean commit. The hard preflight - repairs landed in `bdafca51`, and the final preflight reports all hard checks - passed. -- Brought `npm run test:local` back to green. -- Marked `PORT_subscription-controller-reading-basis`, - `PORT_sync-controller-reading-basis`, - `SPEC_materialize-spy-test-clusters`, - `SPEC_observer-coordinate-pinning`, and - `SPEC_checkpoint-materialize-test-drift` complete in the DAG, then - marked `HEX_sync-secret-plain-string` and - `HEX_sync-production-auth-defaults` complete, then marked - `HEX_sync-no-rate-limiting`, `HEX_sync-500-sanitization`, and - `REL_quarantine-graduate-clean`, then - `REL_full-gate-matrix-green`, then - `REL_release-cut-version-changelog`, then - `REL_release-preflight-and-rc` complete. +`v17.0.0` shipped and was followed by release hardening: + +- The v17 release branch landed through PR #84. +- Follow-up repair and package migration work landed through PR #85. +- Release hardening landed through PR #86. +- The final v17 coverage ratchet landed through PR #87; the signed + `v17.0.0` tag points at that merge. +- npm publish recovery landed through PR #88. +- PR #89 simplified the README model sentence after the release line. + +The shipped v17 scope remains: TypeScript migration, public API honesty, +materialization-frontdoor deletion, readings/optics direction, query +read-model groundwork, sync hardening, release gates, and package publishing. ## What feels wrong -- v17 is still not releasable until the branch is pushed, reviewed, green in - CI, and explicitly approved for merge. -- `REL_push-pr-review-merge` is now the open node. +- `0145-push-pr-review-merge` still needs closeout bookkeeping even though the + release branch has already landed and the package is published. +- The local git fsmonitor warning adds noise to status and diff commands. - The release preflight fix lowered the coverage ratchet to the measured full-suite v17 line baseline `91.74%`; this is tracked as v19 bad-code debt in `SPEC_coverage-ratchet-baseline-drop.md`. -- Broader historical version-suffixed substrate names still exist in - `src/`; the checkpoint upgrade slice removed the touched checkpoint and - migration names only. -- The branch remains local-only relative to origin; pushing is a separate - release/coordination decision. +- v18 can easily turn into adapter folklore if `git-warp` hand-authors local + mirrors of Continuum-owned families instead of consuming Wesley-generated + artifacts. +- v18 can also lie in the other direction: Continuum-shaped values are not + Continuum-native witnesses unless the runtime has actually proven native + witnesshood. Initial git-warp compatibility evidence should be treated as + translated substrate evidence until stronger proof exists. +- `warp-ttd` needs git-warp facts as generated-family nouns, but the existing + ecosystem still contains handwritten adapter and protocol residue. ## What comes next -Continue executing the DAG one open node at a time. - -Recommended next pull: - -- `REL_push-pr-review-merge` - -Why: - -- It is open. -- The full gate matrix, release cut, and local preflight are green. -- The branch is still local-only relative to origin. -- Merge must remain gated on review, green CI, and explicit human approval. - -Keep the loop strict: write the cycle doc, capture RED, green the slice, -update changelog/DAG/SVG/retro, validate, commit, then pull the next open -node. +Run the v18 opening campaign. Update this task list at the end of each slice, +before the final commit for that slice, and mark completed items with `- [x]`. + +## Running Task List + +- [ ] 1. Sync and clean the repo runway: fast-forward `main`, clear fsmonitor + noise, close stale v17/0145 bookkeeping, and record the v18 starting point. +- [ ] 2. Create the v18 Continuum compatibility charter: WARP Optic + compatibility, Continuum contract-family compatibility, Wesley-generated + artifact consumption, and `warp-ttd` acceptance. +- [ ] 3. Build the cross-repo contract matrix: Continuum family to Wesley + generated artifact to git-warp source fact to `warp-ttd` consumer need. +- [ ] 4. Define git-warp's WARP Optic realization map: observer plan, bounded + slice, lowering surface, admissibility law, and retention contract. +- [ ] 5. Add a generated-artifact ingestion path for Continuum families, with a + guard against handwritten local mirrors becoming contract authority. +- [ ] 6. Make evidence posture explicit: translated substrate evidence first, + native Continuum evidence only after native witnesshood is proven. +- [ ] 7. Prove the patch commit visibility contract: success means canonical + writer-tip advancement and visible graph truth, not just object creation. +- [ ] 8. Add the same-writer concurrent patch race witness with final-frontier + and visible-state assertions. +- [ ] 9. Project git-warp receipt facts into the generated Continuum + receipt-family shape with conformance tests. +- [ ] 10. Add the first `warp-ttd` smoke over generated-family git-warp receipt + facts instead of handwritten adapter-local receipt folklore. +- [ ] 11. Re-plan with evidence in hand before expanding into reading-envelope, + suffix/runtime-boundary, neighborhood-core, and settlement-family slices. + +The loop stays strict: write or update the cycle doc, capture RED, green the +slice, update this BEARING task list before the final commit, validate, then +commit only the files touched in that slice. From dbe498a45c681ae8d2fc67aa5cf39d5c5a5f6329 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 10:00:32 -0700 Subject: [PATCH 02/16] docs: close v17 release runway --- docs/BEARING.md | 23 ++++++----- docs/design/0124-v17-release-blocker-dag.dot | 6 ++- docs/design/0124-v17-release-blocker-dag.md | 12 +++--- docs/design/0124-v17-release-blocker-dag.svg | 8 ++-- .../0124-v17-release-blocker-status.csv | 2 +- .../push-pr-review-merge.md | 27 ++++++++++--- .../push-pr-review-merge.md | 38 +++++++++++++++++++ 7 files changed, 88 insertions(+), 28 deletions(-) create mode 100644 docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md diff --git a/docs/BEARING.md b/docs/BEARING.md index 01215f5e..910bac19 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -12,6 +12,16 @@ Scope note: - For later-major horizon planning, use [release-horizon-v20-v21.md](design/release-horizon-v20-v21.md). +## Continuum Optic Admission Posture + +For cross-repo optic admission, git-warp should remain a substrate and fact +provider, not an authority issuer or Echo runtime surrogate. Wesley compiles +artifacts and descriptors, Echo registers and admits runtime-local optic +invocations, authority layers issue grants, and applications hide handles and +basis references behind product adapters. git-warp participates by exposing +truthful graph facts and readings when a Continuum-owned family or adapter asks +for them. + ## Where are we `git-warp` has shipped `v17.0.0`. The release work is now behind us in @@ -36,16 +46,12 @@ actually proven. Current branch state at this boundary: -- Branch: `main` +- Branch: `codex/v18-continuum-opening` - Release tag: `v17.0.0` - Latest remote head inspected: `origin/main` at `5afdd3eb` - Latest package version: `17.0.0` - Latest closed cycle: - `0144-release-preflight-and-rc`; `0145-push-pr-review-merge` still needs - closeout bookkeeping. -- Local git status note: - `core.fsmonitor=true` currently emits `fsmonitor_ipc__send_query` warnings - during status/diff commands. + `0145-push-pr-review-merge` The release ladder is now: @@ -118,9 +124,6 @@ read-model groundwork, sync hardening, release gates, and package publishing. ## What feels wrong -- `0145-push-pr-review-merge` still needs closeout bookkeeping even though the - release branch has already landed and the package is published. -- The local git fsmonitor warning adds noise to status and diff commands. - The release preflight fix lowered the coverage ratchet to the measured full-suite v17 line baseline `91.74%`; this is tracked as v19 bad-code debt in `SPEC_coverage-ratchet-baseline-drop.md`. @@ -141,7 +144,7 @@ before the final commit for that slice, and mark completed items with `- [x]`. ## Running Task List -- [ ] 1. Sync and clean the repo runway: fast-forward `main`, clear fsmonitor +- [x] 1. Sync and clean the repo runway: fast-forward `main`, clear fsmonitor noise, close stale v17/0145 bookkeeping, and record the v18 starting point. - [ ] 2. Create the v18 Continuum compatibility charter: WARP Optic compatibility, Continuum contract-family compatibility, Wesley-generated diff --git a/docs/design/0124-v17-release-blocker-dag.dot b/docs/design/0124-v17-release-blocker-dag.dot index 8cf1b28b..db782657 100644 --- a/docs/design/0124-v17-release-blocker-dag.dot +++ b/docs/design/0124-v17-release-blocker-dag.dot @@ -150,8 +150,10 @@ digraph V17ReleaseBlockers { penwidth=2.4 ]; "REL_push-pr-review-merge" [ - fillcolor="#fee2e2", - label="OPEN\nREL_push / PR\nreview / merge" + color="#15803d", + fillcolor="#bbf7d0", + label="DONE\nREL_push / PR\nreview / merge", + penwidth=2.4 ]; "SPEC_docs-materialize-frontdoor-drift" -> "SPEC_runtime-error-reading-basis-guidance"; diff --git a/docs/design/0124-v17-release-blocker-dag.md b/docs/design/0124-v17-release-blocker-dag.md index 2ae27f38..e081b0c2 100644 --- a/docs/design/0124-v17-release-blocker-dag.md +++ b/docs/design/0124-v17-release-blocker-dag.md @@ -164,8 +164,9 @@ closure. A blank cell means "not directly blocked by that task," not documented runbook warning. `REL_push-pr-review-merge` -: Open. Push the branch, open or update the release PR, get review and green - CI, and merge only after explicit approval. +: Closed in cycle 0145. The release branch was pushed, reviewed, repaired, + merged, tagged, and published. Follow-up hardening and npm publish recovery + also landed on `main`. ## Excluded From v17 Blockers @@ -180,9 +181,9 @@ substrate convergence are also excluded from this release-blocker graph. ## Current Open Front -The tasks with no direct blockers are: +There are no open v17 release-blocker nodes. -- `REL_push-pr-review-merge` +The v17 DAG is closed. `SPEC_consumer-typecheck-materialize-residue` closed in cycle 0125. `SPEC_docs-materialize-frontdoor-drift` closed in cycle 0126 and unlocks @@ -208,7 +209,8 @@ quarantine graduation. Quarantine graduation closed in cycle 0141, opening the full gate matrix. The full gate matrix closed in cycle 0142, opening release cut/version/changelog. Release cut/version/changelog closed in cycle 0143, opening release preflight and RC. Release preflight and RC closed in -cycle 0144, opening push, PR, review, and merge. +cycle 0144, opening push, PR, review, and merge. Push, PR, review, merge, tag, +and publish closed in cycle 0145. ## Regeneration diff --git a/docs/design/0124-v17-release-blocker-dag.svg b/docs/design/0124-v17-release-blocker-dag.svg index b49b6865..a87a246f 100644 --- a/docs/design/0124-v17-release-blocker-dag.svg +++ b/docs/design/0124-v17-release-blocker-dag.svg @@ -419,16 +419,16 @@ REL_push-pr-review-merge - -OPEN + +DONE REL_push / PR review / merge REL_release-preflight-and-rc->REL_push-pr-review-merge - - + + diff --git a/docs/design/0124-v17-release-blocker-status.csv b/docs/design/0124-v17-release-blocker-status.csv index da7f0a0a..29afca4b 100644 --- a/docs/design/0124-v17-release-blocker-status.csv +++ b/docs/design/0124-v17-release-blocker-status.csv @@ -19,4 +19,4 @@ REL_quarantine-graduate-clean,complete,BND_checkpoint-schema-contract-drift;PORT REL_full-gate-matrix-green,complete,SPEC_consumer-typecheck-materialize-residue;SPEC_docs-materialize-frontdoor-drift;SPEC_runtime-error-reading-basis-guidance;BND_checkpoint-schema-contract-drift;PORT_patch-controller-reading-basis;PORT_checkpoint-controller-reading-basis;PORT_subscription-controller-reading-basis;PORT_sync-controller-reading-basis;SPEC_materialize-spy-test-clusters;SPEC_observer-coordinate-pinning;SPEC_checkpoint-materialize-test-drift;SPEC_uniform-git-cas-upgrade-contract-drift;HEX_sync-no-rate-limiting;HEX_sync-500-sanitization;REL_quarantine-graduate-clean,,no,Closed by 0142 full gate matrix. REL_release-cut-version-changelog,complete,REL_full-gate-matrix-green,,no,Closed by 0143 release cut/version/changelog. REL_release-preflight-and-rc,complete,REL_release-cut-version-changelog,,no,Closed by 0144 release preflight and RC readiness. -REL_push-pr-review-merge,incomplete,REL_release-preflight-and-rc,,yes,Open node; release preflight is green. +REL_push-pr-review-merge,complete,REL_release-preflight-and-rc,,no,Closed by 0145 push PR review merge; v17 merged tagged and published. diff --git a/docs/design/0145-push-pr-review-merge/push-pr-review-merge.md b/docs/design/0145-push-pr-review-merge/push-pr-review-merge.md index efd3106e..b4f3b3db 100644 --- a/docs/design/0145-push-pr-review-merge/push-pr-review-merge.md +++ b/docs/design/0145-push-pr-review-merge/push-pr-review-merge.md @@ -1,11 +1,12 @@ --- cycle: 0145 task_id: REL_push-pr-review-merge -status: Draft +status: Complete sponsors: human: James agent: Codex started_at: 2026-05-05 +completed_at: 2026-05-21 release_home: v17.0.0 --- @@ -24,6 +25,21 @@ unless James explicitly says `YES`. The release branch is visible on GitHub, represented by a PR against `main`, and blocked only by external review, CI, or the explicit merge approval gate. +## Closeout + +The coordination hill is closed in repo-visible history: + +- PR #84 merged the `release/v17.0.0` branch to `main`. +- PR #85 landed v17 follow-up repair and package migration work. +- PR #86 landed release publish hardening. +- PR #87 finalized the v17 coverage ratchet and produced the signed + `v17.0.0` tag. +- PR #88 recovered npm release publishing. +- PR #89 landed the post-release README wording cleanup. + +The release branches have been pruned from `origin`, `origin/main` is at +`5afdd3eb`, and `v17.0.0` is visible on npm and JSR. + ## Playback Questions 1. Is `release/v17.0.0` pushed to `origin` at the current local commit? @@ -139,11 +155,10 @@ Current evidence before implementation: ### Goldens -- Local and remote branch tips match. -- The PR body records the v17 scope, validation evidence, and the explicit - non-merge gate. -- The final release decision remains outside automation until James says - `YES`. +- `main` contains the release merge and follow-up release hardening. +- The signed `v17.0.0` tag points at the final coverage-ratchet release merge. +- npm and JSR both expose `17.0.0` as the latest package version. +- The final release branches are no longer open remote work. ### Known Fails diff --git a/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md b/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md new file mode 100644 index 00000000..30e8df71 --- /dev/null +++ b/docs/method/retro/0145-push-pr-review-merge/push-pr-review-merge.md @@ -0,0 +1,38 @@ +# 0145 Push PR Review Merge Retro + +## Outcome + +`REL_push-pr-review-merge` is closed. The v17 release branch was merged to +`main`, the signed `v17.0.0` tag was created, npm and JSR publish recovery +completed, and the short-lived release branches were pruned from `origin`. + +## What Went Well + +- The final release coordination happened through visible PR history instead of + local-only release state. +- Follow-up repair PRs kept publication and CI hardening close to the release + merge, which makes the release story auditable from `main`. +- Registry checks confirmed that both npm and JSR expose `17.0.0` as the + current package version. + +## What Was Messy + +- `BEARING.md` and the 0145 design doc drifted behind reality after the + package published. +- The v17 DAG still showed the final coordination node as open after the branch + had landed. +- Local git fsmonitor was producing noisy status/diff warnings during the + closeout inspection. + +## Follow-Up + +Start v18 from a truthful signpost: Continuum/WARP Optic compatibility through +Wesley-generated artifacts, explicit evidence posture, and `warp-ttd` +acceptance over generated-family facts. + +## Battle Report + +The release train made it into the station, then the station sign kept saying +"boarding soon." This retro fixes the sign. The next mess is bigger: teach +`git-warp` to speak Continuum contract families without dressing adapter +folklore up as native witnesshood. From b9b6a326722990b7c4e987867c4c87d751a128a2 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 10:14:53 -0700 Subject: [PATCH 03/16] docs: align v18 continuum doctrine --- docs/BEARING.md | 53 +++--- docs/VISION.md | 18 ++- .../v18-continuum-compatibility-charter.md | 152 ++++++++++++++++++ docs/design/continuum-categories.pdf | Bin 177421 -> 177741 bytes docs/design/continuum-categories.tex | 10 +- docs/method/backlog/README.md | 11 +- docs/method/backlog/WORKLOADS.md | 12 +- .../method/backlog/bad-code/RELEASE_TRIAGE.md | 12 +- .../v18.0.0/PROTO_echo-shaped-edge-records.md | 10 +- .../v18.0.0/PROTO_echo-shaped-node-records.md | 8 +- docs/method/backlog/v18.0.0/README.md | 23 +-- 11 files changed, 248 insertions(+), 61 deletions(-) create mode 100644 docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md diff --git a/docs/BEARING.md b/docs/BEARING.md index 910bac19..af6f94fb 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -14,25 +14,25 @@ Scope note: ## Continuum Optic Admission Posture -For cross-repo optic admission, git-warp should remain a substrate and fact -provider, not an authority issuer or Echo runtime surrogate. Wesley compiles -artifacts and descriptors, Echo registers and admits runtime-local optic -invocations, authority layers issue grants, and applications hide handles and -basis references behind product adapters. git-warp participates by exposing -truthful graph facts and readings when a Continuum-owned family or adapter asks -for them. +For cross-repo optic admission, git-warp is a complete Continuum participant, +not an Echo runtime surrogate. Continuum is the protocol for exchanging +witnessed causal history. Wesley compiles artifacts and descriptors, Echo +admits Echo-local runtime invocations, git-warp admits git-warp-local causal +history and readings, authority layers issue grants, and applications hide +handles and basis references behind product adapters. ## Where are we `git-warp` has shipped `v17.0.0`. The release work is now behind us in repo history, npm, and JSR; the active direction is `v18.0.0`. -The v18 hill is not generic graph-substrate cleanup. It is Continuum +The v18 hill is not generic graph-model cleanup. It is Continuum compatibility: > Make `git-warp` a Continuum-compatible sibling WARP runtime: consume > Wesley-generated artifacts for Continuum-owned contract families, map -> git-warp's append-only Git substrate into honest WARP Optic evidence, and +> git-warp's append-only Git-backed causal history into honest WARP Optic +> evidence, and > give `warp-ttd` generated-family facts instead of handwritten adapter > folklore. @@ -40,9 +40,19 @@ The long-term compatibility target is the WARP Optic shape described in `~/git/blog/aion-paper-07/dist/aion-paper-07.txt`, plus the Continuum contract families authored in `~/git/continuum/schemas/` and compiled by Wesley. Echo and `git-warp` are sibling runtime implementations. `git-warp` -must not pretend to be Echo's durable half, and it must not emit -Continuum-shaped values as native Continuum witnesses until that witnesshood is -actually proven. +has its own Continuum role, and it must not emit Continuum-shaped values as +native Continuum witnesses until that witnesshood is actually proven. + +Backlog fold-in: the repo-visible v18 lane is +`WL-4A-v18-graph-substrate-convergence` in +[WORKLOADS.md](method/backlog/WORKLOADS.md), backed by the eight notes in +[method/backlog/v18.0.0](method/backlog/v18.0.0/). Treat that lane as the +graph-model track inside this compatibility campaign: node and edge record +identity, attachment slots, graph-op algebra, content cutover, legacy property +projection, migration tooling, and genesis replay equivalence. Existing +`echo-shaped` backlog identities are historical shorthand for graph-model +pressure already exercised by Echo, not a claim that Echo owns `git-warp`'s +Continuum role. Current branch state at this boundary: @@ -58,9 +68,9 @@ The release ladder is now: - `v17.0.0`: shipped TypeScript migration, public API honesty, materialization-frontdoor deletion, readings/optics direction, and query read-model groundwork. -- `v18.0.0`: Continuum/WARP Optic compatibility for the cold Git substrate, - through Wesley-generated contract-family artifacts and honest evidence - posture. +- `v18.0.0`: Continuum/WARP Optic compatibility for git-warp as an independent + Continuum participant, through Wesley-generated contract-family artifacts and + honest evidence posture. - `v19.0.0`: observation, doctrine, and slice-first runtime convergence - `v20.0.0`: slice-first read execution - `v21.0.0`: distributed observer geometry and admission reality @@ -133,7 +143,10 @@ read-model groundwork, sync hardening, release gates, and package publishing. - v18 can also lie in the other direction: Continuum-shaped values are not Continuum-native witnesses unless the runtime has actually proven native witnesshood. Initial git-warp compatibility evidence should be treated as - translated substrate evidence until stronger proof exists. + translated git-warp evidence until stronger proof exists. +- The v18 backlog already names a graph-model convergence lane. The plan must + fold that lane into Continuum compatibility instead of replacing it with a + parallel cross-repo adapter plan. - `warp-ttd` needs git-warp facts as generated-family nouns, but the existing ecosystem still contains handwritten adapter and protocol residue. @@ -146,16 +159,18 @@ before the final commit for that slice, and mark completed items with `- [x]`. - [x] 1. Sync and clean the repo runway: fast-forward `main`, clear fsmonitor noise, close stale v17/0145 bookkeeping, and record the v18 starting point. -- [ ] 2. Create the v18 Continuum compatibility charter: WARP Optic +- [x] 2. Create the v18 Continuum compatibility charter: WARP Optic compatibility, Continuum contract-family compatibility, Wesley-generated artifact consumption, and `warp-ttd` acceptance. - [ ] 3. Build the cross-repo contract matrix: Continuum family to Wesley - generated artifact to git-warp source fact to `warp-ttd` consumer need. + generated artifact to git-warp source fact to `warp-ttd` consumer need, + with `WL-4A-v18-graph-substrate-convergence` folded in as the graph-model + track. - [ ] 4. Define git-warp's WARP Optic realization map: observer plan, bounded slice, lowering surface, admissibility law, and retention contract. - [ ] 5. Add a generated-artifact ingestion path for Continuum families, with a guard against handwritten local mirrors becoming contract authority. -- [ ] 6. Make evidence posture explicit: translated substrate evidence first, +- [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. - [ ] 7. Prove the patch commit visibility contract: success means canonical writer-tip advancement and visible graph truth, not just object creation. diff --git a/docs/VISION.md b/docs/VISION.md index dc41296c..8b4bf682 100644 --- a/docs/VISION.md +++ b/docs/VISION.md @@ -60,13 +60,14 @@ The read-side correction now matters just as much as the admission-side one: - Observer-first read surfaces through worldlines and apertures - Decentralized sync through Git transport -In other words: `git-warp` owns the cold causal substrate and the lawful -read/folding surfaces over it. It should not have to pretend that a giant -in-memory graph is the ontology. +In other words: `git-warp` is a complete Continuum participant for witnessed +causal history, append-only Git-backed persistence, and lawful read/folding +surfaces. It should not have to pretend that a giant in-memory graph is the +ontology. ## What git-warp does not own -- Hot deterministic execution → Echo +- Echo's runtime-local deterministic execution → Echo - Time-travel debugging UI → warp-ttd - Shared schemas and contract surfaces → Wesley - Application domain semantics → yours @@ -87,10 +88,11 @@ Sync is just `git push` / `git fetch` of WARP refs. ## The Continuum horizon -When used in the wider stack, `git-warp` is the cold causal substrate. -The Continuum vision (Paper VII §5) reframes processes as strands -whose live realization is a shadow working set over shared machine -history: +When used in the wider stack, `git-warp` and Echo are sibling Continuum +participants. Continuum is the protocol for exchanging witnessed causal +history, not a runtime hierarchy. The Continuum vision (Paper VII §5) reframes +processes as strands whose live realization is a shadow working set over +shared machine history: - **Ephemeral scratch** — local, weakly retained, disposable - **Author-only speculative lane** — durable, replayable, sealed diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md new file mode 100644 index 00000000..63ac2c1f --- /dev/null +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -0,0 +1,152 @@ +--- +cycle: 0146 +task_id: V18_continuum_compatibility_charter +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Continuum Compatibility Charter + +## Pull + +v17 made the current engine shippable. v18 must make `git-warp` compatible +with the shared Continuum/WARP Optic stack as a complete sibling Continuum +participant without collapsing it into Echo, Wesley, or `warp-ttd`. + +## Hill + +`git-warp` becomes a Continuum-compatible sibling WARP runtime: + +- it consumes Wesley-generated artifacts for Continuum-owned contract families; +- it maps append-only Git history into honest WARP Optic evidence; +- it exposes generated-family facts to `warp-ttd`; +- it separates translated git-warp evidence from native Continuum witnesshood. + +## Source Artifacts + +- `~/git/blog/aion-paper-07/dist/aion-paper-07.txt` +- `~/git/continuum/schemas/` +- `~/git/continuum/docs/contract-family-registry.md` +- `~/git/wesley/README.md` +- `~/git/echo/docs/BEARING.md` +- `~/git/warp-ttd/docs/BEARING.md` +- [VISION.md](../../VISION.md) +- [BEARING.md](../../BEARING.md) +- [backlog/WORKLOADS.md](../../method/backlog/WORKLOADS.md) +- [backlog/v18.0.0/README.md](../../method/backlog/v18.0.0/README.md) + +## Compatibility Law + +The target WARP Optic shape is: + +```text +Psi = (Omega, chi, rho, Pi, Lambda) +Lower_Psi(F*, P) = (R, W, theta) +``` + +For v18, `git-warp` should interpret that shape as a compatibility boundary: + +- `Omega`: observer/read discipline, basis, and emission posture; +- `chi`: bounded frontier-relative support slice; +- `rho`: append-only Git/CRDT lowering surface over patch chains; +- `Pi`: admission law producing derived, plural, conflict, or obstruction + outcomes; +- `Lambda`: retained replay, audit, transport, revelation, and reliance + obligations. + +This charter does not require one generic optic engine. It requires each +published compatibility surface to say which part of the optic shape it +implements and which evidence supports that claim. + +## Contract Families + +The first v18 compatibility families are the Continuum-authored families: + +- `receipt-family` +- `settlement-family` +- `neighborhood-core-family` +- `runtime-boundary-family` + +Wesley is the compiler and artifact authority for these shared families. +`git-warp` must consume generated artifacts or documented generated fixtures. +Handwritten mirrors may exist only as temporary local adapters with explicit +non-authority status. + +## Backlog Integration + +The repo-visible v18 backlog already names +`WL-4A-v18-graph-substrate-convergence` as the first major workload. This +charter folds that work into the Continuum campaign rather than replacing it. + +That workload contributes the graph-model track: + +- node and edge record identity; +- attachment-plane substrate work; +- graph-op algebra convergence; +- content migration out of legacy property conventions; +- property-bag reads reduced to projections; +- graph-model migration tooling; +- replay equivalence from genesis. + +The existing `PROTO_echo-shaped-*` task identities are retained as backlog +history. In this charter, `echo-shaped` means graph-model pressure already +exercised by Echo. It does not mean Echo owns `git-warp`'s Continuum role or +defines `git-warp`'s participant obligations. + +## Evidence Posture + +The default posture for existing git-warp facts mapped into Continuum-family +shapes is translated evidence. A value may be Continuum-shaped without being +Continuum-native. + +`git-warp` may claim native Continuum witnesshood only after a runtime witness +proves the value was produced through the corresponding Continuum family +contract and not merely mapped from local git-warp facts. + +## Non-Goals + +- Do not make Echo the owner or authority for `git-warp`'s Continuum role. +- Do not make `git-warp` a semantic owner for Continuum contract families. +- Do not make `warp-ttd` hand-normalize git-warp facts into substitute shared + contracts. +- Do not build a generic WARP Optic runtime before repeated concrete + compatibility cuts justify it. +- Do not claim native Continuum witnesshood for translated git-warp evidence. + +## Acceptance + +The v18 opening campaign is on track when: + +- [ ] `BEARING.md` tracks the running v18 task list. +- [ ] A cross-repo contract matrix names each family, generated artifact, local + source fact, consumer, and missing witness. +- [ ] A WARP Optic realization map exists for `git-warp`. +- [ ] The repo can ingest at least one generated Continuum family artifact or + generated fixture. +- [ ] A guard prevents generated-family local mirrors from becoming hidden + authority. +- [ ] The first receipt-family projection reaches `warp-ttd` without adapter + folklore. + +## SSJS Scorecard + +- Runtime-backed forms: green for this documentation slice; no runtime forms + introduced. +- Boundary validation: green; the charter names generated artifacts as the + boundary authority. +- Behavior ownership: green; Continuum owns shared semantics, Wesley compiles, + `git-warp` emits git-warp-local facts, and `warp-ttd` observes. +- Message parsing: green; no behavior branches are introduced. +- Ambient time or entropy: green; no runtime code introduced. +- Fake shape trust or cast-cosplay: green; the charter explicitly rejects fake + native witnesshood. + +## Closeout + +This charter closes BEARING task 2 and defines the standard later v18 slices +must satisfy before making stronger compatibility claims. diff --git a/docs/design/continuum-categories.pdf b/docs/design/continuum-categories.pdf index a218496faccd358cf1d364b260bbe362c0c674b8..82c7fc818dc648843e97b50443de3436dfb3e121 100644 GIT binary patch delta 17777 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

s;5+-g}e`j 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 Date: Thu, 21 May 2026 10:21:00 -0700 Subject: [PATCH 04/16] docs: add v18 continuum contract matrix --- docs/BEARING.md | 2 +- .../v18-continuum-compatibility-charter.md | 2 +- .../v18-continuum-contract-matrix.md | 110 ++++++++++++++++++ 3 files changed, 112 insertions(+), 2 deletions(-) create mode 100644 docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md diff --git a/docs/BEARING.md b/docs/BEARING.md index af6f94fb..4fc2fe14 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -162,7 +162,7 @@ before the final commit for that slice, and mark completed items with `- [x]`. - [x] 2. Create the v18 Continuum compatibility charter: WARP Optic compatibility, Continuum contract-family compatibility, Wesley-generated artifact consumption, and `warp-ttd` acceptance. -- [ ] 3. Build the cross-repo contract matrix: Continuum family to Wesley +- [x] 3. Build the cross-repo contract matrix: Continuum family to Wesley generated artifact to git-warp source fact to `warp-ttd` consumer need, with `WL-4A-v18-graph-substrate-convergence` folded in as the graph-model track. diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md index 63ac2c1f..b47d8810 100644 --- a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -123,7 +123,7 @@ contract and not merely mapped from local git-warp facts. The v18 opening campaign is on track when: - [ ] `BEARING.md` tracks the running v18 task list. -- [ ] A cross-repo contract matrix names each family, generated artifact, local +- [x] A cross-repo contract matrix names each family, generated artifact, local source fact, consumer, and missing witness. - [ ] A WARP Optic realization map exists for `git-warp`. - [ ] The repo can ingest at least one generated Continuum family artifact or diff --git a/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md b/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md new file mode 100644 index 00000000..5f68a4f7 --- /dev/null +++ b/docs/design/0147-v18-continuum-contract-matrix/v18-continuum-contract-matrix.md @@ -0,0 +1,110 @@ +--- +cycle: 0147 +task_id: V18_continuum_contract_matrix +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Continuum Contract Matrix + +## Pull + +The v18 charter names the compatibility target. This slice names the actual +contract families and the current proof gap for each one. + +## Hill + +`git-warp` can now point at a concrete matrix: + +- Continuum owns the shared family semantics. +- Wesley compiles and witnesses generated family artifacts. +- Echo and `git-warp` are sibling runtimes that may emit or consume conforming + values. +- `warp-ttd` is the structured debugger/read-model consumer. +- Existing `git-warp` facts are translated evidence until native Continuum + witnesshood is proven. + +## Evidence Snapshot + +The matrix below is based on these inspected local sources: + +| Repo | Head | Evidence | +| --- | --- | --- | +| Continuum | `01e0735` | `docs/contract-family-registry.md`, `schemas/*.graphql`, `wesley/profile/scopes.mjs` | +| Wesley | `19b2c1c9` | `README.md`, `docs/architecture/continuum-minimum-shared-contract-surface.md` | +| Echo | `b1d979d` | `docs/BEARING.md` | +| `warp-ttd` | `0491be6` | `docs/BEARING.md`, `schemas/warp-ttd-protocol.graphql` | +| `git-warp` | this branch | `docs/BEARING.md`, `src/domain/types/TickReceipt.ts`, `src/domain/types/DeliveryObservation.ts`, read/materialize capabilities | + +## Family Matrix + +| Family | Authored home | Wesley status | `git-warp` source facts | Primary `warp-ttd` need | Missing witness | +| --- | --- | --- | --- | --- | --- | +| `receipt-family` | `~/git/continuum/schemas/continuum-receipt-family.graphql` | `profiled`, `fixture-witnessed`; scope `receipt-family` checks cross-leg schema hash, TTD fixture shape, Echo fixture shape, boundary fixture, roundtrip vectors, and receipt/witness separation | `TickReceipt`, op outcomes, `DeliveryObservation`, audit receipt chains, materialize/provenance receipt collection | Receipt and delivery facts as generated-family nouns, not adapter-local summaries | Live `git-warp` receipt publication mapped through generated artifacts with translated evidence posture, then witnessed as native only after a Continuum runtime witness exists | +| `settlement-family` | `~/git/continuum/schemas/continuum-settlement-family.graphql` | `profiled`, `fixture-witnessed`; scope `settlement-family` checks cross-leg coherence and settlement boundary fixtures | Patch diffs, conflict traces, merge/conflict analysis, strand/braid conflict artifacts, writer frontier state | Import/settlement explanation for cross-runtime history and merge inspection | Live settlement values from `git-warp` suffix/import or merge flows, plus generated-artifact conformance | +| `neighborhood-core-family` | `~/git/continuum/schemas/continuum-neighborhood-core-family.graphql` | `authored`; not yet profiled in the current Continuum Wesley scope list | Graph name, writer refs, worldline/frontier facts, local site-like participation facts still unnamed as a stable family | Neighborhood focus, participant catalog, and site navigation across Echo and `git-warp` targets | Wesley profile and fixture witness first; then `git-warp` participant values with explicit translated/native evidence status | +| `runtime-boundary-family` | `~/git/continuum/schemas/continuum-runtime-boundary-family.graphql` | `authored`; not yet profiled in the current Continuum Wesley scope list | Materialize/read requests, observer/read basis, patch suffixes, frontiers, provenance refs, receipt collections, import outcomes still split across local APIs | Admission-chain read model: observer plans, reading envelopes, evidence posture, suffix shells, causal suffix bundles, import outcomes | Wesley profile, generated fixtures, and a live witnessed suffix exchange/admission proof between sibling runtimes | + +## Source-Fact Map + +| Continuum noun | Current `git-warp` anchor | Current posture | +| --- | --- | --- | +| `Receipt` | `TickReceipt`, audit receipts, receipt shards | Translated evidence; shape is not yet generated-family native | +| `DeliveryObservation` | `DeliveryObservation` and effect sink observations | Local fact with strong name overlap; not yet Continuum family output | +| `Witness` | checkpoint-tail witnesses, conflict witnesses, audit chain proofs | Local witness forms; no shared generated family surface yet | +| `SettlementDelta` / `ConflictArtifact` | `PatchDiff`, conflict traces, merge/conflict services | Candidate source facts; missing shared generated settlement adapter | +| `NeighborhoodCore` / `NeighborhoodParticipant` | graph name, writers, frontiers, worldline metadata | Candidate source facts; missing stable local site/participant object | +| `ObserverPlan` / `ObservationRequest` | query/read basis, materialize options, traversal context | Candidate source facts; missing generated runtime-boundary profile | +| `ReadingEnvelope` | materialize/query/read results plus provenance/receipt options | Candidate source facts; missing explicit evidence status wrapper | +| `TranslatedSubstrateEvidence` | append-only Git-backed causal history, patch SHAs, writer refs, receipts | Correct initial evidence posture for compatibility outputs | +| `WitnessedSuffixShell` / `CausalSuffixBundle` | writer patch chains, frontier maps, transport/sync suffixes | Candidate source facts; missing compact generated shell and admission witness | +| `ImportOutcome` | sync/import/materialization outcomes and conflict posture | Candidate source facts; missing runtime-boundary family emission | + +## `warp-ttd` Consumer Matrix + +`warp-ttd`'s active bearing pressures these generated-family facts first: + +| `warp-ttd` target | Needed from `git-warp` | Contract-family lane | +| --- | --- | --- | +| Dual live app debugging | Read-only posture for a live `git-warp` target without host mutation | runtime-boundary-family | +| Admission-chain read model | Artifact registration, evidence posture, receipts, witnesses, and reading envelopes as distinct facts | runtime-boundary-family, receipt-family | +| Neighborhood and site catalog | Participant/site summaries that can compare Echo and `git-warp` targets | neighborhood-core-family | +| Receipt shell summary | Generated-family receipt facts and delivery observations | receipt-family | +| Merge and import inspection | Conflict artifacts, import candidates, settlement plans, and outcomes | settlement-family | + +## First Implementation Pressure + +The first implementation slice should not try to ingest every family. It should +build a generated-artifact ingestion seam around one generated fixture family, +then guard against hidden handwritten mirrors. + +Recommended order: + +1. Ingest or locally fixture the `receipt-family` generated artifact manifest. +2. Reject local `git-warp` files that claim to be authoritative mirrors of + Continuum-owned families. +3. Map `TickReceipt` and `DeliveryObservation` into a translated + `receipt-family` projection without claiming native Continuum witnesshood. +4. Let `warp-ttd` consume that projection as generated-family-shaped input. + +## SSJS Scorecard + +- Runtime-backed forms: green for this documentation slice; no runtime forms + introduced. +- Boundary validation: green; the matrix keeps authored schemas and Wesley + generated artifacts as authority. +- Behavior ownership: green; each row separates authored home, compiler, + runtime source fact, consumer, and missing witness. +- Message parsing: green; no behavior branches introduced. +- Ambient time or entropy: green; no runtime code introduced. +- Fake shape trust or cast-cosplay: green; every current `git-warp` mapping is + marked as translated evidence until a stronger witness exists. + +## Closeout + +This closes BEARING task 3 and supplies the evidence table for slices 4 and 5. From 487ff2b6df73b21b57b30418aa443ea8c22468ec Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 10:22:24 -0700 Subject: [PATCH 05/16] docs: map git-warp optic realization --- docs/BEARING.md | 2 +- .../v18-continuum-compatibility-charter.md | 2 +- .../v18-warp-optic-realization-map.md | 113 ++++++++++++++++++ 3 files changed, 115 insertions(+), 2 deletions(-) create mode 100644 docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md diff --git a/docs/BEARING.md b/docs/BEARING.md index 4fc2fe14..a8d31590 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -166,7 +166,7 @@ before the final commit for that slice, and mark completed items with `- [x]`. generated artifact to git-warp source fact to `warp-ttd` consumer need, with `WL-4A-v18-graph-substrate-convergence` folded in as the graph-model track. -- [ ] 4. Define git-warp's WARP Optic realization map: observer plan, bounded +- [x] 4. Define git-warp's WARP Optic realization map: observer plan, bounded slice, lowering surface, admissibility law, and retention contract. - [ ] 5. Add a generated-artifact ingestion path for Continuum families, with a guard against handwritten local mirrors becoming contract authority. diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md index b47d8810..002b7c32 100644 --- a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -125,7 +125,7 @@ The v18 opening campaign is on track when: - [ ] `BEARING.md` tracks the running v18 task list. - [x] A cross-repo contract matrix names each family, generated artifact, local source fact, consumer, and missing witness. -- [ ] A WARP Optic realization map exists for `git-warp`. +- [x] A WARP Optic realization map exists for `git-warp`. - [ ] The repo can ingest at least one generated Continuum family artifact or generated fixture. - [ ] A guard prevents generated-family local mirrors from becoming hidden diff --git a/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md b/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md new file mode 100644 index 00000000..6c859f4b --- /dev/null +++ b/docs/design/0148-v18-warp-optic-realization-map/v18-warp-optic-realization-map.md @@ -0,0 +1,113 @@ +--- +cycle: 0148 +task_id: V18_warp_optic_realization_map +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 WARP Optic Realization Map + +## Pull + +The contract matrix names the cross-repo families. This slice maps +`git-warp`'s current runtime facts onto the WARP optic tuple without claiming a +generic optic engine. + +## Hill + +For v18, `git-warp` interprets: + +```text +Psi = (Omega, chi, rho, Pi, Lambda) +Lower_Psi(F*, P) = (R, W, theta) +``` + +as a compatibility map over existing repo facts. + +## Component Map + +| Optic component | WARP role | Current `git-warp` anchors | v18 compatibility gap | +| --- | --- | --- | --- | +| `Omega` | Observer discipline: projection, basis, observer state, update discipline, and emission | `WorldlineOptic`, `NodeOptic`, `NodePropertyOptic`, `ReadIdentity`, `Observer`, `QueryRunner`, `QueryReadModelProvider`, `ExternalizationPolicy` | Generated `ObserverPlan`, `ObservationRequest`, and `ReadingEnvelope` family shapes | +| `chi` | Bounded frontier-relative optic slice | `CheckpointTailWitnessLocator`, `CheckpointTailBasisLoader`, `CheckpointTailOpticSource`, `CheckpointTailReadIdentityBuilder`, `ProvenanceController.materializeSlice` | Shared vocabulary for slice support, tail budget, and graph-model attachment plane | +| `rho` | Set-side lowering surface that presents comparable claims over `chi` | `reduceV5`, `applyWithReceipt`, `JoinReducerSession`, op classes, `PatchDiff`, `syncDelta`, materialize-coordinate paths | Generated lowering inputs for receipt, settlement, and runtime-boundary families | +| `Pi` | Admission law for derived, plural, conflict, or obstruction outcomes | `OpStrategies`, `ReceiptBuilder`, `TickReceipt` outcomes, `ConflictAnalyzerService`, `SyncTrustGate`, `OpticReadFailureCause` | First-class outcome algebra aligned with Continuum `AdmissionOutcomeKind` and runtime-boundary admission nouns | +| `Lambda` | Retention contract for replay, audit, transport, revelation, and reliance obligations | append-only Git commits, writer refs, patch SHAs, checkpoints, `ReceiptShard`, `AuditReceiptService`, `ReadIdentity`, sync suffixes | Generated evidence-status wrappers, suffix shells, and explicit retention obligations for `warp-ttd` | + +## Lowering Scales + +| Scale | Weave `P` | Frontier `F*` | Result `R` | Witness `W` | Retained shell `theta` | Current posture | +| --- | --- | --- | --- | --- | --- | --- | +| Tick / patch | One patch or ordered patch sequence | Writer and graph frontier | State transition, `PatchDiff`, or op outcomes | `TickReceipt`, op outcome details | Patch commit, receipt, optional audit receipt | Real local runtime fact; not native Continuum receipt-family output yet | +| Read / optic | Observer/read target plus basis | Live, coordinate, or strand source | Node/property/traversal/materialized reading | `ReadIdentity`, checkpoint-tail witnesses, failure cause | Read identity plus checkpoint/tail anchors | Real local read fact; missing generated `ReadingEnvelope` | +| Provenance slice | Backward cone for a target | Patch graph reachable from target | Bounded reconstructed state and patch count | Causal patch list, optional receipts | Provenance payload and source SHAs | Real local source fact; missing Continuum evidence wrapper | +| Strand / braid | Strand overlay or braided strand set | Parent frontier plus overlay heads | Materialized strand state or conflict trace | conflict receipts, conflict anchors, participant traces | strand descriptor, overlay patches, conflict analysis | Candidate settlement-family source facts | +| Replica / sync | Remote suffix family or frontier delta | Local and remote writer frontiers | Needed patch ranges, trust posture, import/sync result | writer trust gate result, ancestry checks | transferred patch commits and refs | Candidate runtime-boundary suffix/import facts | + +## Outcome Algebra Posture + +The WARP paper's outcome space is: + +```text +O(X) = Derived(X) + Plural(X) + Conflict + Obstruction +``` + +Current `git-warp` facts map into it conservatively: + +| Outcome | Local anchors | Current limitation | +| --- | --- | --- | +| `Derived` | successful reducer outcomes, materialized readings, query results | Not wrapped as a generated Continuum outcome | +| `Plural` | strand/braid coexistence and multi-writer frontier facts | Plurality is represented structurally, not as a named outcome | +| `Conflict` | conflict traces, diagnostics, conflict receipt refs | Settlement-family projection is still missing | +| `Obstruction` | optic read failures, sync trust rejection, validation errors | Obstruction is not yet one shared runtime-boundary noun | + +## Evidence Posture + +The first v18 compatibility layer must mark `git-warp` outputs as translated +evidence unless a native Continuum runtime witness exists. + +That means: + +- a `TickReceipt` can be mapped toward `receipt-family`; +- a conflict trace can be mapped toward `settlement-family`; +- a read result can be mapped toward `runtime-boundary-family`; +- a sync suffix can be mapped toward `WitnessedSuffixShell`; +- none of those mappings may claim native Continuum witnesshood by shape alone. + +## Next Engineering Cut + +Slice 5 should create a generated-artifact ingestion seam that can load one +Continuum family artifact descriptor or fixture and reject hidden handwritten +authority. The first useful family is `receipt-family` because `git-warp` +already has strong local source facts: `TickReceipt`, op outcomes, +`DeliveryObservation`, receipt shards, and audit receipts. + +The seam should not: + +- parse arbitrary GraphQL in the domain; +- generate types at runtime; +- make `git-warp` the owner of Continuum family semantics; +- equate translated `git-warp` evidence with native Continuum witnesshood. + +## SSJS Scorecard + +- Runtime-backed forms: green for this documentation slice; no runtime forms + introduced. +- Boundary validation: green; the map treats generated artifacts as later + boundary inputs. +- Behavior ownership: green; optic components are mapped to owning local + modules and their gaps are named. +- Message parsing: green; no behavior branches introduced. +- Ambient time or entropy: green; no runtime code introduced. +- Fake shape trust or cast-cosplay: green; translated evidence is explicit. + +## Closeout + +This closes BEARING task 4 and gives slice 5 a narrow implementation target: +ingest a generated-family artifact descriptor or fixture and guard against +shadow authority. From c3d03dc6ed0d1ada87b7d7b1d04dbe341627fb8f Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 10:30:33 -0700 Subject: [PATCH 06/16] feat: add continuum artifact ingestion guard --- docs/BEARING.md | 2 +- .../v18-continuum-compatibility-charter.md | 4 +- .../v18-continuum-artifact-ingestion.md | 100 +++++++++++++++ index.ts | 26 +++- .../continuum/ContinuumArtifactAuthority.ts | 61 ++++++++++ .../continuum/ContinuumArtifactDescriptor.ts | 96 +++++++++++++++ .../ContinuumArtifactIngestionPolicy.ts | 21 ++++ src/domain/continuum/ContinuumFamilyId.ts | 58 +++++++++ .../errors/ContinuumArtifactAuthorityError.ts | 8 ++ src/domain/errors/index.ts | 1 + .../ContinuumArtifactJsonFileAdapter.ts | 115 ++++++++++++++++++ .../receipt-family-generated-artifact.json | 11 ++ .../ContinuumArtifactIngestionPolicy.test.ts | 57 +++++++++ test/unit/domain/index.exports.test.ts | 36 ++++++ .../ContinuumArtifactJsonFileAdapter.test.ts | 64 ++++++++++ 15 files changed, 656 insertions(+), 4 deletions(-) create mode 100644 docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md create mode 100644 src/domain/continuum/ContinuumArtifactAuthority.ts create mode 100644 src/domain/continuum/ContinuumArtifactDescriptor.ts create mode 100644 src/domain/continuum/ContinuumArtifactIngestionPolicy.ts create mode 100644 src/domain/continuum/ContinuumFamilyId.ts create mode 100644 src/domain/errors/ContinuumArtifactAuthorityError.ts create mode 100644 src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts create mode 100644 test/fixtures/continuum/receipt-family-generated-artifact.json create mode 100644 test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts create mode 100644 test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts diff --git a/docs/BEARING.md b/docs/BEARING.md index a8d31590..ce239209 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -168,7 +168,7 @@ before the final commit for that slice, and mark completed items with `- [x]`. track. - [x] 4. Define git-warp's WARP Optic realization map: observer plan, bounded slice, lowering surface, admissibility law, and retention contract. -- [ ] 5. Add a generated-artifact ingestion path for Continuum families, with a +- [x] 5. Add a generated-artifact ingestion path for Continuum families, with a guard against handwritten local mirrors becoming contract authority. - [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md index 002b7c32..aa2a3993 100644 --- a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -126,9 +126,9 @@ The v18 opening campaign is on track when: - [x] A cross-repo contract matrix names each family, generated artifact, local source fact, consumer, and missing witness. - [x] A WARP Optic realization map exists for `git-warp`. -- [ ] The repo can ingest at least one generated Continuum family artifact or +- [x] The repo can ingest at least one generated Continuum family artifact or generated fixture. -- [ ] A guard prevents generated-family local mirrors from becoming hidden +- [x] A guard prevents generated-family local mirrors from becoming hidden authority. - [ ] The first receipt-family projection reaches `warp-ttd` without adapter folklore. diff --git a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md new file mode 100644 index 00000000..e7f8d79d --- /dev/null +++ b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md @@ -0,0 +1,100 @@ +--- +cycle: 0149 +task_id: V18_continuum_artifact_ingestion +status: Complete +sponsors: + human: James + agent: Codex +started_at: 2026-05-21 +completed_at: 2026-05-21 +release_home: v18.0.0 +--- + +# V18 Continuum Artifact Ingestion + +## Pull + +The contract matrix and optic map both point at the same first implementation +pressure: `git-warp` needs a generated-family artifact seam before it can map +local facts into Continuum-family shapes. + +## Hill + +Add a narrow ingestion path for generated Continuum family artifact descriptors +and reject local mirrors before they can become hidden family authority. + +## Implementation + +This slice adds: + +- `ContinuumFamilyId` for the four Continuum-owned family ids; +- `ContinuumArtifactAuthority` for generated artifacts, generated fixtures, + local mirrors, and handwritten mirrors; +- `ContinuumArtifactDescriptor` as the runtime-backed descriptor object; +- `ContinuumArtifactIngestionPolicy` as the authority guard; +- `ContinuumArtifactJsonFileAdapter` as the infrastructure-edge JSON loader; +- `test/fixtures/continuum/receipt-family-generated-artifact.json` as the first + generated-family fixture descriptor. + +The guard accepts only: + +- `generated-artifact` +- `generated-fixture` + +It rejects: + +- `local-mirror` +- `handwritten-mirror` + +## Boundary Law + +JSON parsing stays in `src/infrastructure/adapters/`. Domain code receives +validated constructor fields and runtime-backed objects. + +The descriptor does not parse GraphQL, generate TypeScript, or claim family +semantics. It only records which generated-family artifact or fixture is being +admitted and whether that admission posture is allowed. + +## Verification + +Focused checks: + +```text +npx eslint src/domain/continuum/ContinuumFamilyId.ts \ + src/domain/continuum/ContinuumArtifactAuthority.ts \ + src/domain/continuum/ContinuumArtifactDescriptor.ts \ + src/domain/continuum/ContinuumArtifactIngestionPolicy.ts \ + src/domain/errors/ContinuumArtifactAuthorityError.ts \ + src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts +npm run typecheck:src +npm run typecheck:test +npm run typecheck:surface +npx vitest run \ + test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts \ + test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +``` + +Observed focused test result: + +```text +Test Files 2 passed (2) +Tests 9 passed (9) +``` + +## SSJS Scorecard + +- Runtime-backed forms: green; new Continuum concepts are classes with + constructor validation and frozen instances. +- Boundary validation: green; untrusted JSON is parsed only in the + infrastructure adapter. +- Behavior ownership: green; the descriptor owns descriptor invariants and the + ingestion policy owns authority decisions. +- Message parsing: green; no behavior branches parse free-form messages. +- Ambient time or entropy: green; no ambient time or entropy introduced. +- Fake shape trust or cast-cosplay: green; mirror descriptors are rejected + before ingestion. + +## Closeout + +This closes BEARING task 5 and gives later receipt-family projection work a +safe generated-artifact entry point. diff --git a/index.ts b/index.ts index e4719aed..ce8b8572 100644 --- a/index.ts +++ b/index.ts @@ -46,6 +46,7 @@ import NoOpLogger from './src/infrastructure/adapters/NoOpLogger.ts'; import ConsoleLogger, { LogLevel } from './src/infrastructure/adapters/ConsoleLogger.ts'; import { AuditError, + ContinuumArtifactAuthorityError, EncryptionError, ForkError, IndexError, @@ -207,6 +208,14 @@ import { exportCoordinateComparisonFact, exportCoordinateTransferPlanFact, } from './src/domain/services/CoordinateFactExport.ts'; +import ContinuumArtifactAuthority from './src/domain/continuum/ContinuumArtifactAuthority.ts'; +import ContinuumArtifactDescriptor from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; +import ContinuumArtifactIngestionPolicy from './src/domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import ContinuumFamilyId from './src/domain/continuum/ContinuumFamilyId.ts'; +import ContinuumArtifactJsonFileAdapter from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; +import type { ContinuumArtifactAuthorityValue } from './src/domain/continuum/ContinuumArtifactAuthority.ts'; +import type { ContinuumArtifactDescriptorFields } from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; +import type { ContinuumFamilyIdValue } from './src/domain/continuum/ContinuumFamilyId.ts'; export { GitGraphAdapter, @@ -247,6 +256,7 @@ export { // Error types for integrity failure handling AuditError, + ContinuumArtifactAuthorityError, EncryptionError, PatchError, ForkError, @@ -318,6 +328,13 @@ export { exportCoordinateComparisonFact, exportCoordinateTransferPlanFact, + // Continuum compatibility artifacts + ContinuumArtifactAuthority, + ContinuumArtifactDescriptor, + ContinuumArtifactIngestionPolicy, + ContinuumFamilyId, + ContinuumArtifactJsonFileAdapter, + // Tick receipts (LIGHTHOUSE) createTickReceipt, tickReceiptCanonicalJson, @@ -360,7 +377,14 @@ export { SyncSecret, }; -export type { PropValue, SnapshotPropValue, SyncRateLimitConfig }; +export type { + PropValue, + SnapshotPropValue, + SyncRateLimitConfig, + ContinuumArtifactAuthorityValue, + ContinuumArtifactDescriptorFields, + ContinuumFamilyIdValue, +}; // WarpApp is the primary product-facing API for v15. export default WarpApp; diff --git a/src/domain/continuum/ContinuumArtifactAuthority.ts b/src/domain/continuum/ContinuumArtifactAuthority.ts new file mode 100644 index 00000000..589b1a7a --- /dev/null +++ b/src/domain/continuum/ContinuumArtifactAuthority.ts @@ -0,0 +1,61 @@ +import WarpError from '../errors/WarpError.ts'; + +const GENERATED_ARTIFACT_AUTHORITY = 'generated-artifact'; +const GENERATED_FIXTURE_AUTHORITY = 'generated-fixture'; +const LOCAL_MIRROR_AUTHORITY = 'local-mirror'; +const HANDWRITTEN_MIRROR_AUTHORITY = 'handwritten-mirror'; + +export type ContinuumArtifactAuthorityValue = + | typeof GENERATED_ARTIFACT_AUTHORITY + | typeof GENERATED_FIXTURE_AUTHORITY + | typeof LOCAL_MIRROR_AUTHORITY + | typeof HANDWRITTEN_MIRROR_AUTHORITY; + +export const CONTINUUM_ARTIFACT_AUTHORITIES: readonly ContinuumArtifactAuthorityValue[] = Object.freeze([ + GENERATED_ARTIFACT_AUTHORITY, + GENERATED_FIXTURE_AUTHORITY, + LOCAL_MIRROR_AUTHORITY, + HANDWRITTEN_MIRROR_AUTHORITY, +]); + +/** Runtime-backed authority posture for an ingested Continuum artifact. */ +export default class ContinuumArtifactAuthority { + readonly value: ContinuumArtifactAuthorityValue; + + constructor(value: string) { + this.value = requireContinuumArtifactAuthority(value); + Object.freeze(this); + } + + /** Returns true for Wesley-generated artifacts and documented fixtures. */ + isGeneratedAuthority(): boolean { + return ( + this.value === GENERATED_ARTIFACT_AUTHORITY || + this.value === GENERATED_FIXTURE_AUTHORITY + ); + } + + /** Returns the stable authority string. */ + toString(): string { + return this.value; + } +} + +/** Validates a raw authority posture string. */ +export function requireContinuumArtifactAuthority(value: string): ContinuumArtifactAuthorityValue { + switch (value) { + case GENERATED_ARTIFACT_AUTHORITY: + return GENERATED_ARTIFACT_AUTHORITY; + case GENERATED_FIXTURE_AUTHORITY: + return GENERATED_FIXTURE_AUTHORITY; + case LOCAL_MIRROR_AUTHORITY: + return LOCAL_MIRROR_AUTHORITY; + case HANDWRITTEN_MIRROR_AUTHORITY: + return HANDWRITTEN_MIRROR_AUTHORITY; + default: + throw new WarpError( + `Continuum artifact authority must be one of: ${CONTINUUM_ARTIFACT_AUTHORITIES.join(', ')}`, + 'E_VALIDATION', + ); + } +} diff --git a/src/domain/continuum/ContinuumArtifactDescriptor.ts b/src/domain/continuum/ContinuumArtifactDescriptor.ts new file mode 100644 index 00000000..d474b8b1 --- /dev/null +++ b/src/domain/continuum/ContinuumArtifactDescriptor.ts @@ -0,0 +1,96 @@ +import ContinuumArtifactAuthority from './ContinuumArtifactAuthority.ts'; +import ContinuumFamilyId from './ContinuumFamilyId.ts'; +import WarpError from '../errors/WarpError.ts'; + +export type ContinuumArtifactDescriptorFields = { + readonly familyId: string | ContinuumFamilyId; + readonly version: string; + readonly sourceSchemaPath: string; + readonly generatedBy: string; + readonly artifactKind: string; + readonly authority: string | ContinuumArtifactAuthority; + readonly targets: readonly string[]; + readonly witnessScope?: string; + readonly artifactDigest?: string; +}; + +/** Runtime-backed descriptor for a generated Continuum family artifact. */ +export default class ContinuumArtifactDescriptor { + readonly familyId: ContinuumFamilyId; + readonly version: string; + readonly sourceSchemaPath: string; + readonly generatedBy: string; + readonly artifactKind: string; + readonly authority: ContinuumArtifactAuthority; + readonly targets: readonly string[]; + readonly witnessScope: string | undefined; + readonly artifactDigest: string | undefined; + + constructor(fields: ContinuumArtifactDescriptorFields) { + const { familyId, version, sourceSchemaPath, generatedBy, artifactKind, authority, targets } = fields; + this.familyId = normalizeFamilyId(familyId); + this.version = requireNonEmptyString(version, 'version'); + this.sourceSchemaPath = requireNonEmptyString(sourceSchemaPath, 'sourceSchemaPath'); + this.generatedBy = requireNonEmptyString(generatedBy, 'generatedBy'); + this.artifactKind = requireNonEmptyString(artifactKind, 'artifactKind'); + this.authority = normalizeAuthority(authority); + this.targets = freezeTargets(targets); + this.witnessScope = optionalNonEmptyString(fields.witnessScope, 'witnessScope'); + this.artifactDigest = optionalNonEmptyString(fields.artifactDigest, 'artifactDigest'); + Object.freeze(this); + } + + /** Returns true when the descriptor includes the requested generation target. */ + hasTarget(target: string): boolean { + return this.targets.includes(target); + } + + /** Returns true when the artifact may be used as generated authority. */ + hasGeneratedAuthority(): boolean { + return this.authority.isGeneratedAuthority(); + } +} + +/** Normalizes a family id carrier. */ +function normalizeFamilyId(value: string | ContinuumFamilyId): ContinuumFamilyId { + if (value instanceof ContinuumFamilyId) { + return value; + } + return new ContinuumFamilyId(value); +} + +/** Normalizes an authority carrier. */ +function normalizeAuthority(value: string | ContinuumArtifactAuthority): ContinuumArtifactAuthority { + if (value instanceof ContinuumArtifactAuthority) { + return value; + } + return new ContinuumArtifactAuthority(value); +} + +/** Validates a required non-empty string. */ +function requireNonEmptyString(value: string, name: string): string { + if (value.length === 0) { + throw new WarpError(`${name} must be a non-empty string`, 'E_VALIDATION'); + } + return value; +} + +/** Validates an optional non-empty string. */ +function optionalNonEmptyString(value: string | undefined, name: string): string | undefined { + if (value === undefined) { + return undefined; + } + return requireNonEmptyString(value, name); +} + +/** Freezes and validates a generated target list. */ +function freezeTargets(targets: readonly string[]): readonly string[] { + if (targets.length === 0) { + throw new WarpError('targets must contain at least one generated target', 'E_VALIDATION'); + } + const normalized: string[] = []; + for (const target of targets) { + normalized.push(requireNonEmptyString(target, 'targets[]')); + } + return Object.freeze(normalized); +} diff --git a/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts b/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts new file mode 100644 index 00000000..bd0fad1b --- /dev/null +++ b/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts @@ -0,0 +1,21 @@ +import ContinuumArtifactAuthorityError from '../errors/ContinuumArtifactAuthorityError.ts'; +import type ContinuumArtifactDescriptor from './ContinuumArtifactDescriptor.ts'; + +/** Policy gate for admitting generated Continuum family artifacts. */ +export default class ContinuumArtifactIngestionPolicy { + /** Accepts generated artifacts and documented fixtures, rejecting mirrors. */ + ingest(descriptor: ContinuumArtifactDescriptor): ContinuumArtifactDescriptor { + this.assertGeneratedAuthority(descriptor); + return descriptor; + } + + /** Rejects descriptors whose authority would make local mirrors canonical. */ + assertGeneratedAuthority(descriptor: ContinuumArtifactDescriptor): void { + if (descriptor.hasGeneratedAuthority()) { + return; + } + throw new ContinuumArtifactAuthorityError( + `Continuum family ${descriptor.familyId.toString()} must be loaded from a generated artifact or fixture, not ${descriptor.authority.toString()}`, + ); + } +} diff --git a/src/domain/continuum/ContinuumFamilyId.ts b/src/domain/continuum/ContinuumFamilyId.ts new file mode 100644 index 00000000..7c15288e --- /dev/null +++ b/src/domain/continuum/ContinuumFamilyId.ts @@ -0,0 +1,58 @@ +import WarpError from '../errors/WarpError.ts'; + +const RECEIPT_FAMILY_ID = 'receipt-family'; +const SETTLEMENT_FAMILY_ID = 'settlement-family'; +const NEIGHBORHOOD_CORE_FAMILY_ID = 'neighborhood-core-family'; +const RUNTIME_BOUNDARY_FAMILY_ID = 'runtime-boundary-family'; + +export type ContinuumFamilyIdValue = + | typeof RECEIPT_FAMILY_ID + | typeof SETTLEMENT_FAMILY_ID + | typeof NEIGHBORHOOD_CORE_FAMILY_ID + | typeof RUNTIME_BOUNDARY_FAMILY_ID; + +export const CONTINUUM_FAMILY_IDS: readonly ContinuumFamilyIdValue[] = Object.freeze([ + RECEIPT_FAMILY_ID, + SETTLEMENT_FAMILY_ID, + NEIGHBORHOOD_CORE_FAMILY_ID, + RUNTIME_BOUNDARY_FAMILY_ID, +]); + +/** Runtime-backed identifier for a Continuum-owned contract family. */ +export default class ContinuumFamilyId { + readonly value: ContinuumFamilyIdValue; + + constructor(value: string) { + this.value = requireContinuumFamilyId(value); + Object.freeze(this); + } + + /** Returns true when both ids name the same Continuum family. */ + equals(other: ContinuumFamilyId): boolean { + return this.value === other.value; + } + + /** Returns the stable family id string. */ + toString(): string { + return this.value; + } +} + +/** Validates a raw family id string. */ +export function requireContinuumFamilyId(value: string): ContinuumFamilyIdValue { + switch (value) { + case RECEIPT_FAMILY_ID: + return RECEIPT_FAMILY_ID; + case SETTLEMENT_FAMILY_ID: + return SETTLEMENT_FAMILY_ID; + case NEIGHBORHOOD_CORE_FAMILY_ID: + return NEIGHBORHOOD_CORE_FAMILY_ID; + case RUNTIME_BOUNDARY_FAMILY_ID: + return RUNTIME_BOUNDARY_FAMILY_ID; + default: + throw new WarpError( + `Continuum family id must be one of: ${CONTINUUM_FAMILY_IDS.join(', ')}`, + 'E_VALIDATION', + ); + } +} diff --git a/src/domain/errors/ContinuumArtifactAuthorityError.ts b/src/domain/errors/ContinuumArtifactAuthorityError.ts new file mode 100644 index 00000000..4734cd51 --- /dev/null +++ b/src/domain/errors/ContinuumArtifactAuthorityError.ts @@ -0,0 +1,8 @@ +import WarpError, { type WarpErrorOptions } from './WarpError.ts'; + +/** Error thrown when a Continuum artifact would create shadow authority. */ +export default class ContinuumArtifactAuthorityError extends WarpError { + constructor(message: string, options: WarpErrorOptions = {}) { + super(message, 'E_CONTINUUM_ARTIFACT_AUTHORITY', options); + } +} diff --git a/src/domain/errors/index.ts b/src/domain/errors/index.ts index 2dd7cb41..f087fcc3 100644 --- a/src/domain/errors/index.ts +++ b/src/domain/errors/index.ts @@ -1,6 +1,7 @@ /** Custom error classes for domain operations. */ export { default as AuditError } from './AuditError.ts'; +export { default as ContinuumArtifactAuthorityError } from './ContinuumArtifactAuthorityError.ts'; export { default as EncryptionError } from './EncryptionError.ts'; export { default as ForkError } from './ForkError.ts'; export { default as IndexError } from './IndexError.ts'; diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts new file mode 100644 index 00000000..60042a9e --- /dev/null +++ b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts @@ -0,0 +1,115 @@ +import { readFile } from 'node:fs/promises'; + +import ContinuumArtifactDescriptor, { + type ContinuumArtifactDescriptorFields, +} from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; +import ContinuumArtifactIngestionPolicy from '../../domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; + +type JsonObject = Readonly>; + +/** Loads Continuum artifact descriptors from JSON files at the adapter edge. */ +export default class ContinuumArtifactJsonFileAdapter { + private readonly policy: ContinuumArtifactIngestionPolicy; + + constructor(policy: ContinuumArtifactIngestionPolicy = new ContinuumArtifactIngestionPolicy()) { + this.policy = policy; + } + + /** Reads and ingests a generated artifact descriptor from disk. */ + async loadFile(path: string): Promise { + const raw = await readFile(path, 'utf8'); + return this.loadString(raw); + } + + /** Ingests a generated artifact descriptor from JSON text. */ + loadString(raw: string): ContinuumArtifactDescriptor { + const parsed: unknown = JSON.parse(raw); + const fields = parseDescriptorFields(parsed); + return this.policy.ingest(new ContinuumArtifactDescriptor(fields)); + } +} + +/** Converts untrusted JSON into descriptor fields. */ +function parseDescriptorFields(value: unknown): ContinuumArtifactDescriptorFields { + const source = requireJsonObject(value); + const base = { + familyId: readRequiredString(source, 'familyId'), + version: readRequiredString(source, 'version'), + sourceSchemaPath: readRequiredString(source, 'sourceSchemaPath'), + generatedBy: readRequiredString(source, 'generatedBy'), + artifactKind: readRequiredString(source, 'artifactKind'), + authority: readRequiredString(source, 'authority'), + targets: readStringArray(source, 'targets'), + }; + return withOptionalFields(base, source); +} + +/** Adds optional descriptor fields when present. */ +function withOptionalFields( + base: ContinuumArtifactDescriptorFields, + source: JsonObject, +): ContinuumArtifactDescriptorFields { + const witnessScope = readOptionalString(source, 'witnessScope'); + const artifactDigest = readOptionalString(source, 'artifactDigest'); + return { + ...base, + ...(witnessScope !== undefined ? { witnessScope } : {}), + ...(artifactDigest !== undefined ? { artifactDigest } : {}), + }; +} + +/** Requires a non-array JSON object. */ +function requireJsonObject(value: unknown): JsonObject { + if (!isJsonObject(value)) { + throw new AdapterValidationError('Continuum artifact descriptor JSON must be an object'); + } + return value; +} + +/** Returns true when a value is a non-array JSON object. */ +function isJsonObject(value: unknown): value is JsonObject { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +/** Reads a required string field. */ +function readRequiredString(source: JsonObject, key: string): string { + const value = source[key]; + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string`); + } + return value; +} + +/** Reads an optional string field. */ +function readOptionalString(source: JsonObject, key: string): string | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string when present`); + } + return value; +} + +/** Reads a required string array field. */ +function readStringArray(source: JsonObject, key: string): readonly string[] { + const value = source[key]; + if (!Array.isArray(value) || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string array`); + } + const strings: string[] = []; + for (const entry of value) { + strings.push(readStringArrayEntry(entry, key)); + } + return Object.freeze(strings); +} + +/** Reads one string array entry. */ +function readStringArrayEntry(value: unknown, key: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must contain only non-empty strings`); + } + return value; +} diff --git a/test/fixtures/continuum/receipt-family-generated-artifact.json b/test/fixtures/continuum/receipt-family-generated-artifact.json new file mode 100644 index 00000000..cc6c9029 --- /dev/null +++ b/test/fixtures/continuum/receipt-family-generated-artifact.json @@ -0,0 +1,11 @@ +{ + "familyId": "receipt-family", + "version": "0.1.0", + "sourceSchemaPath": "~/git/continuum/schemas/continuum-receipt-family.graphql", + "generatedBy": "wesley witness-continuum --scope receipt-family", + "artifactKind": "continuum.family.fixture", + "authority": "generated-fixture", + "targets": ["typescript", "echo"], + "witnessScope": "receipt-family", + "artifactDigest": "sha256:receipt-fixture" +} diff --git a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts new file mode 100644 index 00000000..b8b86bf2 --- /dev/null +++ b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts @@ -0,0 +1,57 @@ +import { describe, expect, it } from 'vitest'; + +import ContinuumArtifactAuthorityError from '../../../../src/domain/errors/ContinuumArtifactAuthorityError.ts'; +import ContinuumArtifactDescriptor from '../../../../src/domain/continuum/ContinuumArtifactDescriptor.ts'; +import ContinuumArtifactIngestionPolicy from '../../../../src/domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import ContinuumFamilyId from '../../../../src/domain/continuum/ContinuumFamilyId.ts'; + +/** Builds a receipt-family descriptor for policy tests. */ +function makeDescriptor(authority: string): ContinuumArtifactDescriptor { + return new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + version: '0.1.0', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley witness-continuum --scope receipt-family', + artifactKind: 'continuum.family.fixture', + authority, + targets: ['typescript', 'echo'], + witnessScope: 'receipt-family', + }); +} + +describe('ContinuumArtifactIngestionPolicy', () => { + it('accepts documented generated fixtures', () => { + const descriptor = makeDescriptor('generated-fixture'); + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(policy.ingest(descriptor)).toBe(descriptor); + }); + + it('accepts generated artifacts', () => { + const descriptor = makeDescriptor('generated-artifact'); + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(policy.ingest(descriptor)).toBe(descriptor); + }); + + it('rejects local mirrors as family authority', () => { + const descriptor = makeDescriptor('local-mirror'); + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); + }); + + it('rejects handwritten mirrors as family authority', () => { + const descriptor = makeDescriptor('handwritten-mirror'); + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); + }); + + it('keeps family ids runtime-backed', () => { + const descriptor = makeDescriptor('generated-fixture'); + + expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); + expect(descriptor.familyId.toString()).toBe('receipt-family'); + }); +}); diff --git a/test/unit/domain/index.exports.test.ts b/test/unit/domain/index.exports.test.ts index 9e768ac7..c96d5be5 100644 --- a/test/unit/domain/index.exports.test.ts +++ b/test/unit/domain/index.exports.test.ts @@ -40,6 +40,7 @@ import WarpAppDefault, { TraversalError, OperationAbortedError, Observer, + ContinuumArtifactAuthorityError, // Cancellation utilities checkAborted, @@ -57,6 +58,11 @@ import WarpAppDefault, { compareVisibleState, normalizeVisibleStateScope, scopeMaterializedState, + ContinuumArtifactAuthority, + ContinuumArtifactDescriptor, + ContinuumArtifactIngestionPolicy, + ContinuumFamilyId, + ContinuumArtifactJsonFileAdapter, } from '../../../index.ts'; const { WarpGraph, WarpRuntime, Worldline, ObserverView } = (await import('../../../index.ts') as any); @@ -244,6 +250,36 @@ describe('index.ts exports', () => { expect(OperationAbortedError).toBeDefined(); expect(typeof OperationAbortedError).toBe('function'); }); + + it('exports ContinuumArtifactAuthorityError', () => { + expect(ContinuumArtifactAuthorityError).toBeDefined(); + expect(typeof ContinuumArtifactAuthorityError).toBe('function'); + }); + }); + + describe('Continuum compatibility artifacts', () => { + it('exports the artifact descriptor classes', () => { + expect(ContinuumArtifactAuthority).toBeDefined(); + expect(ContinuumArtifactDescriptor).toBeDefined(); + expect(ContinuumArtifactIngestionPolicy).toBeDefined(); + expect(ContinuumFamilyId).toBeDefined(); + expect(ContinuumArtifactJsonFileAdapter).toBeDefined(); + }); + + it('constructs a generated receipt-family descriptor from public exports', () => { + const descriptor = new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + version: '0.1.0', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley witness-continuum --scope receipt-family', + artifactKind: 'continuum.family.fixture', + authority: 'generated-fixture', + targets: ['typescript'], + }); + + expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); + expect(descriptor.hasGeneratedAuthority()).toBe(true); + }); }); describe('cancellation utilities', () => { diff --git a/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts new file mode 100644 index 00000000..e1a08079 --- /dev/null +++ b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts @@ -0,0 +1,64 @@ +import { describe, expect, it } from 'vitest'; +import { fileURLToPath } from 'node:url'; + +import ContinuumArtifactJsonFileAdapter from '../../../../src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; +import ContinuumArtifactAuthorityError from '../../../../src/domain/errors/ContinuumArtifactAuthorityError.ts'; +import AdapterValidationError from '../../../../src/domain/errors/AdapterValidationError.ts'; + +const generatedFixtureJson = `{ + "familyId": "receipt-family", + "version": "0.1.0", + "sourceSchemaPath": "~/git/continuum/schemas/continuum-receipt-family.graphql", + "generatedBy": "wesley witness-continuum --scope receipt-family", + "artifactKind": "continuum.family.fixture", + "authority": "generated-fixture", + "targets": ["typescript", "echo"], + "witnessScope": "receipt-family", + "artifactDigest": "sha256:receipt-fixture" +}`; + +const localMirrorJson = `{ + "familyId": "receipt-family", + "version": "0.1.0", + "sourceSchemaPath": "src/domain/continuum/local-receipt.ts", + "generatedBy": "git-warp", + "artifactKind": "continuum.family.fixture", + "authority": "local-mirror", + "targets": ["typescript"] +}`; + +const generatedFixturePath = fileURLToPath( + new URL('../../../fixtures/continuum/receipt-family-generated-artifact.json', import.meta.url), +); + +describe('ContinuumArtifactJsonFileAdapter', () => { + it('loads generated fixture descriptors', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + const descriptor = adapter.loadString(generatedFixtureJson); + + expect(descriptor.familyId.toString()).toBe('receipt-family'); + expect(descriptor.hasTarget('typescript')).toBe(true); + expect(descriptor.hasGeneratedAuthority()).toBe(true); + expect(descriptor.artifactDigest).toBe('sha256:receipt-fixture'); + }); + + it('loads generated fixture descriptor files', async () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + const descriptor = await adapter.loadFile(generatedFixturePath); + + expect(descriptor.familyId.toString()).toBe('receipt-family'); + expect(descriptor.witnessScope).toBe('receipt-family'); + }); + + it('rejects local mirrors before they become authority', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString(localMirrorJson)).toThrow(ContinuumArtifactAuthorityError); + }); + + it('rejects malformed descriptor JSON', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString('{ "familyId": "receipt-family" }')).toThrow(AdapterValidationError); + }); +}); From 59d69b17b579700fa8196ffad80ec2ffdfbd38dc Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 10:34:19 -0700 Subject: [PATCH 07/16] test: cover continuum error export --- test/unit/domain/errors/index.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/test/unit/domain/errors/index.test.ts b/test/unit/domain/errors/index.test.ts index 30fc4cf7..59c75c57 100644 --- a/test/unit/domain/errors/index.test.ts +++ b/test/unit/domain/errors/index.test.ts @@ -6,6 +6,7 @@ describe('domain/errors index barrel', () => { expect(Object.keys(errors).sort()).toEqual([ 'AuditError', + 'ContinuumArtifactAuthorityError', 'EncryptionError', 'ForkError', 'IndexError', From e1b483e74d114b3281604d3b49dc75d6bd173811 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 11:34:22 -0700 Subject: [PATCH 08/16] fix: harden continuum artifact ingestion --- docs/BEARING.md | 7 +- .../v18-continuum-compatibility-charter.md | 2 +- .../v18-continuum-artifact-ingestion.md | 56 ++- index.ts | 2 + .../continuum/ContinuumArtifactAuthority.ts | 27 +- .../continuum/ContinuumArtifactDescriptor.ts | 59 ++- src/domain/continuum/ContinuumFamilyId.ts | 27 +- .../ContinuumArtifactJsonFileAdapter.ts | 400 ++++++++++++++++-- .../receipt-family-generated-artifact.json | 84 +++- ...pt-family-wesley-realization-manifest.json | 67 +++ .../ContinuumArtifactIngestionPolicy.test.ts | 104 ++++- .../ContinuumArtifactJsonFileAdapter.test.ts | 351 +++++++++++++-- 12 files changed, 1063 insertions(+), 123 deletions(-) create mode 100644 test/fixtures/continuum/receipt-family-wesley-realization-manifest.json diff --git a/docs/BEARING.md b/docs/BEARING.md index ce239209..c6c28895 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -56,7 +56,7 @@ Continuum role. Current branch state at this boundary: -- Branch: `codex/v18-continuum-opening` +- Branch: `v18-continuum-opening` - Release tag: `v17.0.0` - Latest remote head inspected: `origin/main` at `5afdd3eb` - Latest package version: `17.0.0` @@ -169,7 +169,10 @@ before the final commit for that slice, and mark completed items with `- [x]`. - [x] 4. Define git-warp's WARP Optic realization map: observer plan, bounded slice, lowering surface, admissibility law, and retention contract. - [x] 5. Add a generated-artifact ingestion path for Continuum families, with a - guard against handwritten local mirrors becoming contract authority. + guard against handwritten local mirrors becoming contract authority. The + current seam admits Continuum receipt-family fixture JSON and Wesley + realization manifest JSON through explicit load context; it rejects + self-attested authority fields from artifact JSON. - [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. - [ ] 7. Prove the patch commit visibility contract: success means canonical diff --git a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md index aa2a3993..bc935fdb 100644 --- a/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md +++ b/docs/design/0146-v18-continuum-compatibility-charter/v18-continuum-compatibility-charter.md @@ -122,7 +122,7 @@ contract and not merely mapped from local git-warp facts. The v18 opening campaign is on track when: -- [ ] `BEARING.md` tracks the running v18 task list. +- [x] `BEARING.md` tracks the running v18 task list. - [x] A cross-repo contract matrix names each family, generated artifact, local source fact, consumer, and missing witness. - [x] A WARP Optic realization map exists for `git-warp`. diff --git a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md index e7f8d79d..5aa3460d 100644 --- a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md +++ b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md @@ -32,9 +32,12 @@ This slice adds: local mirrors, and handwritten mirrors; - `ContinuumArtifactDescriptor` as the runtime-backed descriptor object; - `ContinuumArtifactIngestionPolicy` as the authority guard; -- `ContinuumArtifactJsonFileAdapter` as the infrastructure-edge JSON loader; +- `ContinuumArtifactJsonFileAdapter` as the infrastructure-edge JSON loader + for Continuum fixture JSON and Wesley realization manifest JSON; - `test/fixtures/continuum/receipt-family-generated-artifact.json` as the first - generated-family fixture descriptor. + receipt-family Continuum fixture; +- `test/fixtures/continuum/receipt-family-wesley-realization-manifest.json` as + the first Wesley realization manifest fixture. The guard accepts only: @@ -45,12 +48,18 @@ It rejects: - `local-mirror` - `handwritten-mirror` +- JSON that attempts to self-attest an `authority` field ## Boundary Law JSON parsing stays in `src/infrastructure/adapters/`. Domain code receives validated constructor fields and runtime-backed objects. +Authority is not read from untrusted artifact JSON. The adapter receives +authority through explicit load context, validates the artifact shape, and then +lets the domain policy decide whether that context can become descriptor +authority. + The descriptor does not parse GraphQL, generate TypeScript, or claim family semantics. It only records which generated-family artifact or fixture is being admitted and whether that admission posture is allowed. @@ -64,23 +73,48 @@ npx eslint src/domain/continuum/ContinuumFamilyId.ts \ src/domain/continuum/ContinuumArtifactAuthority.ts \ src/domain/continuum/ContinuumArtifactDescriptor.ts \ src/domain/continuum/ContinuumArtifactIngestionPolicy.ts \ - src/domain/errors/ContinuumArtifactAuthorityError.ts \ - src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts + src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts \ + test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts \ + test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts \ + test/unit/domain/index.exports.test.ts npm run typecheck:src npm run typecheck:test npm run typecheck:surface npx vitest run \ test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts \ - test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts + test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts \ + test/unit/domain/index.exports.test.ts \ + test/unit/domain/errors/index.test.ts ``` -Observed focused test result: +Observed focused Continuum-suite test result: ```text Test Files 2 passed (2) -Tests 9 passed (9) +Tests 22 passed (22) +``` + +Observed focused export/error sweep: + +```text +Test Files 4 passed (4) +Tests 72 passed (72) ``` +Observed focused coverage result for the two new focused suites: + +```text +npx vitest run --coverage \ + test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts \ + test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +domain/continuum 100% lines +ContinuumArtifactJsonFileAdapter.ts 100% lines +``` + +That targeted coverage command exits nonzero because the repository global +threshold is applied to a two-suite subset; the useful signal is the per-file +coverage for the touched Continuum files. + ## SSJS Scorecard - Runtime-backed forms: green; new Continuum concepts are classes with @@ -91,10 +125,12 @@ Tests 9 passed (9) ingestion policy owns authority decisions. - Message parsing: green; no behavior branches parse free-form messages. - Ambient time or entropy: green; no ambient time or entropy introduced. -- Fake shape trust or cast-cosplay: green; mirror descriptors are rejected - before ingestion. +- Fake shape trust or cast-cosplay: green; generated-family authority is carried + by load context, self-attested JSON authority is rejected, and the accepted + shapes are Continuum fixture JSON and Wesley realization manifest JSON. ## Closeout This closes BEARING task 5 and gives later receipt-family projection work a -safe generated-artifact entry point. +safe generated-artifact entry point without making local mirrors or +self-attested descriptors contract authority. diff --git a/index.ts b/index.ts index ce8b8572..260c1a82 100644 --- a/index.ts +++ b/index.ts @@ -216,6 +216,7 @@ import ContinuumArtifactJsonFileAdapter from './src/infrastructure/adapters/Cont import type { ContinuumArtifactAuthorityValue } from './src/domain/continuum/ContinuumArtifactAuthority.ts'; import type { ContinuumArtifactDescriptorFields } from './src/domain/continuum/ContinuumArtifactDescriptor.ts'; import type { ContinuumFamilyIdValue } from './src/domain/continuum/ContinuumFamilyId.ts'; +import type { ContinuumArtifactJsonLoadContext } from './src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; export { GitGraphAdapter, @@ -383,6 +384,7 @@ export type { SyncRateLimitConfig, ContinuumArtifactAuthorityValue, ContinuumArtifactDescriptorFields, + ContinuumArtifactJsonLoadContext, ContinuumFamilyIdValue, }; diff --git a/src/domain/continuum/ContinuumArtifactAuthority.ts b/src/domain/continuum/ContinuumArtifactAuthority.ts index 589b1a7a..3059a07d 100644 --- a/src/domain/continuum/ContinuumArtifactAuthority.ts +++ b/src/domain/continuum/ContinuumArtifactAuthority.ts @@ -43,19 +43,18 @@ export default class ContinuumArtifactAuthority { /** Validates a raw authority posture string. */ export function requireContinuumArtifactAuthority(value: string): ContinuumArtifactAuthorityValue { - switch (value) { - case GENERATED_ARTIFACT_AUTHORITY: - return GENERATED_ARTIFACT_AUTHORITY; - case GENERATED_FIXTURE_AUTHORITY: - return GENERATED_FIXTURE_AUTHORITY; - case LOCAL_MIRROR_AUTHORITY: - return LOCAL_MIRROR_AUTHORITY; - case HANDWRITTEN_MIRROR_AUTHORITY: - return HANDWRITTEN_MIRROR_AUTHORITY; - default: - throw new WarpError( - `Continuum artifact authority must be one of: ${CONTINUUM_ARTIFACT_AUTHORITIES.join(', ')}`, - 'E_VALIDATION', - ); + if (typeof value !== 'string') { + throw new WarpError( + `Continuum artifact authority must be one of: ${CONTINUUM_ARTIFACT_AUTHORITIES.join(', ')}`, + 'E_VALIDATION', + ); + } + const valid = CONTINUUM_ARTIFACT_AUTHORITIES.find((candidate) => candidate === value); + if (valid === undefined) { + throw new WarpError( + `Continuum artifact authority must be one of: ${CONTINUUM_ARTIFACT_AUTHORITIES.join(', ')}`, + 'E_VALIDATION', + ); } + return valid; } diff --git a/src/domain/continuum/ContinuumArtifactDescriptor.ts b/src/domain/continuum/ContinuumArtifactDescriptor.ts index d474b8b1..bfb48b29 100644 --- a/src/domain/continuum/ContinuumArtifactDescriptor.ts +++ b/src/domain/continuum/ContinuumArtifactDescriptor.ts @@ -4,39 +4,66 @@ import WarpError from '../errors/WarpError.ts'; export type ContinuumArtifactDescriptorFields = { readonly familyId: string | ContinuumFamilyId; - readonly version: string; readonly sourceSchemaPath: string; readonly generatedBy: string; readonly artifactKind: string; readonly authority: string | ContinuumArtifactAuthority; readonly targets: readonly string[]; + readonly version?: string; readonly witnessScope?: string; readonly artifactDigest?: string; + readonly schemaHash?: string; + readonly sourceHash?: string; + readonly integrityStatus?: string; + readonly integrityScope?: string; + readonly hashAlgorithm?: string; + readonly signatureAlgorithm?: string; + readonly signatureKeyId?: string; + readonly generatedLegs?: readonly string[]; + readonly generatedFiles?: readonly string[]; }; /** Runtime-backed descriptor for a generated Continuum family artifact. */ export default class ContinuumArtifactDescriptor { readonly familyId: ContinuumFamilyId; - readonly version: string; readonly sourceSchemaPath: string; readonly generatedBy: string; readonly artifactKind: string; readonly authority: ContinuumArtifactAuthority; readonly targets: readonly string[]; + readonly version: string | undefined; readonly witnessScope: string | undefined; readonly artifactDigest: string | undefined; + readonly schemaHash: string | undefined; + readonly sourceHash: string | undefined; + readonly integrityStatus: string | undefined; + readonly integrityScope: string | undefined; + readonly hashAlgorithm: string | undefined; + readonly signatureAlgorithm: string | undefined; + readonly signatureKeyId: string | undefined; + readonly generatedLegs: readonly string[] | undefined; + readonly generatedFiles: readonly string[] | undefined; constructor(fields: ContinuumArtifactDescriptorFields) { - const { familyId, version, sourceSchemaPath, generatedBy, artifactKind, authority, targets } = fields; + const { familyId, sourceSchemaPath, generatedBy, artifactKind, authority, targets } = fields; this.familyId = normalizeFamilyId(familyId); - this.version = requireNonEmptyString(version, 'version'); this.sourceSchemaPath = requireNonEmptyString(sourceSchemaPath, 'sourceSchemaPath'); this.generatedBy = requireNonEmptyString(generatedBy, 'generatedBy'); this.artifactKind = requireNonEmptyString(artifactKind, 'artifactKind'); this.authority = normalizeAuthority(authority); this.targets = freezeTargets(targets); + this.version = optionalNonEmptyString(fields.version, 'version'); this.witnessScope = optionalNonEmptyString(fields.witnessScope, 'witnessScope'); this.artifactDigest = optionalNonEmptyString(fields.artifactDigest, 'artifactDigest'); + this.schemaHash = optionalNonEmptyString(fields.schemaHash, 'schemaHash'); + this.sourceHash = optionalNonEmptyString(fields.sourceHash, 'sourceHash'); + this.integrityStatus = optionalNonEmptyString(fields.integrityStatus, 'integrityStatus'); + this.integrityScope = optionalNonEmptyString(fields.integrityScope, 'integrityScope'); + this.hashAlgorithm = optionalNonEmptyString(fields.hashAlgorithm, 'hashAlgorithm'); + this.signatureAlgorithm = optionalNonEmptyString(fields.signatureAlgorithm, 'signatureAlgorithm'); + this.signatureKeyId = optionalNonEmptyString(fields.signatureKeyId, 'signatureKeyId'); + this.generatedLegs = optionalStringArray(fields.generatedLegs, 'generatedLegs'); + this.generatedFiles = optionalStringArray(fields.generatedFiles, 'generatedFiles'); Object.freeze(this); } @@ -69,7 +96,7 @@ function normalizeAuthority(value: string | ContinuumArtifactAuthority): Continu /** Validates a required non-empty string. */ function requireNonEmptyString(value: string, name: string): string { - if (value.length === 0) { + if (typeof value !== 'string' || value.length === 0) { throw new WarpError(`${name} must be a non-empty string`, 'E_VALIDATION'); } return value; @@ -85,12 +112,28 @@ function optionalNonEmptyString(value: string | undefined, name: string): string /** Freezes and validates a generated target list. */ function freezeTargets(targets: readonly string[]): readonly string[] { - if (targets.length === 0) { + if (!Array.isArray(targets) || targets.length === 0) { throw new WarpError('targets must contain at least one generated target', 'E_VALIDATION'); } + return freezeStringArray(targets, 'targets[]'); +} + +/** Validates an optional generated string list. */ +function optionalStringArray(value: readonly string[] | undefined, name: string): readonly string[] | undefined { + if (value === undefined) { + return undefined; + } + if (!Array.isArray(value)) { + throw new WarpError(`${name} must be a string array`, 'E_VALIDATION'); + } + return freezeStringArray(value, `${name}[]`); +} + +/** Freezes and validates a generated string list. */ +function freezeStringArray(values: readonly string[], name: string): readonly string[] { const normalized: string[] = []; - for (const target of targets) { - normalized.push(requireNonEmptyString(target, 'targets[]')); + for (const value of values) { + normalized.push(requireNonEmptyString(value, name)); } return Object.freeze(normalized); } diff --git a/src/domain/continuum/ContinuumFamilyId.ts b/src/domain/continuum/ContinuumFamilyId.ts index 7c15288e..fab49ef0 100644 --- a/src/domain/continuum/ContinuumFamilyId.ts +++ b/src/domain/continuum/ContinuumFamilyId.ts @@ -40,19 +40,18 @@ export default class ContinuumFamilyId { /** Validates a raw family id string. */ export function requireContinuumFamilyId(value: string): ContinuumFamilyIdValue { - switch (value) { - case RECEIPT_FAMILY_ID: - return RECEIPT_FAMILY_ID; - case SETTLEMENT_FAMILY_ID: - return SETTLEMENT_FAMILY_ID; - case NEIGHBORHOOD_CORE_FAMILY_ID: - return NEIGHBORHOOD_CORE_FAMILY_ID; - case RUNTIME_BOUNDARY_FAMILY_ID: - return RUNTIME_BOUNDARY_FAMILY_ID; - default: - throw new WarpError( - `Continuum family id must be one of: ${CONTINUUM_FAMILY_IDS.join(', ')}`, - 'E_VALIDATION', - ); + if (typeof value !== 'string') { + throw new WarpError( + `Continuum family id must be one of: ${CONTINUUM_FAMILY_IDS.join(', ')}`, + 'E_VALIDATION', + ); } + const valid = CONTINUUM_FAMILY_IDS.find((candidate) => candidate === value); + if (valid === undefined) { + throw new WarpError( + `Continuum family id must be one of: ${CONTINUUM_FAMILY_IDS.join(', ')}`, + 'E_VALIDATION', + ); + } + return valid; } diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts index 60042a9e..c0f297e5 100644 --- a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts +++ b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts @@ -1,13 +1,48 @@ import { readFile } from 'node:fs/promises'; +import type ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; import ContinuumArtifactDescriptor, { type ContinuumArtifactDescriptorFields, } from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; import ContinuumArtifactIngestionPolicy from '../../domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts'; import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; +const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; +const CONTINUUM_FIXTURE_GENERATOR = 'continuum/wesley fixture'; +const CONTINUUM_FIXTURE_TARGET = 'continuum-fixture'; + type JsonObject = Readonly>; +export type ContinuumArtifactJsonLoadContext = { + readonly familyId: string | ContinuumFamilyId; + readonly authority: string | ContinuumArtifactAuthority; + readonly sourceSchemaPath?: string; + readonly generatedBy?: string; + readonly artifactKind?: string; + readonly version?: string; + readonly targets?: readonly string[]; + readonly witnessScope?: string; + readonly artifactDigest?: string; +}; + +type DescriptorFieldSource = { + readonly sourceSchemaPath: string; + readonly generatedBy: string; + readonly artifactKind: string; + readonly targets: readonly string[]; + readonly schemaHash?: string; + readonly sourceHash?: string; + readonly integrityStatus?: string; + readonly integrityScope?: string; + readonly hashAlgorithm?: string; + readonly signatureAlgorithm?: string; + readonly signatureKeyId?: string; + readonly generatedLegs?: readonly string[]; + readonly generatedFiles?: readonly string[]; +}; + /** Loads Continuum artifact descriptors from JSON files at the adapter edge. */ export default class ContinuumArtifactJsonFileAdapter { private readonly policy: ContinuumArtifactIngestionPolicy; @@ -17,52 +52,314 @@ export default class ContinuumArtifactJsonFileAdapter { } /** Reads and ingests a generated artifact descriptor from disk. */ - async loadFile(path: string): Promise { + async loadFile( + path: string, + context: ContinuumArtifactJsonLoadContext, + ): Promise { const raw = await readFile(path, 'utf8'); - return this.loadString(raw); + return this.loadString(raw, context); } /** Ingests a generated artifact descriptor from JSON text. */ - loadString(raw: string): ContinuumArtifactDescriptor { - const parsed: unknown = JSON.parse(raw); - const fields = parseDescriptorFields(parsed); + loadString(raw: string, context: ContinuumArtifactJsonLoadContext): ContinuumArtifactDescriptor { + const parsed = parseJson(raw); + const fields = parseDescriptorFields(parsed, context); return this.policy.ingest(new ContinuumArtifactDescriptor(fields)); } } +/** Parses untrusted descriptor JSON without leaking platform SyntaxError. */ +function parseJson(raw: string): unknown { + try { + return JSON.parse(raw); + } catch { + throw new AdapterValidationError('Continuum artifact descriptor JSON must be valid JSON'); + } +} + /** Converts untrusted JSON into descriptor fields. */ -function parseDescriptorFields(value: unknown): ContinuumArtifactDescriptorFields { - const source = requireJsonObject(value); - const base = { - familyId: readRequiredString(source, 'familyId'), - version: readRequiredString(source, 'version'), - sourceSchemaPath: readRequiredString(source, 'sourceSchemaPath'), - generatedBy: readRequiredString(source, 'generatedBy'), - artifactKind: readRequiredString(source, 'artifactKind'), - authority: readRequiredString(source, 'authority'), +function parseDescriptorFields( + value: unknown, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + const source = requireJsonObject(value, 'Continuum artifact descriptor JSON'); + if (source['kind'] === WESLEY_REALIZATION_MANIFEST_KIND) { + return parseWesleyRealizationManifest(source, context); + } + if (isContinuumFamilyFixture(source)) { + return parseContinuumFamilyFixture(source, context); + } + throw new AdapterValidationError( + 'Continuum artifact descriptor JSON must be a Wesley realization manifest or Continuum family fixture', + ); +} + +/** Converts a Wesley realization manifest into descriptor fields. */ +function parseWesleyRealizationManifest( + source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + validateWesleyManifestEnvelope(source); + const integrity = readSealedIntegrity(source); + const legs = readGeneratedLegs(source); + return descriptorFields(context, { + ...wesleyManifestFields(source, context), + ...integrity, + generatedLegs: legs.names, + generatedFiles: legs.files, + }); +} + +/** Validates the non-semantic envelope fields on a Wesley manifest. */ +function validateWesleyManifestEnvelope(source: JsonObject): void { + rejectUnknownKeys( + source, + ['kind', 'schemaPath', 'canonicalSchemaPath', 'schemaHash', 'sourceHash', 'outDir', 'targets', 'integrity', 'generatedLegs', 'proves', 'doesNotProve'], + 'Wesley realization manifest', + ); + readOptionalString(source, 'canonicalSchemaPath'); + readOptionalString(source, 'outDir'); + readOptionalStringArray(source, 'proves'); + readOptionalStringArray(source, 'doesNotProve'); +} + +/** Reads the descriptor-facing fields from a Wesley realization manifest. */ +function wesleyManifestFields( + source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): DescriptorFieldSource { + return { + sourceSchemaPath: readRequiredString(source, 'schemaPath'), + generatedBy: context.generatedBy ?? 'wesley compile', + artifactKind: context.artifactKind ?? WESLEY_REALIZATION_MANIFEST_KIND, targets: readStringArray(source, 'targets'), + schemaHash: readRequiredString(source, 'schemaHash'), + sourceHash: readRequiredString(source, 'sourceHash'), }; - return withOptionalFields(base, source); } -/** Adds optional descriptor fields when present. */ -function withOptionalFields( - base: ContinuumArtifactDescriptorFields, +/** Converts a Continuum family fixture into descriptor fields. */ +function parseContinuumFamilyFixture( source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + rejectUnknownKeys( + source, + ['objectTypes', 'enumTypes', 'ops', 'invariants', 'footprints', 'types'], + 'Continuum family fixture', + ); + readOptionalStringArray(source, 'objectTypes'); + readOptionalStringArray(source, 'enumTypes'); + readOperations(source); + readOptionalStringArray(source, 'invariants'); + readOptionalFootprints(source); + readOptionalTypeMap(source); + + return descriptorFields(context, { + sourceSchemaPath: readContextString(context.sourceSchemaPath, 'sourceSchemaPath'), + generatedBy: context.generatedBy ?? CONTINUUM_FIXTURE_GENERATOR, + artifactKind: context.artifactKind ?? CONTINUUM_FIXTURE_KIND, + targets: context.targets ?? [CONTINUUM_FIXTURE_TARGET], + }); +} + +/** Builds descriptor fields without trusting authority from the untrusted JSON. */ +function descriptorFields( + context: ContinuumArtifactJsonLoadContext, + required: DescriptorFieldSource, ): ContinuumArtifactDescriptorFields { - const witnessScope = readOptionalString(source, 'witnessScope'); - const artifactDigest = readOptionalString(source, 'artifactDigest'); return { - ...base, - ...(witnessScope !== undefined ? { witnessScope } : {}), - ...(artifactDigest !== undefined ? { artifactDigest } : {}), + familyId: context.familyId, + sourceSchemaPath: required.sourceSchemaPath, + generatedBy: required.generatedBy, + artifactKind: required.artifactKind, + authority: context.authority, + targets: required.targets, + ...contextDescriptorFields(context), + ...sourceHashFields(required), + ...integrityDescriptorFields(required), + ...signatureDescriptorFields(required), + ...generatedInventoryFields(required), }; } +/** Selects optional descriptor fields provided by load context. */ +function contextDescriptorFields( + context: ContinuumArtifactJsonLoadContext, +): Partial { + return { + ...(context.version !== undefined ? { version: context.version } : {}), + ...(context.witnessScope !== undefined ? { witnessScope: context.witnessScope } : {}), + ...(context.artifactDigest !== undefined ? { artifactDigest: context.artifactDigest } : {}), + }; +} + +/** Selects optional schema hash fields from source evidence. */ +function sourceHashFields(required: DescriptorFieldSource): Partial { + return { + ...(required.schemaHash !== undefined ? { schemaHash: required.schemaHash } : {}), + ...(required.sourceHash !== undefined ? { sourceHash: required.sourceHash } : {}), + }; +} + +/** Selects optional integrity fields from source evidence. */ +function integrityDescriptorFields(required: DescriptorFieldSource): Partial { + return { + ...(required.integrityStatus !== undefined ? { integrityStatus: required.integrityStatus } : {}), + ...(required.integrityScope !== undefined ? { integrityScope: required.integrityScope } : {}), + ...(required.hashAlgorithm !== undefined ? { hashAlgorithm: required.hashAlgorithm } : {}), + }; +} + +/** Selects optional signature fields from source evidence. */ +function signatureDescriptorFields(required: DescriptorFieldSource): Partial { + return { + ...(required.signatureAlgorithm !== undefined ? { signatureAlgorithm: required.signatureAlgorithm } : {}), + ...(required.signatureKeyId !== undefined ? { signatureKeyId: required.signatureKeyId } : {}), + }; +} + +/** Selects optional generated inventory fields from source evidence. */ +function generatedInventoryFields(required: DescriptorFieldSource): Partial { + return { + ...(required.generatedLegs !== undefined ? { generatedLegs: required.generatedLegs } : {}), + ...(required.generatedFiles !== undefined ? { generatedFiles: required.generatedFiles } : {}), + }; +} + +/** Reads and validates a Wesley integrity block. */ +function readSealedIntegrity(source: JsonObject): { + readonly integrityStatus: string; + readonly integrityScope: string; + readonly hashAlgorithm: string; + readonly signatureAlgorithm: string; + readonly signatureKeyId: string; +} { + const integrity = requireJsonObject(source['integrity'], 'Wesley realization manifest integrity'); + rejectUnknownKeys( + integrity, + ['status', 'scope', 'hashAlgorithm', 'signatureAlgorithm', 'signatureKeyId'], + 'Wesley realization manifest integrity', + ); + const status = readRequiredString(integrity, 'status'); + if (status !== 'sealed') { + throw new AdapterValidationError('Wesley realization manifest integrity status must be sealed'); + } + return { + integrityStatus: status, + integrityScope: readRequiredString(integrity, 'scope'), + hashAlgorithm: readRequiredString(integrity, 'hashAlgorithm'), + signatureAlgorithm: readRequiredString(integrity, 'signatureAlgorithm'), + signatureKeyId: readRequiredString(integrity, 'signatureKeyId'), + }; +} + +/** Reads and validates Wesley generated leg inventory. */ +function readGeneratedLegs(source: JsonObject): { + readonly names: readonly string[]; + readonly files: readonly string[]; +} { + const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); + const names = Object.freeze(Object.keys(generatedLegs).sort()); + const files: string[] = []; + for (const name of names) { + const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); + rejectUnknownKeys( + leg, + ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], + `Wesley generated leg "${name}"`, + ); + readRequiredString(leg, 'outDir'); + readRequiredString(leg, 'schemaHash'); + readRequiredString(leg, 'sourceHash'); + readOptionalStringArray(leg, 'targets'); + readOptionalNumber(leg, 'artifactCount'); + for (const path of readGeneratedFiles(leg, name)) { + files.push(path); + } + } + return { names, files: Object.freeze(files.sort()) }; +} + +/** Reads generated file entries from one Wesley generated leg. */ +function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { + const value = source['files']; + if (value === undefined) { + return []; + } + if (!Array.isArray(value)) { + throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); + } + const files: string[] = []; + for (const entry of value) { + const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); + rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); + files.push(readRequiredString(file, 'path')); + readRequiredNumber(file, 'size'); + readRequiredString(file, 'contentHash'); + readRequiredString(file, 'signature'); + } + return Object.freeze(files); +} + +/** Returns true when the top-level object has the Continuum fixture shape. */ +function isContinuumFamilyFixture(source: JsonObject): boolean { + return Array.isArray(source['ops']) && ( + Array.isArray(source['objectTypes']) || + isJsonObject(source['types']) + ); +} + +/** Reads and validates Continuum fixture operations. */ +function readOperations(source: JsonObject): void { + const { ops } = source; + if (!Array.isArray(ops) || ops.length === 0) { + throw new AdapterValidationError('Continuum family fixture field "ops" must be a non-empty operation array'); + } + for (const entry of ops) { + const op = requireJsonObject(entry, 'Continuum family fixture operation'); + rejectUnknownKeys(op, ['name', 'resultType'], 'Continuum family fixture operation'); + readRequiredString(op, 'name'); + readRequiredString(op, 'resultType'); + } +} + +/** Reads and validates optional Continuum fixture footprints. */ +function readOptionalFootprints(source: JsonObject): void { + const { footprints } = source; + if (footprints === undefined) { + return; + } + if (!Array.isArray(footprints)) { + throw new AdapterValidationError('Continuum family fixture field "footprints" must be an array'); + } + for (const entry of footprints) { + const footprint = requireJsonObject(entry, 'Continuum family fixture footprint'); + rejectUnknownKeys(footprint, ['opName', 'reads', 'writes', 'creates', 'deletes'], 'Continuum family fixture footprint'); + readRequiredString(footprint, 'opName'); + readStringArray(footprint, 'reads'); + readStringArray(footprint, 'writes'); + readStringArray(footprint, 'creates'); + readStringArray(footprint, 'deletes'); + } +} + +/** Reads and validates an optional Continuum boundary type map. */ +function readOptionalTypeMap(source: JsonObject): void { + const { types } = source; + if (types === undefined) { + return; + } + const typeMap = requireJsonObject(types, 'Continuum family fixture types'); + for (const name of Object.keys(typeMap)) { + readStringArray(typeMap, name); + } +} + /** Requires a non-array JSON object. */ -function requireJsonObject(value: unknown): JsonObject { +function requireJsonObject(value: unknown, label: string): JsonObject { if (!isJsonObject(value)) { - throw new AdapterValidationError('Continuum artifact descriptor JSON must be an object'); + throw new AdapterValidationError(`${label} must be an object`); } return value; } @@ -72,6 +369,15 @@ function isJsonObject(value: unknown): value is JsonObject { return value !== null && typeof value === 'object' && !Array.isArray(value); } +/** Rejects unexpected fields at a parsed JSON boundary. */ +function rejectUnknownKeys(source: JsonObject, allowed: readonly string[], label: string): void { + for (const key of Object.keys(source)) { + if (!allowed.includes(key)) { + throw new AdapterValidationError(`${label} field "${key}" is not allowed`); + } + } +} + /** Reads a required string field. */ function readRequiredString(source: JsonObject, key: string): string { const value = source[key]; @@ -81,23 +387,55 @@ function readRequiredString(source: JsonObject, key: string): string { return value; } +/** Reads a required context string. */ +function readContextString(value: string | undefined, key: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact load context field "${key}" must be a non-empty string`); + } + return value; +} + +/** Reads a required number field. */ +function readRequiredNumber(source: JsonObject, key: string): number { + const value = source[key]; + if (typeof value !== 'number' || !Number.isFinite(value)) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a finite number`); + } + return value; +} + +/** Reads an optional number field. */ +function readOptionalNumber(source: JsonObject, key: string): number | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + return readRequiredNumber(source, key); +} + +/** Reads an optional string array field. */ +function readOptionalStringArray(source: JsonObject, key: string): readonly string[] | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + return readStringArray(source, key); +} + /** Reads an optional string field. */ function readOptionalString(source: JsonObject, key: string): string | undefined { const value = source[key]; if (value === undefined) { return undefined; } - if (typeof value !== 'string' || value.length === 0) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string when present`); - } - return value; + return readRequiredString(source, key); } /** Reads a required string array field. */ function readStringArray(source: JsonObject, key: string): readonly string[] { const value = source[key]; - if (!Array.isArray(value) || value.length === 0) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string array`); + if (!Array.isArray(value)) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a string array`); } const strings: string[] = []; for (const entry of value) { diff --git a/test/fixtures/continuum/receipt-family-generated-artifact.json b/test/fixtures/continuum/receipt-family-generated-artifact.json index cc6c9029..b7d6c950 100644 --- a/test/fixtures/continuum/receipt-family-generated-artifact.json +++ b/test/fixtures/continuum/receipt-family-generated-artifact.json @@ -1,11 +1,77 @@ { - "familyId": "receipt-family", - "version": "0.1.0", - "sourceSchemaPath": "~/git/continuum/schemas/continuum-receipt-family.graphql", - "generatedBy": "wesley witness-continuum --scope receipt-family", - "artifactKind": "continuum.family.fixture", - "authority": "generated-fixture", - "targets": ["typescript", "echo"], - "witnessScope": "receipt-family", - "artifactDigest": "sha256:receipt-fixture" + "objectTypes": [ + "Capability", + "ContinuumReceiptFamilyInvariants", + "DeliveryObservation", + "Receipt", + "Witness" + ], + "enumTypes": [ + "CapabilityScope", + "DeliveryOutcome", + "ExecutionMode", + "WitnessKind" + ], + "ops": [ + { + "name": "capabilities", + "resultType": "Capability" + }, + { + "name": "deliveryObservations", + "resultType": "DeliveryObservation" + }, + { + "name": "receipts", + "resultType": "Receipt" + }, + { + "name": "witnesses", + "resultType": "Witness" + } + ], + "invariants": [ + "delivery_links_receipt", + "receipt_input_tick_non_negative", + "receipt_output_tick_non_negative", + "witness_links_receipt" + ], + "footprints": [ + { + "opName": "capabilities", + "reads": [ + "Capability" + ], + "writes": [], + "creates": [], + "deletes": [] + }, + { + "opName": "deliveryObservations", + "reads": [ + "DeliveryObservation" + ], + "writes": [], + "creates": [], + "deletes": [] + }, + { + "opName": "receipts", + "reads": [ + "Receipt" + ], + "writes": [], + "creates": [], + "deletes": [] + }, + { + "opName": "witnesses", + "reads": [ + "Witness" + ], + "writes": [], + "creates": [], + "deletes": [] + } + ] } diff --git a/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json b/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json new file mode 100644 index 00000000..555d3b6a --- /dev/null +++ b/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json @@ -0,0 +1,67 @@ +{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql", + "schemaHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "sourceHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "outDir": "out/proof", + "targets": [ + "warp-ttd", + "echo" + ], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "builtin:wesley-v0.1.0-local-dev-hmac-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "out/proof/warp-ttd", + "schemaHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "sourceHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "targets": [ + "manifest", + "typescript" + ], + "files": [ + { + "path": "manifest/schema.json", + "size": 12101, + "contentHash": "f3322bcc5f07b8a635e24302d77926c983d9dc43c63c1ba7ab50f6ca6e229e0a", + "signature": "dc916e2920dc599d1ca1ca5b197b7bc7deb6af7a0c319abf2d26d931d0015e2c" + }, + { + "path": "typescript/types.ts", + "size": 2463, + "contentHash": "54b5d24902357b7504e8bccadaa329133808eed6e3a77433ceaf80cf285a5afe", + "signature": "831994c0a68c78a7ed1100568966ab90f6a7a76c8abce5f4419bb1bec2c622f1" + } + ] + }, + "echo": { + "outDir": "out/proof/echo", + "schemaHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "sourceHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", + "artifactCount": 1, + "files": [ + { + "path": "ir.json", + "size": 11881, + "contentHash": "ba2e2fa15ed865571fde7a588f346ff4bc4ceded28907d4ab383d393fdab6dfd", + "signature": "e164174476ccbbc336bf0262b2926937794eedf0a06bdb812fff5032111506be" + } + ] + } + }, + "proves": [ + "one authored schema path was compiled into one or more generated consumer legs", + "generated legs share one authored schema hash", + "the emitted files for each selected target are inspectable from this realization manifest" + ], + "doesNotProve": [ + "cross-leg conformance beyond shared schema identity", + "runtime semantics" + ] +} diff --git a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts index b8b86bf2..c5a1ea55 100644 --- a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts +++ b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts @@ -1,20 +1,22 @@ import { describe, expect, it } from 'vitest'; import ContinuumArtifactAuthorityError from '../../../../src/domain/errors/ContinuumArtifactAuthorityError.ts'; +import ContinuumArtifactAuthority from '../../../../src/domain/continuum/ContinuumArtifactAuthority.ts'; import ContinuumArtifactDescriptor from '../../../../src/domain/continuum/ContinuumArtifactDescriptor.ts'; import ContinuumArtifactIngestionPolicy from '../../../../src/domain/continuum/ContinuumArtifactIngestionPolicy.ts'; import ContinuumFamilyId from '../../../../src/domain/continuum/ContinuumFamilyId.ts'; +import WarpError from '../../../../src/domain/errors/WarpError.ts'; /** Builds a receipt-family descriptor for policy tests. */ function makeDescriptor(authority: string): ContinuumArtifactDescriptor { return new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - version: '0.1.0', sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', generatedBy: 'wesley witness-continuum --scope receipt-family', artifactKind: 'continuum.family.fixture', authority, targets: ['typescript', 'echo'], + version: '0.1.0', witnessScope: 'receipt-family', }); } @@ -54,4 +56,104 @@ describe('ContinuumArtifactIngestionPolicy', () => { expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); expect(descriptor.familyId.toString()).toBe('receipt-family'); }); + + it('accepts every Continuum family id and compares ids by value', () => { + const receipt = new ContinuumFamilyId('receipt-family'); + + expect(receipt.equals(new ContinuumFamilyId('receipt-family'))).toBe(true); + expect(receipt.equals(new ContinuumFamilyId('settlement-family'))).toBe(false); + expect(new ContinuumFamilyId('neighborhood-core-family').toString()).toBe('neighborhood-core-family'); + expect(new ContinuumFamilyId('runtime-boundary-family').toString()).toBe('runtime-boundary-family'); + }); + + it('rejects unknown family ids and authorities', () => { + expect(() => new ContinuumFamilyId('not-a-family')).toThrow(WarpError); + expect(() => new ContinuumArtifactAuthority('not-authority')).toThrow(WarpError); + }); + + it('constructs descriptors from runtime-backed carriers', () => { + const descriptor = new ContinuumArtifactDescriptor({ + familyId: new ContinuumFamilyId('receipt-family'), + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: new ContinuumArtifactAuthority('generated-artifact'), + targets: ['warp-ttd'], + generatedLegs: ['warpTtd'], + generatedFiles: ['manifest/schema.json'], + }); + + expect(descriptor.familyId.toString()).toBe('receipt-family'); + expect(descriptor.authority.toString()).toBe('generated-artifact'); + expect(descriptor.generatedLegs).toEqual(['warpTtd']); + expect(descriptor.generatedFiles).toEqual(['manifest/schema.json']); + }); + + it('rejects invalid descriptor fields with WarpError', () => { + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: '', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + targets: ['warp-ttd'], + })).toThrow(WarpError); + + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + targets: [], + })).toThrow(WarpError); + + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + targets: ['warp-ttd'], + generatedLegs: [''], + })).toThrow(WarpError); + }); + + it('rejects wrong runtime types even when JavaScript bypasses TypeScript', () => { + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + // @ts-expect-error runtime guard for JS callers + sourceSchemaPath: 7, + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + targets: ['warp-ttd'], + })).toThrow(WarpError); + + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + // @ts-expect-error runtime guard for JS callers + targets: 'warp-ttd', + })).toThrow(WarpError); + + expect(() => new ContinuumArtifactDescriptor({ + familyId: 'receipt-family', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + generatedBy: 'wesley compile', + artifactKind: 'wesley.realization.manifest.v1', + authority: 'generated-artifact', + targets: ['warp-ttd'], + // @ts-expect-error runtime guard for JS callers + generatedFiles: 'manifest/schema.json', + })).toThrow(WarpError); + + // @ts-expect-error runtime guard for JS callers + expect(() => new ContinuumFamilyId(null)).toThrow(WarpError); + // @ts-expect-error runtime guard for JS callers + expect(() => new ContinuumArtifactAuthority(null)).toThrow(WarpError); + }); }); diff --git a/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts index e1a08079..136a1bd9 100644 --- a/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +++ b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts @@ -1,64 +1,349 @@ import { describe, expect, it } from 'vitest'; import { fileURLToPath } from 'node:url'; -import ContinuumArtifactJsonFileAdapter from '../../../../src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; +import ContinuumArtifactJsonFileAdapter, { + type ContinuumArtifactJsonLoadContext, +} from '../../../../src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; import ContinuumArtifactAuthorityError from '../../../../src/domain/errors/ContinuumArtifactAuthorityError.ts'; import AdapterValidationError from '../../../../src/domain/errors/AdapterValidationError.ts'; -const generatedFixtureJson = `{ - "familyId": "receipt-family", - "version": "0.1.0", - "sourceSchemaPath": "~/git/continuum/schemas/continuum-receipt-family.graphql", - "generatedBy": "wesley witness-continuum --scope receipt-family", - "artifactKind": "continuum.family.fixture", - "authority": "generated-fixture", - "targets": ["typescript", "echo"], - "witnessScope": "receipt-family", - "artifactDigest": "sha256:receipt-fixture" +const generatedFixturePath = fileURLToPath( + new URL('../../../fixtures/continuum/receipt-family-generated-artifact.json', import.meta.url), +); + +const wesleyManifestPath = fileURLToPath( + new URL('../../../fixtures/continuum/receipt-family-wesley-realization-manifest.json', import.meta.url), +); + +const fixtureContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-fixture', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + witnessScope: 'receipt-family', + artifactDigest: 'sha256:receipt-fixture', +}; + +const artifactContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-artifact', + witnessScope: 'receipt-family', +}; + +const localMirrorContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'local-mirror', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', +}; + +const selfAttestedFixtureJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": "receipts", + "resultType": "Receipt" + } + ], + "invariants": [], + "footprints": [], + "authority": "generated-artifact" }`; -const localMirrorJson = `{ - "familyId": "receipt-family", - "version": "0.1.0", - "sourceSchemaPath": "src/domain/continuum/local-receipt.ts", - "generatedBy": "git-warp", - "artifactKind": "continuum.family.fixture", - "authority": "local-mirror", - "targets": ["typescript"] +const unknownFixtureFieldJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": "receipts", + "resultType": "Receipt" + } + ], + "invariants": [], + "footprints": [], + "extra": "drift" }`; -const generatedFixturePath = fileURLToPath( - new URL('../../../fixtures/continuum/receipt-family-generated-artifact.json', import.meta.url), -); +const invalidOperationJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": 7, + "resultType": "Receipt" + } + ], + "invariants": [], + "footprints": [] +}`; + +const fixtureWithoutFootprintsJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": "receipts", + "resultType": "Receipt" + } + ], + "invariants": [] +}`; + +const emptyOperationsJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [], + "invariants": [], + "footprints": [] +}`; + +const invalidFootprintsJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": "receipts", + "resultType": "Receipt" + } + ], + "invariants": [], + "footprints": "none" +}`; + +const typeMapFixtureJson = `{ + "objectTypes": ["Receipt"], + "enumTypes": [], + "ops": [ + { + "name": "receipts", + "resultType": "Receipt" + } + ], + "invariants": [], + "footprints": [], + "types": { + "Receipt": ["receiptId"] + } +}`; + +const unsealedWesleyManifestJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "open", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": {} +}`; + +const invalidWesleyArtifactCountJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "dist/warp-ttd", + "schemaHash": "hash", + "sourceHash": "hash", + "artifactCount": "one" + } + } +}`; + +const wesleyManifestWithoutGeneratedFilesJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "dist/warp-ttd", + "schemaHash": "hash", + "sourceHash": "hash", + "artifactCount": 0 + } + } +}`; + +const invalidWesleyGeneratedFilesJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "dist/warp-ttd", + "schemaHash": "hash", + "sourceHash": "hash", + "files": "dist/warp-ttd/types.ts" + } + } +}`; + +const invalidWesleyTargetsJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": "warp-ttd", + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": {} +}`; + +const invalidWesleyTargetEntryJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": [""], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": {} +}`; describe('ContinuumArtifactJsonFileAdapter', () => { - it('loads generated fixture descriptors', () => { + it('loads real Continuum receipt-family fixture descriptors', async () => { const adapter = new ContinuumArtifactJsonFileAdapter(); - const descriptor = adapter.loadString(generatedFixtureJson); + const descriptor = await adapter.loadFile(generatedFixturePath, fixtureContext); expect(descriptor.familyId.toString()).toBe('receipt-family'); - expect(descriptor.hasTarget('typescript')).toBe(true); + expect(descriptor.hasTarget('continuum-fixture')).toBe(true); expect(descriptor.hasGeneratedAuthority()).toBe(true); expect(descriptor.artifactDigest).toBe('sha256:receipt-fixture'); + expect(descriptor.witnessScope).toBe('receipt-family'); }); - it('loads generated fixture descriptor files', async () => { + it('loads Wesley realization manifest descriptors without local descriptor fields', async () => { const adapter = new ContinuumArtifactJsonFileAdapter(); - const descriptor = await adapter.loadFile(generatedFixturePath); + const descriptor = await adapter.loadFile(wesleyManifestPath, artifactContext); - expect(descriptor.familyId.toString()).toBe('receipt-family'); - expect(descriptor.witnessScope).toBe('receipt-family'); + expect(descriptor.artifactKind).toBe('wesley.realization.manifest.v1'); + expect(descriptor.sourceSchemaPath).toBe('schemas/continuum-receipt-family.graphql'); + expect(descriptor.schemaHash).toBe('16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602'); + expect(descriptor.sourceHash).toBe('16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602'); + expect(descriptor.integrityStatus).toBe('sealed'); + expect(descriptor.signatureAlgorithm).toBe('hmac-sha256'); + expect(descriptor.targets).toEqual(['warp-ttd', 'echo']); + expect(descriptor.generatedLegs).toEqual(['echo', 'warpTtd']); + expect(descriptor.generatedFiles).toEqual([ + 'ir.json', + 'manifest/schema.json', + 'typescript/types.ts', + ]); + }); + + it('rejects local mirrors before they become authority', async () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + await expect(adapter.loadFile(generatedFixturePath, localMirrorContext)).rejects.toThrow(ContinuumArtifactAuthorityError); + }); + + it('rejects self-attested authority fields inside artifact JSON', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString(selfAttestedFixtureJson, fixtureContext)).toThrow(AdapterValidationError); + }); + + it('wraps invalid JSON syntax as adapter validation failure', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString('{ "objectTypes": [', fixtureContext)).toThrow(AdapterValidationError); + }); + + it('rejects unsupported top-level JSON shapes', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString('null', fixtureContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString('[]', fixtureContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString('{ "familyId": "receipt-family" }', fixtureContext)).toThrow(AdapterValidationError); + }); + + it('rejects unknown fixture keys and malformed nested entries', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString(unknownFixtureFieldJson, fixtureContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(invalidOperationJson, fixtureContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(emptyOperationsJson, fixtureContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(invalidFootprintsJson, fixtureContext)).toThrow(AdapterValidationError); + }); + + it('accepts fixture artifacts with omitted optional footprints', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(adapter.loadString(fixtureWithoutFootprintsJson, fixtureContext).artifactKind).toBe('continuum.family.fixture'); + }); + + it('requires source schema context for fixture artifacts', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + const contextWithoutSchemaPath: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-fixture', + }; + + expect(() => adapter.loadString(typeMapFixtureJson, contextWithoutSchemaPath)).toThrow(AdapterValidationError); + }); + + it('rejects unsealed Wesley realization manifests', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString(unsealedWesleyManifestJson, artifactContext)).toThrow(AdapterValidationError); }); - it('rejects local mirrors before they become authority', () => { + it('rejects malformed Wesley realization manifest numbers and targets', () => { const adapter = new ContinuumArtifactJsonFileAdapter(); - expect(() => adapter.loadString(localMirrorJson)).toThrow(ContinuumArtifactAuthorityError); + expect(() => adapter.loadString(invalidWesleyArtifactCountJson, artifactContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(invalidWesleyGeneratedFilesJson, artifactContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(invalidWesleyTargetsJson, artifactContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(invalidWesleyTargetEntryJson, artifactContext)).toThrow(AdapterValidationError); }); - it('rejects malformed descriptor JSON', () => { + it('accepts Wesley generated legs before the compiler writes file inventory', () => { const adapter = new ContinuumArtifactJsonFileAdapter(); + const descriptor = adapter.loadString(wesleyManifestWithoutGeneratedFilesJson, artifactContext); - expect(() => adapter.loadString('{ "familyId": "receipt-family" }')).toThrow(AdapterValidationError); + expect(descriptor.generatedLegs).toEqual(['warpTtd']); + expect(descriptor.generatedFiles).toEqual([]); }); }); From 9b5b6c3615082d2342922ecd7311127d72f9c8ab Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 14:07:18 -0700 Subject: [PATCH 09/16] fix: bind continuum artifact shape authority --- docs/BEARING.md | 6 +- .../v18-continuum-artifact-ingestion.md | 36 +++--- .../ContinuumArtifactJsonFileAdapter.ts | 122 +++++++++++++++--- ...pt-family-wesley-realization-manifest.json | 2 +- .../ContinuumArtifactJsonFileAdapter.test.ts | 110 +++++++++++++++- 5 files changed, 238 insertions(+), 38 deletions(-) diff --git a/docs/BEARING.md b/docs/BEARING.md index c6c28895..319c07ee 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -171,8 +171,10 @@ before the final commit for that slice, and mark completed items with `- [x]`. - [x] 5. Add a generated-artifact ingestion path for Continuum families, with a guard against handwritten local mirrors becoming contract authority. The current seam admits Continuum receipt-family fixture JSON and Wesley - realization manifest JSON through explicit load context; it rejects - self-attested authority fields from artifact JSON. + realization manifest JSON through explicit load context; it binds each + accepted JSON shape to the matching context authority, rejects self-attested + authority fields from artifact JSON, and rejects empty or internally + inconsistent Wesley generated inventory. - [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. - [ ] 7. Prove the patch commit visibility contract: success means canonical diff --git a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md index 5aa3460d..878ec369 100644 --- a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md +++ b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md @@ -56,9 +56,13 @@ JSON parsing stays in `src/infrastructure/adapters/`. Domain code receives validated constructor fields and runtime-backed objects. Authority is not read from untrusted artifact JSON. The adapter receives -authority through explicit load context, validates the artifact shape, and then -lets the domain policy decide whether that context can become descriptor -authority. +authority through explicit load context, validates the artifact shape, requires +the context authority that belongs to that shape, and then lets the domain +policy decide whether that context can become descriptor authority. + +Wesley realization manifests must contain at least one generated leg. When +Wesley records an `artifactCount`, it must match the generated file inventory +for that leg. The descriptor does not parse GraphQL, generate TypeScript, or claim family semantics. It only records which generated-family artifact or fixture is being @@ -91,29 +95,28 @@ Observed focused Continuum-suite test result: ```text Test Files 2 passed (2) -Tests 22 passed (22) +Tests 25 passed (25) ``` Observed focused export/error sweep: ```text Test Files 4 passed (4) -Tests 72 passed (72) +Tests 75 passed (75) ``` -Observed focused coverage result for the two new focused suites: +Coverage gate: ```text -npx vitest run --coverage \ - test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts \ - test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts -domain/continuum 100% lines -ContinuumArtifactJsonFileAdapter.ts 100% lines +npm run test:coverage:ci +Test Files 447 passed (447) +Tests 6824 passed (6824) +All files 92.12% lines ``` -That targeted coverage command exits nonzero because the repository global -threshold is applied to a two-suite subset; the useful signal is the per-file -coverage for the touched Continuum files. +Targeted coverage diagnostics are not recorded as green slice gates because the +repository global threshold applies to subset runs. The authoritative coverage +gate for this slice is the full-suite CI coverage command above. ## SSJS Scorecard @@ -126,8 +129,9 @@ coverage for the touched Continuum files. - Message parsing: green; no behavior branches parse free-form messages. - Ambient time or entropy: green; no ambient time or entropy introduced. - Fake shape trust or cast-cosplay: green; generated-family authority is carried - by load context, self-attested JSON authority is rejected, and the accepted - shapes are Continuum fixture JSON and Wesley realization manifest JSON. + by load context, self-attested JSON authority is rejected, each accepted JSON + shape is bound to its context authority, and Wesley generated inventory is + checked before descriptor construction. ## Closeout diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts index c0f297e5..4f490d90 100644 --- a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts +++ b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts @@ -1,6 +1,6 @@ import { readFile } from 'node:fs/promises'; -import type ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; +import ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; import ContinuumArtifactDescriptor, { type ContinuumArtifactDescriptorFields, } from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; @@ -9,9 +9,21 @@ import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts' import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; +const WESLEY_REALIZATION_MANIFEST_AUTHORITY = 'generated-artifact'; const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; +const CONTINUUM_FIXTURE_AUTHORITY = 'generated-fixture'; const CONTINUUM_FIXTURE_GENERATOR = 'continuum/wesley fixture'; const CONTINUUM_FIXTURE_TARGET = 'continuum-fixture'; +const LOAD_CONTEXT_KEYS = Object.freeze([ + 'familyId', + 'authority', + 'sourceSchemaPath', + 'generatedBy', + 'version', + 'targets', + 'witnessScope', + 'artifactDigest', +]); type JsonObject = Readonly>; @@ -20,7 +32,6 @@ export type ContinuumArtifactJsonLoadContext = { readonly authority: string | ContinuumArtifactAuthority; readonly sourceSchemaPath?: string; readonly generatedBy?: string; - readonly artifactKind?: string; readonly version?: string; readonly targets?: readonly string[]; readonly witnessScope?: string; @@ -62,6 +73,7 @@ export default class ContinuumArtifactJsonFileAdapter { /** Ingests a generated artifact descriptor from JSON text. */ loadString(raw: string, context: ContinuumArtifactJsonLoadContext): ContinuumArtifactDescriptor { + validateLoadContext(context); const parsed = parseJson(raw); const fields = parseDescriptorFields(parsed, context); return this.policy.ingest(new ContinuumArtifactDescriptor(fields)); @@ -99,6 +111,7 @@ function parseWesleyRealizationManifest( source: JsonObject, context: ContinuumArtifactJsonLoadContext, ): ContinuumArtifactDescriptorFields { + requireContextAuthority(context, WESLEY_REALIZATION_MANIFEST_AUTHORITY, 'Wesley realization manifest'); validateWesleyManifestEnvelope(source); const integrity = readSealedIntegrity(source); const legs = readGeneratedLegs(source); @@ -131,7 +144,7 @@ function wesleyManifestFields( return { sourceSchemaPath: readRequiredString(source, 'schemaPath'), generatedBy: context.generatedBy ?? 'wesley compile', - artifactKind: context.artifactKind ?? WESLEY_REALIZATION_MANIFEST_KIND, + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, targets: readStringArray(source, 'targets'), schemaHash: readRequiredString(source, 'schemaHash'), sourceHash: readRequiredString(source, 'sourceHash'), @@ -143,6 +156,7 @@ function parseContinuumFamilyFixture( source: JsonObject, context: ContinuumArtifactJsonLoadContext, ): ContinuumArtifactDescriptorFields { + requireContextAuthority(context, CONTINUUM_FIXTURE_AUTHORITY, 'Continuum family fixture'); rejectUnknownKeys( source, ['objectTypes', 'enumTypes', 'ops', 'invariants', 'footprints', 'types'], @@ -158,7 +172,7 @@ function parseContinuumFamilyFixture( return descriptorFields(context, { sourceSchemaPath: readContextString(context.sourceSchemaPath, 'sourceSchemaPath'), generatedBy: context.generatedBy ?? CONTINUUM_FIXTURE_GENERATOR, - artifactKind: context.artifactKind ?? CONTINUUM_FIXTURE_KIND, + artifactKind: CONTINUUM_FIXTURE_KIND, targets: context.targets ?? [CONTINUUM_FIXTURE_TARGET], }); } @@ -260,27 +274,57 @@ function readGeneratedLegs(source: JsonObject): { readonly files: readonly string[]; } { const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); - const names = Object.freeze(Object.keys(generatedLegs).sort()); + const names = readGeneratedLegNames(generatedLegs); const files: string[] = []; for (const name of names) { - const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); - rejectUnknownKeys( - leg, - ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], - `Wesley generated leg "${name}"`, - ); - readRequiredString(leg, 'outDir'); - readRequiredString(leg, 'schemaHash'); - readRequiredString(leg, 'sourceHash'); - readOptionalStringArray(leg, 'targets'); - readOptionalNumber(leg, 'artifactCount'); - for (const path of readGeneratedFiles(leg, name)) { + for (const path of readGeneratedLegFiles(generatedLegs, name)) { files.push(path); } } return { names, files: Object.freeze(files.sort()) }; } +/** Reads the sorted generated leg names from a Wesley manifest. */ +function readGeneratedLegNames(generatedLegs: JsonObject): readonly string[] { + const names = Object.freeze(Object.keys(generatedLegs).sort()); + if (names.length === 0) { + throw new AdapterValidationError('Wesley realization manifest generatedLegs must contain at least one leg'); + } + return names; +} + +/** Reads and validates one Wesley generated leg inventory. */ +function readGeneratedLegFiles(generatedLegs: JsonObject, name: string): readonly string[] { + const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); + validateGeneratedLegEnvelope(leg, name); + const artifactCount = readOptionalArtifactCount(leg, 'artifactCount'); + const legFiles = readGeneratedFiles(leg, name); + requireArtifactCountMatchesFiles(artifactCount, legFiles.length, name); + return legFiles; +} + +/** Validates one Wesley generated leg envelope. */ +function validateGeneratedLegEnvelope(leg: JsonObject, name: string): void { + rejectUnknownKeys( + leg, + ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], + `Wesley generated leg "${name}"`, + ); + readRequiredString(leg, 'outDir'); + readRequiredString(leg, 'schemaHash'); + readRequiredString(leg, 'sourceHash'); + readOptionalStringArray(leg, 'targets'); +} + +/** Requires Wesley's artifact count to match the generated file inventory. */ +function requireArtifactCountMatchesFiles(count: number | undefined, fileCount: number, legName: string): void { + if (count !== undefined && count !== fileCount) { + throw new AdapterValidationError( + `Wesley generated leg "${legName}" field "artifactCount" must match generated file count`, + ); + } +} + /** Reads generated file entries from one Wesley generated leg. */ function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { const value = source['files']; @@ -413,6 +457,50 @@ function readOptionalNumber(source: JsonObject, key: string): number | undefined return readRequiredNumber(source, key); } +/** Reads an optional generated artifact count. */ +function readOptionalArtifactCount(source: JsonObject, key: string): number | undefined { + const count = readOptionalNumber(source, key); + if (count === undefined) { + return undefined; + } + if (!Number.isInteger(count) || count < 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-negative integer`); + } + return count; +} + +/** Validates the caller-supplied load context boundary. */ +function validateLoadContext(context: ContinuumArtifactJsonLoadContext): void { + rejectUnknownKeys( + requireJsonObject(context, 'Continuum artifact load context'), + LOAD_CONTEXT_KEYS, + 'Continuum artifact load context', + ); +} + +/** Requires the context authority that matches the parsed artifact shape. */ +function requireContextAuthority( + context: ContinuumArtifactJsonLoadContext, + expected: string, + label: string, +): void { + const actual = readContextAuthority(context.authority); + if (actual !== expected) { + throw new AdapterValidationError(`${label} load context authority must be ${expected}`); + } +} + +/** Reads a context authority carrier as a string. */ +function readContextAuthority(value: string | ContinuumArtifactAuthority): string { + if (typeof value === 'string') { + return value; + } + if (value instanceof ContinuumArtifactAuthority) { + return value.toString(); + } + throw new AdapterValidationError('Continuum artifact load context field "authority" must be an authority carrier'); +} + /** Reads an optional string array field. */ function readOptionalStringArray(source: JsonObject, key: string): readonly string[] | undefined { const value = source[key]; diff --git a/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json b/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json index 555d3b6a..399ff90f 100644 --- a/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json +++ b/test/fixtures/continuum/receipt-family-wesley-realization-manifest.json @@ -1,7 +1,7 @@ { "kind": "wesley.realization.manifest.v1", "schemaPath": "schemas/continuum-receipt-family.graphql", - "canonicalSchemaPath": "/Users/james/git/continuum/schemas/continuum-receipt-family.graphql", + "canonicalSchemaPath": "continuum/schemas/continuum-receipt-family.graphql", "schemaHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", "sourceHash": "16bf631145b60e0ec240f97484ff2cb5f534cd38c963cf12044985915766a602", "outDir": "out/proof", diff --git a/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts index 136a1bd9..ffd4ef77 100644 --- a/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts +++ b/test/unit/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.test.ts @@ -4,7 +4,6 @@ import { fileURLToPath } from 'node:url'; import ContinuumArtifactJsonFileAdapter, { type ContinuumArtifactJsonLoadContext, } from '../../../../src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts'; -import ContinuumArtifactAuthorityError from '../../../../src/domain/errors/ContinuumArtifactAuthorityError.ts'; import AdapterValidationError from '../../../../src/domain/errors/AdapterValidationError.ts'; const generatedFixturePath = fileURLToPath( @@ -35,6 +34,18 @@ const localMirrorContext: ContinuumArtifactJsonLoadContext = { sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', }; +const fixtureAsArtifactContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-artifact', + sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', +}; + +const artifactAsFixtureContext: ContinuumArtifactJsonLoadContext = { + familyId: 'receipt-family', + authority: 'generated-fixture', + witnessScope: 'receipt-family', +}; + const selfAttestedFixtureJson = `{ "objectTypes": ["Receipt"], "enumTypes": [], @@ -242,6 +253,76 @@ const invalidWesleyTargetEntryJson = `{ "generatedLegs": {} }`; +const emptyWesleyGeneratedLegsJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": {} +}`; + +const mismatchedWesleyArtifactCountJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "dist/warp-ttd", + "schemaHash": "hash", + "sourceHash": "hash", + "artifactCount": 2, + "files": [ + { + "path": "manifest/schema.json", + "size": 1, + "contentHash": "hash", + "signature": "signature" + } + ] + } + } +}`; + +const positiveWesleyArtifactCountWithoutFilesJson = `{ + "kind": "wesley.realization.manifest.v1", + "schemaPath": "schemas/continuum-receipt-family.graphql", + "schemaHash": "hash", + "sourceHash": "hash", + "targets": ["warp-ttd"], + "integrity": { + "status": "sealed", + "scope": "generated-leg-files", + "hashAlgorithm": "sha256", + "signatureAlgorithm": "hmac-sha256", + "signatureKeyId": "fixture-key" + }, + "generatedLegs": { + "warpTtd": { + "outDir": "dist/warp-ttd", + "schemaHash": "hash", + "sourceHash": "hash", + "artifactCount": 1 + } + } +}`; + describe('ContinuumArtifactJsonFileAdapter', () => { it('loads real Continuum receipt-family fixture descriptors', async () => { const adapter = new ContinuumArtifactJsonFileAdapter(); @@ -276,7 +357,24 @@ describe('ContinuumArtifactJsonFileAdapter', () => { it('rejects local mirrors before they become authority', async () => { const adapter = new ContinuumArtifactJsonFileAdapter(); - await expect(adapter.loadFile(generatedFixturePath, localMirrorContext)).rejects.toThrow(ContinuumArtifactAuthorityError); + await expect(adapter.loadFile(generatedFixturePath, localMirrorContext)).rejects.toThrow(AdapterValidationError); + }); + + it('requires authority to match the parsed artifact shape', async () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + await expect(adapter.loadFile(wesleyManifestPath, artifactAsFixtureContext)).rejects.toThrow(AdapterValidationError); + expect(() => adapter.loadString(typeMapFixtureJson, fixtureAsArtifactContext)).toThrow(AdapterValidationError); + }); + + it('rejects stale load context artifact kind overrides', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + const contextWithArtifactKind = { + ...artifactContext, + artifactKind: 'continuum.family.fixture', + }; + + expect(() => adapter.loadString(wesleyManifestWithoutGeneratedFilesJson, contextWithArtifactKind)).toThrow(AdapterValidationError); }); it('rejects self-attested authority fields inside artifact JSON', () => { @@ -339,6 +437,14 @@ describe('ContinuumArtifactJsonFileAdapter', () => { expect(() => adapter.loadString(invalidWesleyTargetEntryJson, artifactContext)).toThrow(AdapterValidationError); }); + it('rejects empty or inconsistent Wesley generated inventory', () => { + const adapter = new ContinuumArtifactJsonFileAdapter(); + + expect(() => adapter.loadString(emptyWesleyGeneratedLegsJson, artifactContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(mismatchedWesleyArtifactCountJson, artifactContext)).toThrow(AdapterValidationError); + expect(() => adapter.loadString(positiveWesleyArtifactCountWithoutFilesJson, artifactContext)).toThrow(AdapterValidationError); + }); + it('accepts Wesley generated legs before the compiler writes file inventory', () => { const adapter = new ContinuumArtifactJsonFileAdapter(); const descriptor = adapter.loadString(wesleyManifestWithoutGeneratedFilesJson, artifactContext); From fbd240f5ae486f85de8eb36b8bf4b18dc3bd8036 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 15:54:58 -0700 Subject: [PATCH 10/16] fix: enforce continuum artifact authority pairing --- .../ContinuumArtifactIngestionPolicy.ts | 24 +++++- .../ContinuumArtifactIngestionPolicy.test.ts | 78 ++++++++++++++----- 2 files changed, 79 insertions(+), 23 deletions(-) diff --git a/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts b/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts index bd0fad1b..d35ad1e0 100644 --- a/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts +++ b/src/domain/continuum/ContinuumArtifactIngestionPolicy.ts @@ -1,6 +1,11 @@ import ContinuumArtifactAuthorityError from '../errors/ContinuumArtifactAuthorityError.ts'; import type ContinuumArtifactDescriptor from './ContinuumArtifactDescriptor.ts'; +const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; +const WESLEY_REALIZATION_MANIFEST_AUTHORITY = 'generated-artifact'; +const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; +const CONTINUUM_FIXTURE_AUTHORITY = 'generated-fixture'; + /** Policy gate for admitting generated Continuum family artifacts. */ export default class ContinuumArtifactIngestionPolicy { /** Accepts generated artifacts and documented fixtures, rejecting mirrors. */ @@ -11,11 +16,26 @@ export default class ContinuumArtifactIngestionPolicy { /** Rejects descriptors whose authority would make local mirrors canonical. */ assertGeneratedAuthority(descriptor: ContinuumArtifactDescriptor): void { - if (descriptor.hasGeneratedAuthority()) { + const expectedAuthority = expectedGeneratedAuthority(descriptor); + const actualAuthority = descriptor.authority.toString(); + if (descriptor.hasGeneratedAuthority() && actualAuthority === expectedAuthority) { return; } throw new ContinuumArtifactAuthorityError( - `Continuum family ${descriptor.familyId.toString()} must be loaded from a generated artifact or fixture, not ${descriptor.authority.toString()}`, + `Continuum family ${descriptor.familyId.toString()} artifact kind ${descriptor.artifactKind} must use authority ${expectedAuthority}, not ${actualAuthority}`, ); } } + +/** Returns the generated authority required for the descriptor's artifact kind. */ +function expectedGeneratedAuthority(descriptor: ContinuumArtifactDescriptor): string { + if (descriptor.artifactKind === WESLEY_REALIZATION_MANIFEST_KIND) { + return WESLEY_REALIZATION_MANIFEST_AUTHORITY; + } + if (descriptor.artifactKind === CONTINUUM_FIXTURE_KIND) { + return CONTINUUM_FIXTURE_AUTHORITY; + } + throw new ContinuumArtifactAuthorityError( + `Continuum family ${descriptor.familyId.toString()} artifact kind ${descriptor.artifactKind} is not a recognized generated Continuum artifact shape`, + ); +} diff --git a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts index c5a1ea55..bcd5aef8 100644 --- a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts +++ b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts @@ -7,14 +7,23 @@ import ContinuumArtifactIngestionPolicy from '../../../../src/domain/continuum/C import ContinuumFamilyId from '../../../../src/domain/continuum/ContinuumFamilyId.ts'; import WarpError from '../../../../src/domain/errors/WarpError.ts'; +const RECEIPT_SCHEMA_PATH = 'schemas/continuum-receipt-family.graphql'; +const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; +const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; + +type DescriptorFixtureFields = { + readonly artifactKind?: string; + readonly authority?: string; +}; + /** Builds a receipt-family descriptor for policy tests. */ -function makeDescriptor(authority: string): ContinuumArtifactDescriptor { +function makeDescriptor(fields: DescriptorFixtureFields = {}): ContinuumArtifactDescriptor { return new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley witness-continuum --scope receipt-family', - artifactKind: 'continuum.family.fixture', - authority, + artifactKind: fields.artifactKind ?? CONTINUUM_FIXTURE_KIND, + authority: fields.authority ?? 'generated-fixture', targets: ['typescript', 'echo'], version: '0.1.0', witnessScope: 'receipt-family', @@ -23,35 +32,62 @@ function makeDescriptor(authority: string): ContinuumArtifactDescriptor { describe('ContinuumArtifactIngestionPolicy', () => { it('accepts documented generated fixtures', () => { - const descriptor = makeDescriptor('generated-fixture'); + const descriptor = makeDescriptor({ authority: 'generated-fixture' }); const policy = new ContinuumArtifactIngestionPolicy(); expect(policy.ingest(descriptor)).toBe(descriptor); }); it('accepts generated artifacts', () => { - const descriptor = makeDescriptor('generated-artifact'); + const descriptor = makeDescriptor({ + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, + authority: 'generated-artifact', + }); const policy = new ContinuumArtifactIngestionPolicy(); expect(policy.ingest(descriptor)).toBe(descriptor); }); + it('rejects generated authority that does not match the descriptor kind', () => { + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(() => policy.ingest(makeDescriptor({ + artifactKind: CONTINUUM_FIXTURE_KIND, + authority: 'generated-artifact', + }))).toThrow(ContinuumArtifactAuthorityError); + + expect(() => policy.ingest(makeDescriptor({ + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, + authority: 'generated-fixture', + }))).toThrow(ContinuumArtifactAuthorityError); + }); + + it('rejects generated authority for unknown artifact kinds', () => { + const descriptor = makeDescriptor({ + artifactKind: 'continuum.unknown.fixture', + authority: 'generated-fixture', + }); + const policy = new ContinuumArtifactIngestionPolicy(); + + expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); + }); + it('rejects local mirrors as family authority', () => { - const descriptor = makeDescriptor('local-mirror'); + const descriptor = makeDescriptor({ authority: 'local-mirror' }); const policy = new ContinuumArtifactIngestionPolicy(); expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); }); it('rejects handwritten mirrors as family authority', () => { - const descriptor = makeDescriptor('handwritten-mirror'); + const descriptor = makeDescriptor({ authority: 'handwritten-mirror' }); const policy = new ContinuumArtifactIngestionPolicy(); expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); }); it('keeps family ids runtime-backed', () => { - const descriptor = makeDescriptor('generated-fixture'); + const descriptor = makeDescriptor({ authority: 'generated-fixture' }); expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); expect(descriptor.familyId.toString()).toBe('receipt-family'); @@ -74,9 +110,9 @@ describe('ContinuumArtifactIngestionPolicy', () => { it('constructs descriptors from runtime-backed carriers', () => { const descriptor = new ContinuumArtifactDescriptor({ familyId: new ContinuumFamilyId('receipt-family'), - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: new ContinuumArtifactAuthority('generated-artifact'), targets: ['warp-ttd'], generatedLegs: ['warpTtd'], @@ -94,25 +130,25 @@ describe('ContinuumArtifactIngestionPolicy', () => { familyId: 'receipt-family', sourceSchemaPath: '', generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', targets: ['warp-ttd'], })).toThrow(WarpError); expect(() => new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', targets: [], })).toThrow(WarpError); expect(() => new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', targets: ['warp-ttd'], generatedLegs: [''], @@ -125,16 +161,16 @@ describe('ContinuumArtifactIngestionPolicy', () => { // @ts-expect-error runtime guard for JS callers sourceSchemaPath: 7, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', targets: ['warp-ttd'], })).toThrow(WarpError); expect(() => new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', // @ts-expect-error runtime guard for JS callers targets: 'warp-ttd', @@ -142,9 +178,9 @@ describe('ContinuumArtifactIngestionPolicy', () => { expect(() => new ContinuumArtifactDescriptor({ familyId: 'receipt-family', - sourceSchemaPath: '~/git/continuum/schemas/continuum-receipt-family.graphql', + sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', - artifactKind: 'wesley.realization.manifest.v1', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, authority: 'generated-artifact', targets: ['warp-ttd'], // @ts-expect-error runtime guard for JS callers From 6e529d8b0214e4de2fa63ab0fe8f7ec16232e18a Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 15:58:58 -0700 Subject: [PATCH 11/16] refactor: split continuum artifact json adapter --- .../ContinuumArtifactJsonFileAdapter.ts | 519 +----------------- .../adapters/continuumArtifactJsonParser.ts | 242 ++++++++ .../adapters/continuumArtifactJsonTypes.ts | 44 ++ .../continuumArtifactJsonValidation.ts | 156 ++++++ .../continuumFamilyFixtureValidation.ts | 28 + .../continuumWesleyManifestInventory.ts | 106 ++++ 6 files changed, 582 insertions(+), 513 deletions(-) create mode 100644 src/infrastructure/adapters/continuumArtifactJsonParser.ts create mode 100644 src/infrastructure/adapters/continuumArtifactJsonTypes.ts create mode 100644 src/infrastructure/adapters/continuumArtifactJsonValidation.ts create mode 100644 src/infrastructure/adapters/continuumFamilyFixtureValidation.ts create mode 100644 src/infrastructure/adapters/continuumWesleyManifestInventory.ts diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts index 4f490d90..d1b9da34 100644 --- a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts +++ b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts @@ -1,58 +1,12 @@ import { readFile } from 'node:fs/promises'; -import ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; -import ContinuumArtifactDescriptor, { - type ContinuumArtifactDescriptorFields, -} from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; +import ContinuumArtifactDescriptor from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; import ContinuumArtifactIngestionPolicy from '../../domain/continuum/ContinuumArtifactIngestionPolicy.ts'; -import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts'; -import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import { parseContinuumArtifactDescriptorFields } from './continuumArtifactJsonParser.ts'; +import type { ContinuumArtifactJsonLoadContext } from './continuumArtifactJsonTypes.ts'; +import { validateLoadContext } from './continuumArtifactJsonValidation.ts'; -const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; -const WESLEY_REALIZATION_MANIFEST_AUTHORITY = 'generated-artifact'; -const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; -const CONTINUUM_FIXTURE_AUTHORITY = 'generated-fixture'; -const CONTINUUM_FIXTURE_GENERATOR = 'continuum/wesley fixture'; -const CONTINUUM_FIXTURE_TARGET = 'continuum-fixture'; -const LOAD_CONTEXT_KEYS = Object.freeze([ - 'familyId', - 'authority', - 'sourceSchemaPath', - 'generatedBy', - 'version', - 'targets', - 'witnessScope', - 'artifactDigest', -]); - -type JsonObject = Readonly>; - -export type ContinuumArtifactJsonLoadContext = { - readonly familyId: string | ContinuumFamilyId; - readonly authority: string | ContinuumArtifactAuthority; - readonly sourceSchemaPath?: string; - readonly generatedBy?: string; - readonly version?: string; - readonly targets?: readonly string[]; - readonly witnessScope?: string; - readonly artifactDigest?: string; -}; - -type DescriptorFieldSource = { - readonly sourceSchemaPath: string; - readonly generatedBy: string; - readonly artifactKind: string; - readonly targets: readonly string[]; - readonly schemaHash?: string; - readonly sourceHash?: string; - readonly integrityStatus?: string; - readonly integrityScope?: string; - readonly hashAlgorithm?: string; - readonly signatureAlgorithm?: string; - readonly signatureKeyId?: string; - readonly generatedLegs?: readonly string[]; - readonly generatedFiles?: readonly string[]; -}; +export type { ContinuumArtifactJsonLoadContext } from './continuumArtifactJsonTypes.ts'; /** Loads Continuum artifact descriptors from JSON files at the adapter edge. */ export default class ContinuumArtifactJsonFileAdapter { @@ -74,468 +28,7 @@ export default class ContinuumArtifactJsonFileAdapter { /** Ingests a generated artifact descriptor from JSON text. */ loadString(raw: string, context: ContinuumArtifactJsonLoadContext): ContinuumArtifactDescriptor { validateLoadContext(context); - const parsed = parseJson(raw); - const fields = parseDescriptorFields(parsed, context); + const fields = parseContinuumArtifactDescriptorFields(raw, context); return this.policy.ingest(new ContinuumArtifactDescriptor(fields)); } } - -/** Parses untrusted descriptor JSON without leaking platform SyntaxError. */ -function parseJson(raw: string): unknown { - try { - return JSON.parse(raw); - } catch { - throw new AdapterValidationError('Continuum artifact descriptor JSON must be valid JSON'); - } -} - -/** Converts untrusted JSON into descriptor fields. */ -function parseDescriptorFields( - value: unknown, - context: ContinuumArtifactJsonLoadContext, -): ContinuumArtifactDescriptorFields { - const source = requireJsonObject(value, 'Continuum artifact descriptor JSON'); - if (source['kind'] === WESLEY_REALIZATION_MANIFEST_KIND) { - return parseWesleyRealizationManifest(source, context); - } - if (isContinuumFamilyFixture(source)) { - return parseContinuumFamilyFixture(source, context); - } - throw new AdapterValidationError( - 'Continuum artifact descriptor JSON must be a Wesley realization manifest or Continuum family fixture', - ); -} - -/** Converts a Wesley realization manifest into descriptor fields. */ -function parseWesleyRealizationManifest( - source: JsonObject, - context: ContinuumArtifactJsonLoadContext, -): ContinuumArtifactDescriptorFields { - requireContextAuthority(context, WESLEY_REALIZATION_MANIFEST_AUTHORITY, 'Wesley realization manifest'); - validateWesleyManifestEnvelope(source); - const integrity = readSealedIntegrity(source); - const legs = readGeneratedLegs(source); - return descriptorFields(context, { - ...wesleyManifestFields(source, context), - ...integrity, - generatedLegs: legs.names, - generatedFiles: legs.files, - }); -} - -/** Validates the non-semantic envelope fields on a Wesley manifest. */ -function validateWesleyManifestEnvelope(source: JsonObject): void { - rejectUnknownKeys( - source, - ['kind', 'schemaPath', 'canonicalSchemaPath', 'schemaHash', 'sourceHash', 'outDir', 'targets', 'integrity', 'generatedLegs', 'proves', 'doesNotProve'], - 'Wesley realization manifest', - ); - readOptionalString(source, 'canonicalSchemaPath'); - readOptionalString(source, 'outDir'); - readOptionalStringArray(source, 'proves'); - readOptionalStringArray(source, 'doesNotProve'); -} - -/** Reads the descriptor-facing fields from a Wesley realization manifest. */ -function wesleyManifestFields( - source: JsonObject, - context: ContinuumArtifactJsonLoadContext, -): DescriptorFieldSource { - return { - sourceSchemaPath: readRequiredString(source, 'schemaPath'), - generatedBy: context.generatedBy ?? 'wesley compile', - artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - targets: readStringArray(source, 'targets'), - schemaHash: readRequiredString(source, 'schemaHash'), - sourceHash: readRequiredString(source, 'sourceHash'), - }; -} - -/** Converts a Continuum family fixture into descriptor fields. */ -function parseContinuumFamilyFixture( - source: JsonObject, - context: ContinuumArtifactJsonLoadContext, -): ContinuumArtifactDescriptorFields { - requireContextAuthority(context, CONTINUUM_FIXTURE_AUTHORITY, 'Continuum family fixture'); - rejectUnknownKeys( - source, - ['objectTypes', 'enumTypes', 'ops', 'invariants', 'footprints', 'types'], - 'Continuum family fixture', - ); - readOptionalStringArray(source, 'objectTypes'); - readOptionalStringArray(source, 'enumTypes'); - readOperations(source); - readOptionalStringArray(source, 'invariants'); - readOptionalFootprints(source); - readOptionalTypeMap(source); - - return descriptorFields(context, { - sourceSchemaPath: readContextString(context.sourceSchemaPath, 'sourceSchemaPath'), - generatedBy: context.generatedBy ?? CONTINUUM_FIXTURE_GENERATOR, - artifactKind: CONTINUUM_FIXTURE_KIND, - targets: context.targets ?? [CONTINUUM_FIXTURE_TARGET], - }); -} - -/** Builds descriptor fields without trusting authority from the untrusted JSON. */ -function descriptorFields( - context: ContinuumArtifactJsonLoadContext, - required: DescriptorFieldSource, -): ContinuumArtifactDescriptorFields { - return { - familyId: context.familyId, - sourceSchemaPath: required.sourceSchemaPath, - generatedBy: required.generatedBy, - artifactKind: required.artifactKind, - authority: context.authority, - targets: required.targets, - ...contextDescriptorFields(context), - ...sourceHashFields(required), - ...integrityDescriptorFields(required), - ...signatureDescriptorFields(required), - ...generatedInventoryFields(required), - }; -} - -/** Selects optional descriptor fields provided by load context. */ -function contextDescriptorFields( - context: ContinuumArtifactJsonLoadContext, -): Partial { - return { - ...(context.version !== undefined ? { version: context.version } : {}), - ...(context.witnessScope !== undefined ? { witnessScope: context.witnessScope } : {}), - ...(context.artifactDigest !== undefined ? { artifactDigest: context.artifactDigest } : {}), - }; -} - -/** Selects optional schema hash fields from source evidence. */ -function sourceHashFields(required: DescriptorFieldSource): Partial { - return { - ...(required.schemaHash !== undefined ? { schemaHash: required.schemaHash } : {}), - ...(required.sourceHash !== undefined ? { sourceHash: required.sourceHash } : {}), - }; -} - -/** Selects optional integrity fields from source evidence. */ -function integrityDescriptorFields(required: DescriptorFieldSource): Partial { - return { - ...(required.integrityStatus !== undefined ? { integrityStatus: required.integrityStatus } : {}), - ...(required.integrityScope !== undefined ? { integrityScope: required.integrityScope } : {}), - ...(required.hashAlgorithm !== undefined ? { hashAlgorithm: required.hashAlgorithm } : {}), - }; -} - -/** Selects optional signature fields from source evidence. */ -function signatureDescriptorFields(required: DescriptorFieldSource): Partial { - return { - ...(required.signatureAlgorithm !== undefined ? { signatureAlgorithm: required.signatureAlgorithm } : {}), - ...(required.signatureKeyId !== undefined ? { signatureKeyId: required.signatureKeyId } : {}), - }; -} - -/** Selects optional generated inventory fields from source evidence. */ -function generatedInventoryFields(required: DescriptorFieldSource): Partial { - return { - ...(required.generatedLegs !== undefined ? { generatedLegs: required.generatedLegs } : {}), - ...(required.generatedFiles !== undefined ? { generatedFiles: required.generatedFiles } : {}), - }; -} - -/** Reads and validates a Wesley integrity block. */ -function readSealedIntegrity(source: JsonObject): { - readonly integrityStatus: string; - readonly integrityScope: string; - readonly hashAlgorithm: string; - readonly signatureAlgorithm: string; - readonly signatureKeyId: string; -} { - const integrity = requireJsonObject(source['integrity'], 'Wesley realization manifest integrity'); - rejectUnknownKeys( - integrity, - ['status', 'scope', 'hashAlgorithm', 'signatureAlgorithm', 'signatureKeyId'], - 'Wesley realization manifest integrity', - ); - const status = readRequiredString(integrity, 'status'); - if (status !== 'sealed') { - throw new AdapterValidationError('Wesley realization manifest integrity status must be sealed'); - } - return { - integrityStatus: status, - integrityScope: readRequiredString(integrity, 'scope'), - hashAlgorithm: readRequiredString(integrity, 'hashAlgorithm'), - signatureAlgorithm: readRequiredString(integrity, 'signatureAlgorithm'), - signatureKeyId: readRequiredString(integrity, 'signatureKeyId'), - }; -} - -/** Reads and validates Wesley generated leg inventory. */ -function readGeneratedLegs(source: JsonObject): { - readonly names: readonly string[]; - readonly files: readonly string[]; -} { - const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); - const names = readGeneratedLegNames(generatedLegs); - const files: string[] = []; - for (const name of names) { - for (const path of readGeneratedLegFiles(generatedLegs, name)) { - files.push(path); - } - } - return { names, files: Object.freeze(files.sort()) }; -} - -/** Reads the sorted generated leg names from a Wesley manifest. */ -function readGeneratedLegNames(generatedLegs: JsonObject): readonly string[] { - const names = Object.freeze(Object.keys(generatedLegs).sort()); - if (names.length === 0) { - throw new AdapterValidationError('Wesley realization manifest generatedLegs must contain at least one leg'); - } - return names; -} - -/** Reads and validates one Wesley generated leg inventory. */ -function readGeneratedLegFiles(generatedLegs: JsonObject, name: string): readonly string[] { - const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); - validateGeneratedLegEnvelope(leg, name); - const artifactCount = readOptionalArtifactCount(leg, 'artifactCount'); - const legFiles = readGeneratedFiles(leg, name); - requireArtifactCountMatchesFiles(artifactCount, legFiles.length, name); - return legFiles; -} - -/** Validates one Wesley generated leg envelope. */ -function validateGeneratedLegEnvelope(leg: JsonObject, name: string): void { - rejectUnknownKeys( - leg, - ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], - `Wesley generated leg "${name}"`, - ); - readRequiredString(leg, 'outDir'); - readRequiredString(leg, 'schemaHash'); - readRequiredString(leg, 'sourceHash'); - readOptionalStringArray(leg, 'targets'); -} - -/** Requires Wesley's artifact count to match the generated file inventory. */ -function requireArtifactCountMatchesFiles(count: number | undefined, fileCount: number, legName: string): void { - if (count !== undefined && count !== fileCount) { - throw new AdapterValidationError( - `Wesley generated leg "${legName}" field "artifactCount" must match generated file count`, - ); - } -} - -/** Reads generated file entries from one Wesley generated leg. */ -function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { - const value = source['files']; - if (value === undefined) { - return []; - } - if (!Array.isArray(value)) { - throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); - } - const files: string[] = []; - for (const entry of value) { - const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); - rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); - files.push(readRequiredString(file, 'path')); - readRequiredNumber(file, 'size'); - readRequiredString(file, 'contentHash'); - readRequiredString(file, 'signature'); - } - return Object.freeze(files); -} - -/** Returns true when the top-level object has the Continuum fixture shape. */ -function isContinuumFamilyFixture(source: JsonObject): boolean { - return Array.isArray(source['ops']) && ( - Array.isArray(source['objectTypes']) || - isJsonObject(source['types']) - ); -} - -/** Reads and validates Continuum fixture operations. */ -function readOperations(source: JsonObject): void { - const { ops } = source; - if (!Array.isArray(ops) || ops.length === 0) { - throw new AdapterValidationError('Continuum family fixture field "ops" must be a non-empty operation array'); - } - for (const entry of ops) { - const op = requireJsonObject(entry, 'Continuum family fixture operation'); - rejectUnknownKeys(op, ['name', 'resultType'], 'Continuum family fixture operation'); - readRequiredString(op, 'name'); - readRequiredString(op, 'resultType'); - } -} - -/** Reads and validates optional Continuum fixture footprints. */ -function readOptionalFootprints(source: JsonObject): void { - const { footprints } = source; - if (footprints === undefined) { - return; - } - if (!Array.isArray(footprints)) { - throw new AdapterValidationError('Continuum family fixture field "footprints" must be an array'); - } - for (const entry of footprints) { - const footprint = requireJsonObject(entry, 'Continuum family fixture footprint'); - rejectUnknownKeys(footprint, ['opName', 'reads', 'writes', 'creates', 'deletes'], 'Continuum family fixture footprint'); - readRequiredString(footprint, 'opName'); - readStringArray(footprint, 'reads'); - readStringArray(footprint, 'writes'); - readStringArray(footprint, 'creates'); - readStringArray(footprint, 'deletes'); - } -} - -/** Reads and validates an optional Continuum boundary type map. */ -function readOptionalTypeMap(source: JsonObject): void { - const { types } = source; - if (types === undefined) { - return; - } - const typeMap = requireJsonObject(types, 'Continuum family fixture types'); - for (const name of Object.keys(typeMap)) { - readStringArray(typeMap, name); - } -} - -/** Requires a non-array JSON object. */ -function requireJsonObject(value: unknown, label: string): JsonObject { - if (!isJsonObject(value)) { - throw new AdapterValidationError(`${label} must be an object`); - } - return value; -} - -/** Returns true when a value is a non-array JSON object. */ -function isJsonObject(value: unknown): value is JsonObject { - return value !== null && typeof value === 'object' && !Array.isArray(value); -} - -/** Rejects unexpected fields at a parsed JSON boundary. */ -function rejectUnknownKeys(source: JsonObject, allowed: readonly string[], label: string): void { - for (const key of Object.keys(source)) { - if (!allowed.includes(key)) { - throw new AdapterValidationError(`${label} field "${key}" is not allowed`); - } - } -} - -/** Reads a required string field. */ -function readRequiredString(source: JsonObject, key: string): string { - const value = source[key]; - if (typeof value !== 'string' || value.length === 0) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string`); - } - return value; -} - -/** Reads a required context string. */ -function readContextString(value: string | undefined, key: string): string { - if (typeof value !== 'string' || value.length === 0) { - throw new AdapterValidationError(`Continuum artifact load context field "${key}" must be a non-empty string`); - } - return value; -} - -/** Reads a required number field. */ -function readRequiredNumber(source: JsonObject, key: string): number { - const value = source[key]; - if (typeof value !== 'number' || !Number.isFinite(value)) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a finite number`); - } - return value; -} - -/** Reads an optional number field. */ -function readOptionalNumber(source: JsonObject, key: string): number | undefined { - const value = source[key]; - if (value === undefined) { - return undefined; - } - return readRequiredNumber(source, key); -} - -/** Reads an optional generated artifact count. */ -function readOptionalArtifactCount(source: JsonObject, key: string): number | undefined { - const count = readOptionalNumber(source, key); - if (count === undefined) { - return undefined; - } - if (!Number.isInteger(count) || count < 0) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-negative integer`); - } - return count; -} - -/** Validates the caller-supplied load context boundary. */ -function validateLoadContext(context: ContinuumArtifactJsonLoadContext): void { - rejectUnknownKeys( - requireJsonObject(context, 'Continuum artifact load context'), - LOAD_CONTEXT_KEYS, - 'Continuum artifact load context', - ); -} - -/** Requires the context authority that matches the parsed artifact shape. */ -function requireContextAuthority( - context: ContinuumArtifactJsonLoadContext, - expected: string, - label: string, -): void { - const actual = readContextAuthority(context.authority); - if (actual !== expected) { - throw new AdapterValidationError(`${label} load context authority must be ${expected}`); - } -} - -/** Reads a context authority carrier as a string. */ -function readContextAuthority(value: string | ContinuumArtifactAuthority): string { - if (typeof value === 'string') { - return value; - } - if (value instanceof ContinuumArtifactAuthority) { - return value.toString(); - } - throw new AdapterValidationError('Continuum artifact load context field "authority" must be an authority carrier'); -} - -/** Reads an optional string array field. */ -function readOptionalStringArray(source: JsonObject, key: string): readonly string[] | undefined { - const value = source[key]; - if (value === undefined) { - return undefined; - } - return readStringArray(source, key); -} - -/** Reads an optional string field. */ -function readOptionalString(source: JsonObject, key: string): string | undefined { - const value = source[key]; - if (value === undefined) { - return undefined; - } - return readRequiredString(source, key); -} - -/** Reads a required string array field. */ -function readStringArray(source: JsonObject, key: string): readonly string[] { - const value = source[key]; - if (!Array.isArray(value)) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a string array`); - } - const strings: string[] = []; - for (const entry of value) { - strings.push(readStringArrayEntry(entry, key)); - } - return Object.freeze(strings); -} - -/** Reads one string array entry. */ -function readStringArrayEntry(value: unknown, key: string): string { - if (typeof value !== 'string' || value.length === 0) { - throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must contain only non-empty strings`); - } - return value; -} diff --git a/src/infrastructure/adapters/continuumArtifactJsonParser.ts b/src/infrastructure/adapters/continuumArtifactJsonParser.ts new file mode 100644 index 00000000..e554e09d --- /dev/null +++ b/src/infrastructure/adapters/continuumArtifactJsonParser.ts @@ -0,0 +1,242 @@ +import type { + ContinuumArtifactJsonLoadContext, + DescriptorFieldSource, + JsonObject, +} from './continuumArtifactJsonTypes.ts'; +import type { ContinuumArtifactDescriptorFields } from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; +import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import { readGeneratedLegs, readSealedIntegrity } from './continuumWesleyManifestInventory.ts'; +import { + isJsonObject, + readContextString, + readOptionalStringArray, + readOptionalString, + readRequiredString, + readStringArray, + rejectUnknownKeys, + requireContextAuthority, + requireJsonObject, +} from './continuumArtifactJsonValidation.ts'; +import { readOptionalFootprints } from './continuumFamilyFixtureValidation.ts'; + +const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; +const WESLEY_REALIZATION_MANIFEST_AUTHORITY = 'generated-artifact'; +const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; +const CONTINUUM_FIXTURE_AUTHORITY = 'generated-fixture'; +const CONTINUUM_FIXTURE_GENERATOR = 'continuum/wesley fixture'; +const CONTINUUM_FIXTURE_TARGET = 'continuum-fixture'; + +/** Converts untrusted JSON text into descriptor fields. */ +export function parseContinuumArtifactDescriptorFields( + raw: string, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + return parseDescriptorFields(parseJson(raw), context); +} + +/** Parses untrusted descriptor JSON without leaking platform SyntaxError. */ +function parseJson(raw: string): unknown { + try { + return JSON.parse(raw); + } catch { + throw new AdapterValidationError('Continuum artifact descriptor JSON must be valid JSON'); + } +} + +/** Converts untrusted JSON into descriptor fields. */ +function parseDescriptorFields( + value: unknown, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + const source = requireJsonObject(value, 'Continuum artifact descriptor JSON'); + if (source['kind'] === WESLEY_REALIZATION_MANIFEST_KIND) { + return parseWesleyRealizationManifest(source, context); + } + if (isContinuumFamilyFixture(source)) { + return parseContinuumFamilyFixture(source, context); + } + throw new AdapterValidationError( + 'Continuum artifact descriptor JSON must be a Wesley realization manifest or Continuum family fixture', + ); +} + +/** Converts a Wesley realization manifest into descriptor fields. */ +function parseWesleyRealizationManifest( + source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + requireContextAuthority(context, WESLEY_REALIZATION_MANIFEST_AUTHORITY, 'Wesley realization manifest'); + validateWesleyManifestEnvelope(source); + const integrity = readSealedIntegrity(source); + const legs = readGeneratedLegs(source); + return descriptorFields(context, { + ...wesleyManifestFields(source, context), + ...integrity, + generatedLegs: legs.names, + generatedFiles: legs.files, + }); +} + +/** Validates the non-semantic envelope fields on a Wesley manifest. */ +function validateWesleyManifestEnvelope(source: JsonObject): void { + rejectUnknownKeys( + source, + [ + 'kind', + 'schemaPath', + 'canonicalSchemaPath', + 'schemaHash', + 'sourceHash', + 'outDir', + 'targets', + 'integrity', + 'generatedLegs', + 'proves', + 'doesNotProve', + ], + 'Wesley realization manifest', + ); + readOptionalString(source, 'canonicalSchemaPath'); + readOptionalString(source, 'outDir'); + readOptionalStringArray(source, 'proves'); + readOptionalStringArray(source, 'doesNotProve'); +} + +/** Reads the descriptor-facing fields from a Wesley realization manifest. */ +function wesleyManifestFields( + source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): DescriptorFieldSource { + return { + sourceSchemaPath: readRequiredString(source, 'schemaPath'), + generatedBy: context.generatedBy ?? 'wesley compile', + artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, + targets: readStringArray(source, 'targets'), + schemaHash: readRequiredString(source, 'schemaHash'), + sourceHash: readRequiredString(source, 'sourceHash'), + }; +} + +/** Converts a Continuum family fixture into descriptor fields. */ +function parseContinuumFamilyFixture( + source: JsonObject, + context: ContinuumArtifactJsonLoadContext, +): ContinuumArtifactDescriptorFields { + requireContextAuthority(context, CONTINUUM_FIXTURE_AUTHORITY, 'Continuum family fixture'); + rejectUnknownKeys( + source, + ['objectTypes', 'enumTypes', 'ops', 'invariants', 'footprints', 'types'], + 'Continuum family fixture', + ); + readOptionalStringArray(source, 'objectTypes'); + readOptionalStringArray(source, 'enumTypes'); + readOperations(source); + readOptionalStringArray(source, 'invariants'); + readOptionalFootprints(source); + readOptionalTypeMap(source); + + return descriptorFields(context, { + sourceSchemaPath: readContextString(context.sourceSchemaPath, 'sourceSchemaPath'), + generatedBy: context.generatedBy ?? CONTINUUM_FIXTURE_GENERATOR, + artifactKind: CONTINUUM_FIXTURE_KIND, + targets: context.targets ?? [CONTINUUM_FIXTURE_TARGET], + }); +} + +/** Builds descriptor fields without trusting authority from the untrusted JSON. */ +function descriptorFields( + context: ContinuumArtifactJsonLoadContext, + required: DescriptorFieldSource, +): ContinuumArtifactDescriptorFields { + return { + familyId: context.familyId, + sourceSchemaPath: required.sourceSchemaPath, + generatedBy: required.generatedBy, + artifactKind: required.artifactKind, + authority: context.authority, + targets: required.targets, + ...contextDescriptorFields(context), + ...sourceHashFields(required), + ...integrityDescriptorFields(required), + ...signatureDescriptorFields(required), + ...generatedInventoryFields(required), + }; +} + +/** Selects optional descriptor fields provided by load context. */ +function contextDescriptorFields( + context: ContinuumArtifactJsonLoadContext, +): Partial { + return { + ...(context.version !== undefined ? { version: context.version } : {}), + ...(context.witnessScope !== undefined ? { witnessScope: context.witnessScope } : {}), + ...(context.artifactDigest !== undefined ? { artifactDigest: context.artifactDigest } : {}), + }; +} + +/** Selects optional schema hash fields from source evidence. */ +function sourceHashFields(required: DescriptorFieldSource): Partial { + return { + ...(required.schemaHash !== undefined ? { schemaHash: required.schemaHash } : {}), + ...(required.sourceHash !== undefined ? { sourceHash: required.sourceHash } : {}), + }; +} + +/** Selects optional integrity fields from source evidence. */ +function integrityDescriptorFields(required: DescriptorFieldSource): Partial { + return { + ...(required.integrityStatus !== undefined ? { integrityStatus: required.integrityStatus } : {}), + ...(required.integrityScope !== undefined ? { integrityScope: required.integrityScope } : {}), + ...(required.hashAlgorithm !== undefined ? { hashAlgorithm: required.hashAlgorithm } : {}), + }; +} + +/** Selects optional signature fields from source evidence. */ +function signatureDescriptorFields(required: DescriptorFieldSource): Partial { + return { + ...(required.signatureAlgorithm !== undefined ? { signatureAlgorithm: required.signatureAlgorithm } : {}), + ...(required.signatureKeyId !== undefined ? { signatureKeyId: required.signatureKeyId } : {}), + }; +} + +/** Selects optional generated inventory fields from source evidence. */ +function generatedInventoryFields(required: DescriptorFieldSource): Partial { + return { + ...(required.generatedLegs !== undefined ? { generatedLegs: required.generatedLegs } : {}), + ...(required.generatedFiles !== undefined ? { generatedFiles: required.generatedFiles } : {}), + }; +} + +/** Returns true when the top-level object has the Continuum fixture shape. */ +function isContinuumFamilyFixture(source: JsonObject): boolean { + return Array.isArray(source['ops']) && ( + Array.isArray(source['objectTypes']) || + isJsonObject(source['types']) + ); +} + +/** Reads and validates Continuum fixture operations. */ +function readOperations(source: JsonObject): void { + const { ops } = source; + if (!Array.isArray(ops) || ops.length === 0) { + throw new AdapterValidationError('Continuum family fixture field "ops" must be a non-empty operation array'); + } + for (const entry of ops) { + const op = requireJsonObject(entry, 'Continuum family fixture operation'); + rejectUnknownKeys(op, ['name', 'resultType'], 'Continuum family fixture operation'); + readRequiredString(op, 'name'); + readRequiredString(op, 'resultType'); + } +} + +/** Reads and validates an optional Continuum boundary type map. */ +function readOptionalTypeMap(source: JsonObject): void { + const { types } = source; + if (types === undefined) { + return; + } + const typeMap = requireJsonObject(types, 'Continuum family fixture types'); + for (const name of Object.keys(typeMap)) { + readStringArray(typeMap, name); + } +} diff --git a/src/infrastructure/adapters/continuumArtifactJsonTypes.ts b/src/infrastructure/adapters/continuumArtifactJsonTypes.ts new file mode 100644 index 00000000..cb8eb058 --- /dev/null +++ b/src/infrastructure/adapters/continuumArtifactJsonTypes.ts @@ -0,0 +1,44 @@ +import type ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; +import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts'; + +export type JsonObject = Readonly>; + +export type ContinuumArtifactJsonLoadContext = { + readonly familyId: string | ContinuumFamilyId; + readonly authority: string | ContinuumArtifactAuthority; + readonly sourceSchemaPath?: string; + readonly generatedBy?: string; + readonly version?: string; + readonly targets?: readonly string[]; + readonly witnessScope?: string; + readonly artifactDigest?: string; +}; + +export type DescriptorFieldSource = { + readonly sourceSchemaPath: string; + readonly generatedBy: string; + readonly artifactKind: string; + readonly targets: readonly string[]; + readonly schemaHash?: string; + readonly sourceHash?: string; + readonly integrityStatus?: string; + readonly integrityScope?: string; + readonly hashAlgorithm?: string; + readonly signatureAlgorithm?: string; + readonly signatureKeyId?: string; + readonly generatedLegs?: readonly string[]; + readonly generatedFiles?: readonly string[]; +}; + +export type WesleyIntegrityFields = { + readonly integrityStatus: string; + readonly integrityScope: string; + readonly hashAlgorithm: string; + readonly signatureAlgorithm: string; + readonly signatureKeyId: string; +}; + +export type GeneratedLegInventory = { + readonly names: readonly string[]; + readonly files: readonly string[]; +}; diff --git a/src/infrastructure/adapters/continuumArtifactJsonValidation.ts b/src/infrastructure/adapters/continuumArtifactJsonValidation.ts new file mode 100644 index 00000000..7c87f6a7 --- /dev/null +++ b/src/infrastructure/adapters/continuumArtifactJsonValidation.ts @@ -0,0 +1,156 @@ +import ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; +import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import type { ContinuumArtifactJsonLoadContext, JsonObject } from './continuumArtifactJsonTypes.ts'; + +const LOAD_CONTEXT_KEYS = Object.freeze([ + 'familyId', + 'authority', + 'sourceSchemaPath', + 'generatedBy', + 'version', + 'targets', + 'witnessScope', + 'artifactDigest', +]); + +/** Validates the caller-supplied load context boundary. */ +export function validateLoadContext(context: ContinuumArtifactJsonLoadContext): void { + rejectUnknownKeys( + requireJsonObject(context, 'Continuum artifact load context'), + LOAD_CONTEXT_KEYS, + 'Continuum artifact load context', + ); +} + +/** Requires the context authority that matches the parsed artifact shape. */ +export function requireContextAuthority( + context: ContinuumArtifactJsonLoadContext, + expected: string, + label: string, +): void { + const actual = readContextAuthority(context.authority); + if (actual !== expected) { + throw new AdapterValidationError(`${label} load context authority must be ${expected}`); + } +} + +/** Requires a non-array JSON object. */ +export function requireJsonObject(value: unknown, label: string): JsonObject { + if (!isJsonObject(value)) { + throw new AdapterValidationError(`${label} must be an object`); + } + return value; +} + +/** Returns true when a value is a non-array JSON object. */ +export function isJsonObject(value: unknown): value is JsonObject { + return value !== null && typeof value === 'object' && !Array.isArray(value); +} + +/** Rejects unexpected fields at a parsed JSON boundary. */ +export function rejectUnknownKeys(source: JsonObject, allowed: readonly string[], label: string): void { + for (const key of Object.keys(source)) { + if (!allowed.includes(key)) { + throw new AdapterValidationError(`${label} field "${key}" is not allowed`); + } + } +} + +/** Reads a required string field. */ +export function readRequiredString(source: JsonObject, key: string): string { + const value = source[key]; + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-empty string`); + } + return value; +} + +/** Reads a required context string. */ +export function readContextString(value: string | undefined, key: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError(`Continuum artifact load context field "${key}" must be a non-empty string`); + } + return value; +} + +/** Reads a required number field. */ +export function readRequiredNumber(source: JsonObject, key: string): number { + const value = source[key]; + if (typeof value !== 'number' || !Number.isFinite(value)) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a finite number`); + } + return value; +} + +/** Reads an optional generated artifact count. */ +export function readOptionalArtifactCount(source: JsonObject, key: string): number | undefined { + const count = readOptionalNumber(source, key); + if (count === undefined) { + return undefined; + } + if (!Number.isInteger(count) || count < 0) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a non-negative integer`); + } + return count; +} + +/** Reads an optional string array field. */ +export function readOptionalStringArray(source: JsonObject, key: string): readonly string[] | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + return readStringArray(source, key); +} + +/** Reads an optional string field. */ +export function readOptionalString(source: JsonObject, key: string): string | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + return readRequiredString(source, key); +} + +/** Reads a required string array field. */ +export function readStringArray(source: JsonObject, key: string): readonly string[] { + const value = source[key]; + if (!Array.isArray(value)) { + throw new AdapterValidationError(`Continuum artifact descriptor field "${key}" must be a string array`); + } + const strings: string[] = []; + for (const entry of value) { + strings.push(readStringArrayEntry(entry, key)); + } + return Object.freeze(strings); +} + +/** Reads an optional number field. */ +function readOptionalNumber(source: JsonObject, key: string): number | undefined { + const value = source[key]; + if (value === undefined) { + return undefined; + } + return readRequiredNumber(source, key); +} + +/** Reads a context authority carrier as a string. */ +function readContextAuthority(value: string | ContinuumArtifactAuthority): string { + if (typeof value === 'string') { + return value; + } + if (value instanceof ContinuumArtifactAuthority) { + return value.toString(); + } + throw new AdapterValidationError('Continuum artifact load context field "authority" must be an authority carrier'); +} + +/** Reads one string array entry. */ +function readStringArrayEntry(value: unknown, key: string): string { + if (typeof value !== 'string' || value.length === 0) { + throw new AdapterValidationError( + `Continuum artifact descriptor field "${key}" must contain only non-empty strings`, + ); + } + return value; +} diff --git a/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts b/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts new file mode 100644 index 00000000..b75cebf3 --- /dev/null +++ b/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts @@ -0,0 +1,28 @@ +import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import type { JsonObject } from './continuumArtifactJsonTypes.ts'; +import { + readRequiredString, + readStringArray, + rejectUnknownKeys, + requireJsonObject, +} from './continuumArtifactJsonValidation.ts'; + +/** Reads and validates optional Continuum fixture footprints. */ +export function readOptionalFootprints(source: JsonObject): void { + const { footprints } = source; + if (footprints === undefined) { + return; + } + if (!Array.isArray(footprints)) { + throw new AdapterValidationError('Continuum family fixture field "footprints" must be an array'); + } + for (const entry of footprints) { + const footprint = requireJsonObject(entry, 'Continuum family fixture footprint'); + rejectUnknownKeys(footprint, ['opName', 'reads', 'writes', 'creates', 'deletes'], 'Continuum family fixture footprint'); + readRequiredString(footprint, 'opName'); + readStringArray(footprint, 'reads'); + readStringArray(footprint, 'writes'); + readStringArray(footprint, 'creates'); + readStringArray(footprint, 'deletes'); + } +} diff --git a/src/infrastructure/adapters/continuumWesleyManifestInventory.ts b/src/infrastructure/adapters/continuumWesleyManifestInventory.ts new file mode 100644 index 00000000..e047da70 --- /dev/null +++ b/src/infrastructure/adapters/continuumWesleyManifestInventory.ts @@ -0,0 +1,106 @@ +import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import type { GeneratedLegInventory, JsonObject, WesleyIntegrityFields } from './continuumArtifactJsonTypes.ts'; +import { + readOptionalArtifactCount, + readOptionalStringArray, + readRequiredNumber, + readRequiredString, + rejectUnknownKeys, + requireJsonObject, +} from './continuumArtifactJsonValidation.ts'; + +/** Reads and validates a Wesley integrity block. */ +export function readSealedIntegrity(source: JsonObject): WesleyIntegrityFields { + const integrity = requireJsonObject(source['integrity'], 'Wesley realization manifest integrity'); + rejectUnknownKeys( + integrity, + ['status', 'scope', 'hashAlgorithm', 'signatureAlgorithm', 'signatureKeyId'], + 'Wesley realization manifest integrity', + ); + const status = readRequiredString(integrity, 'status'); + if (status !== 'sealed') { + throw new AdapterValidationError('Wesley realization manifest integrity status must be sealed'); + } + return { + integrityStatus: status, + integrityScope: readRequiredString(integrity, 'scope'), + hashAlgorithm: readRequiredString(integrity, 'hashAlgorithm'), + signatureAlgorithm: readRequiredString(integrity, 'signatureAlgorithm'), + signatureKeyId: readRequiredString(integrity, 'signatureKeyId'), + }; +} + +/** Reads and validates Wesley generated leg inventory. */ +export function readGeneratedLegs(source: JsonObject): GeneratedLegInventory { + const generatedLegs = requireJsonObject(source['generatedLegs'], 'Wesley realization manifest generatedLegs'); + const names = readGeneratedLegNames(generatedLegs); + const files: string[] = []; + for (const name of names) { + for (const path of readGeneratedLegFiles(generatedLegs, name)) { + files.push(path); + } + } + return { names, files: Object.freeze(files.sort()) }; +} + +/** Reads the sorted generated leg names from a Wesley manifest. */ +function readGeneratedLegNames(generatedLegs: JsonObject): readonly string[] { + const names = Object.freeze(Object.keys(generatedLegs).sort()); + if (names.length === 0) { + throw new AdapterValidationError('Wesley realization manifest generatedLegs must contain at least one leg'); + } + return names; +} + +/** Reads and validates one Wesley generated leg inventory. */ +function readGeneratedLegFiles(generatedLegs: JsonObject, name: string): readonly string[] { + const leg = requireJsonObject(generatedLegs[name], `Wesley generated leg "${name}"`); + validateGeneratedLegEnvelope(leg, name); + const artifactCount = readOptionalArtifactCount(leg, 'artifactCount'); + const legFiles = readGeneratedFiles(leg, name); + requireArtifactCountMatchesFiles(artifactCount, legFiles.length, name); + return legFiles; +} + +/** Validates one Wesley generated leg envelope. */ +function validateGeneratedLegEnvelope(leg: JsonObject, name: string): void { + rejectUnknownKeys( + leg, + ['outDir', 'schemaHash', 'sourceHash', 'targets', 'artifactCount', 'files'], + `Wesley generated leg "${name}"`, + ); + readRequiredString(leg, 'outDir'); + readRequiredString(leg, 'schemaHash'); + readRequiredString(leg, 'sourceHash'); + readOptionalStringArray(leg, 'targets'); +} + +/** Requires Wesley's artifact count to match the generated file inventory. */ +function requireArtifactCountMatchesFiles(count: number | undefined, fileCount: number, legName: string): void { + if (count !== undefined && count !== fileCount) { + throw new AdapterValidationError( + `Wesley generated leg "${legName}" field "artifactCount" must match generated file count`, + ); + } +} + +/** Reads generated file entries from one Wesley generated leg. */ +function readGeneratedFiles(source: JsonObject, legName: string): readonly string[] { + const value = source['files']; + if (value === undefined) { + return []; + } + if (!Array.isArray(value)) { + throw new AdapterValidationError(`Wesley generated leg "${legName}" field "files" must be an array`); + } + const files: string[] = []; + for (const entry of value) { + const file = requireJsonObject(entry, `Wesley generated leg "${legName}" file`); + rejectUnknownKeys(file, ['path', 'size', 'contentHash', 'signature'], `Wesley generated leg "${legName}" file`); + files.push(readRequiredString(file, 'path')); + readRequiredNumber(file, 'size'); + readRequiredString(file, 'contentHash'); + readRequiredString(file, 'signature'); + } + return Object.freeze(files); +} From 9422601978855dfb796b0417983224e3914edc37 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 15:59:48 -0700 Subject: [PATCH 12/16] docs: record continuum review fixes --- CHANGELOG.md | 7 ++++++ docs/BEARING.md | 8 ++++--- .../v18-continuum-artifact-ingestion.md | 23 ++++++++++++------- 3 files changed, 27 insertions(+), 11 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9bba2e8..b97afa14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- Continuum artifact ingestion now enforces artifact-kind/authority pairing at + the domain policy layer, keeps review fixtures repo-neutral, and splits the + JSON adapter into focused parser, validation, fixture, and Wesley inventory + modules so the adapter entry point stays below the source-size policy cap. + ## [17.0.0] — 2026-05-05 ### Changed diff --git a/docs/BEARING.md b/docs/BEARING.md index 319c07ee..6a20aaa8 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -172,9 +172,11 @@ before the final commit for that slice, and mark completed items with `- [x]`. guard against handwritten local mirrors becoming contract authority. The current seam admits Continuum receipt-family fixture JSON and Wesley realization manifest JSON through explicit load context; it binds each - accepted JSON shape to the matching context authority, rejects self-attested - authority fields from artifact JSON, and rejects empty or internally - inconsistent Wesley generated inventory. + accepted JSON shape to the matching context authority, the domain policy + independently rejects descriptor kind/authority mismatches, the adapter entry + point is split below the source-size cap, self-attested authority fields from + artifact JSON are rejected, and empty or internally inconsistent Wesley + generated inventory is rejected. - [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. - [ ] 7. Prove the patch commit visibility contract: success means canonical diff --git a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md index 878ec369..b077cbf5 100644 --- a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md +++ b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md @@ -32,8 +32,11 @@ This slice adds: local mirrors, and handwritten mirrors; - `ContinuumArtifactDescriptor` as the runtime-backed descriptor object; - `ContinuumArtifactIngestionPolicy` as the authority guard; -- `ContinuumArtifactJsonFileAdapter` as the infrastructure-edge JSON loader - for Continuum fixture JSON and Wesley realization manifest JSON; +- `ContinuumArtifactJsonFileAdapter` as the infrastructure-edge file/string + entry point; +- adapter-local JSON parser, validation, Continuum fixture, and Wesley manifest + inventory modules for Continuum fixture JSON and Wesley realization manifest + JSON; - `test/fixtures/continuum/receipt-family-generated-artifact.json` as the first receipt-family Continuum fixture; - `test/fixtures/continuum/receipt-family-wesley-realization-manifest.json` as @@ -58,7 +61,10 @@ validated constructor fields and runtime-backed objects. Authority is not read from untrusted artifact JSON. The adapter receives authority through explicit load context, validates the artifact shape, requires the context authority that belongs to that shape, and then lets the domain -policy decide whether that context can become descriptor authority. +policy decide whether that context can become descriptor authority. The domain +policy also independently verifies that a descriptor's artifact kind and +generated authority remain paired, so hand-built descriptors cannot bypass the +adapter seam. Wesley realization manifests must contain at least one generated leg. When Wesley records an `artifactCount`, it must match the generated file inventory @@ -95,14 +101,14 @@ Observed focused Continuum-suite test result: ```text Test Files 2 passed (2) -Tests 25 passed (25) +Tests 27 passed (27) ``` Observed focused export/error sweep: ```text Test Files 4 passed (4) -Tests 75 passed (75) +Tests 77 passed (77) ``` Coverage gate: @@ -125,13 +131,14 @@ gate for this slice is the full-suite CI coverage command above. - Boundary validation: green; untrusted JSON is parsed only in the infrastructure adapter. - Behavior ownership: green; the descriptor owns descriptor invariants and the - ingestion policy owns authority decisions. + ingestion policy owns authority decisions, including kind/authority pairing. - Message parsing: green; no behavior branches parse free-form messages. - Ambient time or entropy: green; no ambient time or entropy introduced. - Fake shape trust or cast-cosplay: green; generated-family authority is carried by load context, self-attested JSON authority is rejected, each accepted JSON - shape is bound to its context authority, and Wesley generated inventory is - checked before descriptor construction. + shape is bound to its context authority, the policy independently checks + descriptor kind/authority pairing, and Wesley generated inventory is checked + before descriptor construction. ## Closeout From 7728214a565ed18f861a038eecf75256290bd2f4 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 16:01:59 -0700 Subject: [PATCH 13/16] docs: update continuum coverage result --- .../v18-continuum-artifact-ingestion.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md index b077cbf5..e98f3744 100644 --- a/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md +++ b/docs/design/0149-v18-continuum-artifact-ingestion/v18-continuum-artifact-ingestion.md @@ -116,8 +116,8 @@ Coverage gate: ```text npm run test:coverage:ci Test Files 447 passed (447) -Tests 6824 passed (6824) -All files 92.12% lines +Tests 6826 passed (6826) +All files 92.13% lines ``` Targeted coverage diagnostics are not recorded as green slice gates because the From 4725ed4e08abdfc8327775fe5cffc0e9ce62b554 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 16:12:45 -0700 Subject: [PATCH 14/16] refactor: split continuum json type carriers --- .../ContinuumArtifactJsonFileAdapter.ts | 4 +- .../ContinuumArtifactJsonLoadContext.ts | 13 ++++++ .../adapters/DescriptorFieldSource.ts | 15 +++++++ .../adapters/GeneratedLegInventory.ts | 4 ++ src/infrastructure/adapters/JsonObject.ts | 1 + .../adapters/WesleyIntegrityFields.ts | 7 +++ .../adapters/continuumArtifactJsonParser.ts | 8 ++-- .../adapters/continuumArtifactJsonTypes.ts | 44 ------------------- .../continuumArtifactJsonValidation.ts | 3 +- .../continuumFamilyFixtureValidation.ts | 2 +- .../continuumWesleyManifestInventory.ts | 4 +- 11 files changed, 51 insertions(+), 54 deletions(-) create mode 100644 src/infrastructure/adapters/ContinuumArtifactJsonLoadContext.ts create mode 100644 src/infrastructure/adapters/DescriptorFieldSource.ts create mode 100644 src/infrastructure/adapters/GeneratedLegInventory.ts create mode 100644 src/infrastructure/adapters/JsonObject.ts create mode 100644 src/infrastructure/adapters/WesleyIntegrityFields.ts delete mode 100644 src/infrastructure/adapters/continuumArtifactJsonTypes.ts diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts index d1b9da34..c6e790e4 100644 --- a/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts +++ b/src/infrastructure/adapters/ContinuumArtifactJsonFileAdapter.ts @@ -2,11 +2,11 @@ import { readFile } from 'node:fs/promises'; import ContinuumArtifactDescriptor from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; import ContinuumArtifactIngestionPolicy from '../../domain/continuum/ContinuumArtifactIngestionPolicy.ts'; +import type { ContinuumArtifactJsonLoadContext } from './ContinuumArtifactJsonLoadContext.ts'; import { parseContinuumArtifactDescriptorFields } from './continuumArtifactJsonParser.ts'; -import type { ContinuumArtifactJsonLoadContext } from './continuumArtifactJsonTypes.ts'; import { validateLoadContext } from './continuumArtifactJsonValidation.ts'; -export type { ContinuumArtifactJsonLoadContext } from './continuumArtifactJsonTypes.ts'; +export type { ContinuumArtifactJsonLoadContext } from './ContinuumArtifactJsonLoadContext.ts'; /** Loads Continuum artifact descriptors from JSON files at the adapter edge. */ export default class ContinuumArtifactJsonFileAdapter { diff --git a/src/infrastructure/adapters/ContinuumArtifactJsonLoadContext.ts b/src/infrastructure/adapters/ContinuumArtifactJsonLoadContext.ts new file mode 100644 index 00000000..466baa09 --- /dev/null +++ b/src/infrastructure/adapters/ContinuumArtifactJsonLoadContext.ts @@ -0,0 +1,13 @@ +import type ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; +import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts'; + +export type ContinuumArtifactJsonLoadContext = { + readonly familyId: string | ContinuumFamilyId; + readonly authority: string | ContinuumArtifactAuthority; + readonly sourceSchemaPath?: string; + readonly generatedBy?: string; + readonly version?: string; + readonly targets?: readonly string[]; + readonly witnessScope?: string; + readonly artifactDigest?: string; +}; diff --git a/src/infrastructure/adapters/DescriptorFieldSource.ts b/src/infrastructure/adapters/DescriptorFieldSource.ts new file mode 100644 index 00000000..1970efe6 --- /dev/null +++ b/src/infrastructure/adapters/DescriptorFieldSource.ts @@ -0,0 +1,15 @@ +export type DescriptorFieldSource = { + readonly sourceSchemaPath: string; + readonly generatedBy: string; + readonly artifactKind: string; + readonly targets: readonly string[]; + readonly schemaHash?: string; + readonly sourceHash?: string; + readonly integrityStatus?: string; + readonly integrityScope?: string; + readonly hashAlgorithm?: string; + readonly signatureAlgorithm?: string; + readonly signatureKeyId?: string; + readonly generatedLegs?: readonly string[]; + readonly generatedFiles?: readonly string[]; +}; diff --git a/src/infrastructure/adapters/GeneratedLegInventory.ts b/src/infrastructure/adapters/GeneratedLegInventory.ts new file mode 100644 index 00000000..b2b2e0f8 --- /dev/null +++ b/src/infrastructure/adapters/GeneratedLegInventory.ts @@ -0,0 +1,4 @@ +export type GeneratedLegInventory = { + readonly names: readonly string[]; + readonly files: readonly string[]; +}; diff --git a/src/infrastructure/adapters/JsonObject.ts b/src/infrastructure/adapters/JsonObject.ts new file mode 100644 index 00000000..962622a0 --- /dev/null +++ b/src/infrastructure/adapters/JsonObject.ts @@ -0,0 +1 @@ +export type JsonObject = Readonly>; diff --git a/src/infrastructure/adapters/WesleyIntegrityFields.ts b/src/infrastructure/adapters/WesleyIntegrityFields.ts new file mode 100644 index 00000000..08f6e5f9 --- /dev/null +++ b/src/infrastructure/adapters/WesleyIntegrityFields.ts @@ -0,0 +1,7 @@ +export type WesleyIntegrityFields = { + readonly integrityStatus: string; + readonly integrityScope: string; + readonly hashAlgorithm: string; + readonly signatureAlgorithm: string; + readonly signatureKeyId: string; +}; diff --git a/src/infrastructure/adapters/continuumArtifactJsonParser.ts b/src/infrastructure/adapters/continuumArtifactJsonParser.ts index e554e09d..2932209d 100644 --- a/src/infrastructure/adapters/continuumArtifactJsonParser.ts +++ b/src/infrastructure/adapters/continuumArtifactJsonParser.ts @@ -1,10 +1,8 @@ -import type { - ContinuumArtifactJsonLoadContext, - DescriptorFieldSource, - JsonObject, -} from './continuumArtifactJsonTypes.ts'; import type { ContinuumArtifactDescriptorFields } from '../../domain/continuum/ContinuumArtifactDescriptor.ts'; import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; +import type { ContinuumArtifactJsonLoadContext } from './ContinuumArtifactJsonLoadContext.ts'; +import type { DescriptorFieldSource } from './DescriptorFieldSource.ts'; +import type { JsonObject } from './JsonObject.ts'; import { readGeneratedLegs, readSealedIntegrity } from './continuumWesleyManifestInventory.ts'; import { isJsonObject, diff --git a/src/infrastructure/adapters/continuumArtifactJsonTypes.ts b/src/infrastructure/adapters/continuumArtifactJsonTypes.ts deleted file mode 100644 index cb8eb058..00000000 --- a/src/infrastructure/adapters/continuumArtifactJsonTypes.ts +++ /dev/null @@ -1,44 +0,0 @@ -import type ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; -import type ContinuumFamilyId from '../../domain/continuum/ContinuumFamilyId.ts'; - -export type JsonObject = Readonly>; - -export type ContinuumArtifactJsonLoadContext = { - readonly familyId: string | ContinuumFamilyId; - readonly authority: string | ContinuumArtifactAuthority; - readonly sourceSchemaPath?: string; - readonly generatedBy?: string; - readonly version?: string; - readonly targets?: readonly string[]; - readonly witnessScope?: string; - readonly artifactDigest?: string; -}; - -export type DescriptorFieldSource = { - readonly sourceSchemaPath: string; - readonly generatedBy: string; - readonly artifactKind: string; - readonly targets: readonly string[]; - readonly schemaHash?: string; - readonly sourceHash?: string; - readonly integrityStatus?: string; - readonly integrityScope?: string; - readonly hashAlgorithm?: string; - readonly signatureAlgorithm?: string; - readonly signatureKeyId?: string; - readonly generatedLegs?: readonly string[]; - readonly generatedFiles?: readonly string[]; -}; - -export type WesleyIntegrityFields = { - readonly integrityStatus: string; - readonly integrityScope: string; - readonly hashAlgorithm: string; - readonly signatureAlgorithm: string; - readonly signatureKeyId: string; -}; - -export type GeneratedLegInventory = { - readonly names: readonly string[]; - readonly files: readonly string[]; -}; diff --git a/src/infrastructure/adapters/continuumArtifactJsonValidation.ts b/src/infrastructure/adapters/continuumArtifactJsonValidation.ts index 7c87f6a7..44330601 100644 --- a/src/infrastructure/adapters/continuumArtifactJsonValidation.ts +++ b/src/infrastructure/adapters/continuumArtifactJsonValidation.ts @@ -1,6 +1,7 @@ import ContinuumArtifactAuthority from '../../domain/continuum/ContinuumArtifactAuthority.ts'; import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; -import type { ContinuumArtifactJsonLoadContext, JsonObject } from './continuumArtifactJsonTypes.ts'; +import type { ContinuumArtifactJsonLoadContext } from './ContinuumArtifactJsonLoadContext.ts'; +import type { JsonObject } from './JsonObject.ts'; const LOAD_CONTEXT_KEYS = Object.freeze([ 'familyId', diff --git a/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts b/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts index b75cebf3..861e185e 100644 --- a/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts +++ b/src/infrastructure/adapters/continuumFamilyFixtureValidation.ts @@ -1,5 +1,5 @@ import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; -import type { JsonObject } from './continuumArtifactJsonTypes.ts'; +import type { JsonObject } from './JsonObject.ts'; import { readRequiredString, readStringArray, diff --git a/src/infrastructure/adapters/continuumWesleyManifestInventory.ts b/src/infrastructure/adapters/continuumWesleyManifestInventory.ts index e047da70..2198f037 100644 --- a/src/infrastructure/adapters/continuumWesleyManifestInventory.ts +++ b/src/infrastructure/adapters/continuumWesleyManifestInventory.ts @@ -1,5 +1,7 @@ import AdapterValidationError from '../../domain/errors/AdapterValidationError.ts'; -import type { GeneratedLegInventory, JsonObject, WesleyIntegrityFields } from './continuumArtifactJsonTypes.ts'; +import type { GeneratedLegInventory } from './GeneratedLegInventory.ts'; +import type { JsonObject } from './JsonObject.ts'; +import type { WesleyIntegrityFields } from './WesleyIntegrityFields.ts'; import { readOptionalArtifactCount, readOptionalStringArray, From b0b7bf96c7b1de0c4d325ab8af9a66c4d3be2ad9 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 16:12:59 -0700 Subject: [PATCH 15/16] test: name continuum authority fixtures --- .../ContinuumArtifactIngestionPolicy.test.ts | 38 ++++++++++--------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts index bcd5aef8..ddfdbfa8 100644 --- a/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts +++ b/test/unit/domain/continuum/ContinuumArtifactIngestionPolicy.test.ts @@ -10,6 +10,10 @@ import WarpError from '../../../../src/domain/errors/WarpError.ts'; const RECEIPT_SCHEMA_PATH = 'schemas/continuum-receipt-family.graphql'; const CONTINUUM_FIXTURE_KIND = 'continuum.family.fixture'; const WESLEY_REALIZATION_MANIFEST_KIND = 'wesley.realization.manifest.v1'; +const AUTHORITY_GENERATED_FIXTURE = 'generated-fixture'; +const AUTHORITY_GENERATED_ARTIFACT = 'generated-artifact'; +const AUTHORITY_LOCAL_MIRROR = 'local-mirror'; +const AUTHORITY_HANDWRITTEN_MIRROR = 'handwritten-mirror'; type DescriptorFixtureFields = { readonly artifactKind?: string; @@ -23,7 +27,7 @@ function makeDescriptor(fields: DescriptorFixtureFields = {}): ContinuumArtifact sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley witness-continuum --scope receipt-family', artifactKind: fields.artifactKind ?? CONTINUUM_FIXTURE_KIND, - authority: fields.authority ?? 'generated-fixture', + authority: fields.authority ?? AUTHORITY_GENERATED_FIXTURE, targets: ['typescript', 'echo'], version: '0.1.0', witnessScope: 'receipt-family', @@ -32,7 +36,7 @@ function makeDescriptor(fields: DescriptorFixtureFields = {}): ContinuumArtifact describe('ContinuumArtifactIngestionPolicy', () => { it('accepts documented generated fixtures', () => { - const descriptor = makeDescriptor({ authority: 'generated-fixture' }); + const descriptor = makeDescriptor({ authority: AUTHORITY_GENERATED_FIXTURE }); const policy = new ContinuumArtifactIngestionPolicy(); expect(policy.ingest(descriptor)).toBe(descriptor); @@ -41,7 +45,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { it('accepts generated artifacts', () => { const descriptor = makeDescriptor({ artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, }); const policy = new ContinuumArtifactIngestionPolicy(); @@ -53,19 +57,19 @@ describe('ContinuumArtifactIngestionPolicy', () => { expect(() => policy.ingest(makeDescriptor({ artifactKind: CONTINUUM_FIXTURE_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, }))).toThrow(ContinuumArtifactAuthorityError); expect(() => policy.ingest(makeDescriptor({ artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-fixture', + authority: AUTHORITY_GENERATED_FIXTURE, }))).toThrow(ContinuumArtifactAuthorityError); }); it('rejects generated authority for unknown artifact kinds', () => { const descriptor = makeDescriptor({ artifactKind: 'continuum.unknown.fixture', - authority: 'generated-fixture', + authority: AUTHORITY_GENERATED_FIXTURE, }); const policy = new ContinuumArtifactIngestionPolicy(); @@ -73,21 +77,21 @@ describe('ContinuumArtifactIngestionPolicy', () => { }); it('rejects local mirrors as family authority', () => { - const descriptor = makeDescriptor({ authority: 'local-mirror' }); + const descriptor = makeDescriptor({ authority: AUTHORITY_LOCAL_MIRROR }); const policy = new ContinuumArtifactIngestionPolicy(); expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); }); it('rejects handwritten mirrors as family authority', () => { - const descriptor = makeDescriptor({ authority: 'handwritten-mirror' }); + const descriptor = makeDescriptor({ authority: AUTHORITY_HANDWRITTEN_MIRROR }); const policy = new ContinuumArtifactIngestionPolicy(); expect(() => policy.ingest(descriptor)).toThrow(ContinuumArtifactAuthorityError); }); it('keeps family ids runtime-backed', () => { - const descriptor = makeDescriptor({ authority: 'generated-fixture' }); + const descriptor = makeDescriptor({ authority: AUTHORITY_GENERATED_FIXTURE }); expect(descriptor.familyId).toBeInstanceOf(ContinuumFamilyId); expect(descriptor.familyId.toString()).toBe('receipt-family'); @@ -113,14 +117,14 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: new ContinuumArtifactAuthority('generated-artifact'), + authority: new ContinuumArtifactAuthority(AUTHORITY_GENERATED_ARTIFACT), targets: ['warp-ttd'], generatedLegs: ['warpTtd'], generatedFiles: ['manifest/schema.json'], }); expect(descriptor.familyId.toString()).toBe('receipt-family'); - expect(descriptor.authority.toString()).toBe('generated-artifact'); + expect(descriptor.authority.toString()).toBe(AUTHORITY_GENERATED_ARTIFACT); expect(descriptor.generatedLegs).toEqual(['warpTtd']); expect(descriptor.generatedFiles).toEqual(['manifest/schema.json']); }); @@ -131,7 +135,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: '', generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, targets: ['warp-ttd'], })).toThrow(WarpError); @@ -140,7 +144,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, targets: [], })).toThrow(WarpError); @@ -149,7 +153,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, targets: ['warp-ttd'], generatedLegs: [''], })).toThrow(WarpError); @@ -162,7 +166,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: 7, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, targets: ['warp-ttd'], })).toThrow(WarpError); @@ -171,7 +175,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, // @ts-expect-error runtime guard for JS callers targets: 'warp-ttd', })).toThrow(WarpError); @@ -181,7 +185,7 @@ describe('ContinuumArtifactIngestionPolicy', () => { sourceSchemaPath: RECEIPT_SCHEMA_PATH, generatedBy: 'wesley compile', artifactKind: WESLEY_REALIZATION_MANIFEST_KIND, - authority: 'generated-artifact', + authority: AUTHORITY_GENERATED_ARTIFACT, targets: ['warp-ttd'], // @ts-expect-error runtime guard for JS callers generatedFiles: 'manifest/schema.json', From 040008a41ea688f5e1f6ca3a7cc2126bd167f793 Mon Sep 17 00:00:00 2001 From: James Ross Date: Thu, 21 May 2026 16:15:51 -0700 Subject: [PATCH 16/16] docs: record continuum review follow-up --- CHANGELOG.md | 3 +++ docs/BEARING.md | 7 ++++--- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b97afa14..92803c09 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 the domain policy layer, keeps review fixtures repo-neutral, and splits the JSON adapter into focused parser, validation, fixture, and Wesley inventory modules so the adapter entry point stays below the source-size policy cap. +- Continuum adapter review follow-up now keeps adapter-local JSON type carriers + in one file per exported type and names the policy-test authority fixtures so + review fixes preserve the source-structure and magic-string rules. ## [17.0.0] — 2026-05-05 diff --git a/docs/BEARING.md b/docs/BEARING.md index 6a20aaa8..cd51494d 100644 --- a/docs/BEARING.md +++ b/docs/BEARING.md @@ -174,9 +174,10 @@ before the final commit for that slice, and mark completed items with `- [x]`. realization manifest JSON through explicit load context; it binds each accepted JSON shape to the matching context authority, the domain policy independently rejects descriptor kind/authority mismatches, the adapter entry - point is split below the source-size cap, self-attested authority fields from - artifact JSON are rejected, and empty or internally inconsistent Wesley - generated inventory is rejected. + point and adapter-local JSON type carriers are split below the source-size + and one-file-per-concept caps, self-attested authority fields from artifact + JSON are rejected, policy-test authority fixtures are named constants, and + empty or internally inconsistent Wesley generated inventory is rejected. - [ ] 6. Make evidence posture explicit: translated git-warp evidence first, native Continuum evidence only after native witnesshood is proven. - [ ] 7. Prove the patch commit visibility contract: success means canonical