Repository navigation
fix(model): one model validator for YAML and ledger read modes (#576) - #593
Conversation
YamlModelStore.validateModel and the ledger read path both call validateModelFiles in model-store-yaml. validateLedgerModelFiles, the ledger-only subset validator, is removed. Ledger read mode now runs node schema, runtime topology and state-machine checks, including the nested state-machine path error from #571. The ledger read path derives projected file schemaVersions with the shared extractSchemaVersion; schemaVersionFromModelBody is removed.
Codex Review Summary: ❌ FailedThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
repo-harness acceptance claim |
Ancienttwo
left a comment
There was a problem hiding this comment.
repo-harness acceptance review
Round: 1
Subject: 22ce466
Tier: high
Verdict: FAIL
Reviewer: codex (gpt-6.1-terra)
Model relation: different-family
Cross-family review (Codex CLI, read-only). Both read paths call the single validateModelFiles validator; the old ledger subset validator and duplicate schema-version parser are removed; the moved YAML checks keep their sequence. The two callers order files differently, so ledger and YAML can report the same failures in a different order. The PR omits the added-line test budget counts.
NITs
- F2: PR body omits the added-line counts: 85 test, 86 product, no new test file; within the caps.
Findings outside the diff
- P2 F1 at packages/local-runtime/runtime-daemon/src/index.ts:244: Ledger files sort with localeCompare, YAML files with default sort (model-store-yaml/src/index.ts:447); B.yaml precedes a.yaml in YAML order and follows it in ledger order. The shared validator appends errors in input order, so multiple invalid files can produce a different first error and differently ordered error arrays. -> Use the same path ordering in both paths and cover multiple invalid files in the parity test.
|
repo-harness acceptance record
Model relation: different-family Findings:
|
|
repo-harness acceptance claim |
Ancienttwo
left a comment
There was a problem hiding this comment.
repo-harness acceptance review
Round: 1
Subject: 22ce466
Tier: high
Verdict: PASS
Reviewer: pi (claude-opus-5-5)
Model relation: same-family
Fresh-context Opus review (Pi seat, magpie). One validator, validateModelFiles in model-store-yaml, called by YamlModelStore.validateModel and ArchitectureLedgerReadModelStore.validateModel; validateLedgerModelFiles, modelDigestForFiles and schemaVersionFromModelBody are deleted with no references left; no compatibility path. git diff -w shows the old validateModel body moved unchanged except an equivalent required-files membership check. Ran the PR's daemon test against origin/main: schema, topology and nested state-machine rows all returned valid in ledger mode, so the tests are real. typecheck pass; validate-model + ledger-layer-validation 44 pass; local-runtime -t ledger 9 pass.
NITs
- F1: runtime-daemon index.ts:244 sorts with localeCompare while listModelFiles uses default sort; confirmed orders differ ([x/B,x/a,x/a1,x/a_b] vs [x/a_b,x/a,x/a1,x/B]), so error order and modelDigest can differ between modes. Pre-existing; use one comparator and add a two-error row whose paths sort differently.
- F2: local-runtime.test.ts:2333 table-driven test stops at the first failing row; optional test.each so each row fails on its own.
|
repo-harness acceptance record
Model relation: same-family Findings:
|
validateModelFiles orders its input by path in code unit order, the order listModelFiles produces, so error order and modelDigest never depend on the caller. The ledger read path ordered its file set with localeCompare and now uses the same exported orderModelFiles. Siblings covered: every ledger-mode caller of the merged file set (validateModel and loadModel), and the YAML listModelFiles order, which now uses the same comparator. Parity test rows now run with test.each.
|
Round 1 fixes are in 5c3670a.
|
|
repo-harness acceptance claim |
Ancienttwo
left a comment
There was a problem hiding this comment.
repo-harness acceptance review
Round: 2
Subject: 5c3670a
Tier: high
Verdict: FAIL
Reviewer: codex (gpt-6.1-terra)
Model relation: different-family
Cross-family review (Codex CLI, read-only) of the round-2 delta. The validator owns the file order and the parity table gains a two-file row; the budget matches (115 test / 99 product, no new file); prior assertions are intact. The daemon parity test checks each mode's modelDigest against its own file set rather than asserting equality between modes.
NITs
- F2: runtime-daemon index.ts:245 loadLedgerModel still calls orderModelFiles; validateModelFiles sorts again. Keep ordering for loadModel where needed and let the validator own validation order.
|
repo-harness acceptance record
Model relation: different-family Findings:
|
|
repo-harness acceptance claim |
Ancienttwo
left a comment
There was a problem hiding this comment.
repo-harness acceptance review
Round: 2
Subject: 5c3670a
Tier: high
Verdict: PASS
Reviewer: pi (claude-opus-5-5)
Model relation: same-family
Fresh-context Opus review (Pi seat, magpie) of the round-2 delta. validateModelFiles starts with orderModelFiles (compareModelPaths, identical to default string sort), so YAML error order and modelDigest are unchanged for existing repositories. loadLedgerModel keeps orderModelFiles because completeTask reads loadModel's list in ledger mode, justified in the PR body; localeCompare is gone. Cross-mode digest equality is not a property of the design (ledger hashes canonical projected YAML, YAML hashes disk bodies); instead each mode's digest is checked against its own set in default order, and a unit test proves validateModelFiles is input-order independent (same errors, order and modelDigest for reverse-localeCompare input). test.each adopted; budget matches numstat. typecheck pass; 45 + 13 pass; parity row 5 and the order unit test fail on 22ce466, so the tests are real.
NITs
- R2-F1: local-runtime.test.ts:2394 digest oracle builds ledgerSet from all listModelFiles(root) plus projected files, while the real ledger path removes ledger-managed Git paths first; correct for this fixture, optionally filter to mirror isArchitectureLedgerManagedModelPath.
|
repo-harness acceptance record
Model relation: same-family Findings:
|
|
repo-harness acceptance claim |
Ancienttwo
left a comment
There was a problem hiding this comment.
repo-harness acceptance review
Round: 3
Subject: 5c3670a
Tier: high
Verdict: PASS
Reviewer: codex (gpt-6.1-terra)
Model relation: different-family
Follow-up on the same head after the head owner's decline of round-2 F1, with no code change. The shared validator test feeds the same files in different orders and compares the full result, errors and modelDigest included; the daemon test checks each mode's digest against its own file set in default path order. Together they verify the ordering requirement without asserting equal digests for different file bodies, so F1 is resolved.
NITs
- F2: runtime-daemon index.ts:245 loadLedgerModel still calls orderModelFiles while validateModelFiles sorts again; loadModel keeps its ordered output by design.
|
repo-harness acceptance record
Model relation: different-family Findings:
|
|
repo-harness integration status |
|
Merge-only sync: 82310e8 has parents 5c3670a (the reviewed head) and e41f70f (main after #591). Seat: orchestrator t7 |
|
The chatgpt-codex-connector review summary is for the earlier commit 22ce466. Its follow-up reports the Codex usage limit, so it raised no finding on the current head. The required reviews come from the GPT and Opus receipts above. Seat: orchestrator t7 |
…S1) (#589) Refs #574 S1 of ADR-0058: one in-process, read-only CodeGraph read session, with the repository readiness check rebuilt on it. Base: `main` 60f2968 (0.14.1, #593 and Bun 1.4.4 from #595). ## Goal `inspectRepositoryCodeFacts` (doctor, `complete` debt, the private snapshot) and `assertRepositoryCodeFactsReady` (the checkpoint, debt, projection and CLI-provider gate) read the index only through `CodeGraph.openSync(root, { readOnly: true })`. They start no CodeGraph process and run no `git status` or `git diff`. ## Changes **Read session** (`codegraph-adapter/src/read-session.ts`, new) - `openCodeGraphReadSession` / `withCodeGraphReadSession` open one handle per operation and close it in `finally`. - The session offers `version()`, `compatibility()`, `freshness()` (memoized per session), `revision()`, `unchangedSinceOpen()`, `graph` and `close()`. - Open states, checked in this order: - `lstat(.codegraph)` fails with `ENOENT` or `ENOTDIR`: `index-missing`. Any other `lstat` error: `index-open-failed`. - `.codegraph` is a link, dangling or not: `index-open-failed`. - `stat(codegraph.db)` fails with `ENOENT` or `ENOTDIR`: `index-missing`. Any other `stat` error: `index-open-failed`. This is the same rule as for `.codegraph`. - A live writer lock (#426): `build-in-progress`, and the index is never opened. - The read-only open or the first revision read throws: `index-open-failed`. - Otherwise `open`. - **Coherent read (design G4):** `unchangedSinceOpen()` re-checks the writer lock and compares the revision taken at open with the revision after the reads. When anything moved, readiness answers not-ready (`index-incomplete`, every index field `null`), and the gate refuses with `code-facts-not-ready` and `retryable: true`. It never passes. - `freshness()` is CodeGraph's own change test: the provider's enumeration, plus its change hash (`readBoundedSourceSync`, `oversizeStamp`, `hashContent`). - An indexed file that left the enumeration or the disk counts as removed. - A file that exists but cannot be read counts as modified, because it cannot be attested. - `pinnedCodeGraphProvider()` takes its version from the manifest of the loaded platform bundle, so `REQUIRED_CODEGRAPH_VERSION` stays the only pin. - `CODEGRAPH_PROVIDER_INTERNAL_EXPORTS` is the Decision 7 table of provider-internal modules and exports. **Readiness** (`codegraph-adapter/src/index.ts`) - **One reader.** `inspectRepositoryCodeFacts` reads both tiers, compatibility and freshness. `assertRepositoryCodeFactsReady` reads compatibility only and reads no source file. - `withExistingCodeGraphSnapshot` calls the same `inspectRepositoryCodeFacts(snapshotRoot)`. The CLI probe and `CodeGraphSnapshotOptions` are deleted. S4 removes the snapshot itself. - `RepositoryCodeFactsV1` keeps its shape. **C2:** `unavailable.reasonCode` gains `"index-open-failed"`. - **`binary` removed from the read options.** `RepositoryCodeFactsInspectionOptions` is now `{ provider? }`. `CodeGraphCliProvider` takes an optional third argument, `readProvider`; its `binary` now only builds the index (`sync`). `prepareArchitectureDocumentationProjectionSnapshot` accepts `readProvider` for its gate. `runtime-daemon/src/flow-selector-check.ts` no longer passes `binary` to the snapshot. **A `.codegraph` without `codegraph.db` is `index-missing`** (round-2 item 1). The design table maps an absent database to `AC_CODE_FACTS_UNAVAILABLE` / `index-missing`, and the surviving snapshot path (`index.ts:1225` on main) already refused it that way. Round 1 had returned `not-initialized` (`AC_CODE_FACTS_NOT_READY`); that state is gone. Doctor reports `index-missing` with `indexPresent: false`, and the checkpoint gate refuses with `index-missing`. A 0-byte or schema-less `codegraph.db` now gives `index-open-failed` instead of `not-initialized`. **Contracts** - **C2:** the `failure.ts` policy table gains `index-open-failed` (`AC_CODE_FACTS_UNAVAILABLE`, `inspect-diagnostics`, `retryable: false`). **Agreed scope widening** - `runtime-daemon/src/architecture-debt-code-facts.ts`: the two inspect calls drop `binary` and take the injected `provider`. - **1a:** `surfaces/cli/src/main.ts` passes `process.execArgv` to the daemon it starts, only when `process.versions.bun` is set. Under Node nothing new is passed. - **2a was removed under Bun 1.4.4.** The read session, selector index and symbol reader close exactly as on main, with no GC (see "Bun 1.4.4"). - **1b:** the `bun.lock` hand-edit is dropped. Main's lock was regenerated by Bun 1.4.4 (#595) and already points the workspace `archctx` bin at `./cli/bin/archctx`. This PR changes no byte of `bun.lock`. `bun install --frozen-lockfile` under 1.4.4 is accepted, and `readlink node_modules/.bin/archctx` → `../@archcontext/surfaces/cli/bin/archctx`. - **Dev runtime on darwin:** `packages/surfaces/cli/bin/bun-darwin-sqlite.ts` runs only under Bun on darwin. It calls `bun:sqlite` `Database.setCustomSQLite` with a library from a closed list (`/opt/homebrew/opt/sqlite/lib/libsqlite3.dylib`, `/usr/local/opt/sqlite/lib/libsqlite3.dylib`), and throws with a `brew install sqlite` hint when neither exists. - The `bunfig.toml` test preload imports it. Dev `bin/archctx` imports it and preloads it into the CLI process it starts. - The probe's CI stderr line is removed. - Still needed under Bun 1.4.4 (see below). - It never reaches the npm tarball: the package boundary audit passes, and a release-style bundle has no `setCustomSQLite`, no `bun-darwin-sqlite` and no `libsqlite3.dylib`. **Refactors** - SDK loading, the platform-bundle lookup and the writer-lock check move from `index.ts` into `read-session.ts`. `REQUIRED_CODEGRAPH_PACKAGE` and `REQUIRED_CODEGRAPH_VERSION` are re-exported unchanged. - `codeGraphSnapshotRuntime` and the snapshot's source scanner use the shared bundle lookup. - `repositoryCodeFactsFromStatus` is the one evaluator of an index state. - The gate's unreachable `pending-source-changes` branch is removed, because the compatibility tier never measures pending changes. **Paths not yet moved, each with the slice that moves it.** These are sequencing, not fallbacks. - The projection handshake (`prepareProjectionCodeFacts`) and the import population still use the CLI `status` and `query`. S2+S3 move them. - The snapshot copy and its Node query child stay until S4. ## Round 4 - **R3-1:** the database check is a `stat`, with the same rule as the `.codegraph` `lstat`: only `ENOENT` and `ENOTDIR` mean `index-missing`, and any other error is `index-open-failed`. - Test: an unreadable `.codegraph` (chmod 000, so `codegraph.db` cannot be stat'ed) and an unsearchable project directory (chmod 000, so `.codegraph` cannot be lstat'ed) are both `index-open-failed`, and neither opens the index. - The test is skipped on win32 and when running as root. - **Opus F1:** the open-state list above now states the `lstat` rule exactly. - **Opus F3:** the snapshot comment now says readiness opens the copied index in-process through the read session, while the queries still run in the provider's Node child until S4. The snapshot's `rmSync` is unchanged. ## Bun 1.4.4 (rebased onto 60f2968) Both checks were run locally with Bun 1.4.4 on macOS. - **macOS sidecar: still needed.** After `codegraph sync` removed the sidecar files, Bun 1.4.4 with the system SQLite (3.51.0) still fails the read-only open with "unable to open database file". The darwin module stays unchanged. - **Windows EBUSY guard: removed.** - Measured with `lsof`: after `close()`, Bun 1.4.4 holds 0 of the 5 database files it had open. Bun 1.4.3 held 6 of 6 until a GC after the handle was released. - In all 22 protocol fixtures, the test process held 0 files under the fixture root after `daemon.stop()` without any GC, against 8 under 1.4.3, and had no child process. - So `collectReleasedCodeGraphHandles` and its three call sites are deleted, and so is the test-side `Bun.gc(true)`. - The protocol fixture keeps only rmSync's `maxRetries: 5, retryDelay: 100` on win32, for a transient hold outside the process. - This is a macOS measurement. The Windows shards confirm it on this head; if `EBUSY` returns there, the drop commit (9db4488) is the one to revert. ## Round 3 - **R2-1:** the snapshot keeps C2. When the session inspector answers `unavailable` / `index-open-failed`, `withExistingCodeGraphSnapshot` throws `index-open-failed`; every other unavailable answer stays `code-facts-unavailable`. Test: a non-SQLite `codegraph.db` through the snapshot gives `AC_CODE_FACTS_UNAVAILABLE` / `index-open-failed`. - **R2-2:** `lstat(.codegraph)` failing with `ENOENT` or `ENOTDIR` is `index-missing`. Any other failure (permission, I/O) is `index-open-failed`, because it does not prove the index is absent. - **Opus R1:** the fake gains `readFailsAfterOpen`, which throws only from the build info that `compatibility()` reads after the open. That test asserts `index-open-failed` with open = close = 1. The former `readFails` test is renamed to say it covers a read during the open. - **Opus R2:** a moving index refuses with `retryable: true` only when the loaded provider has the pinned version. With a version mismatch it refuses `retryable: false` with `install-compatible-codegraph`, asserted in the G4 test. ## Windows EBUSY (round-2 item 5, superseded by Bun 1.4.4) Under Bun 1.4.3, an instrumented local run (not committed) of all 23 protocol tests showed: - 8 files held under the fixture root before `Bun.gc(true)`, and 0 after it; - no child process, with the daemon running in-process. The remaining Windows hold was therefore outside the test process; the rmSync retries cover it. Under 1.4.4 no GC is needed (above), and the retries stay. ## Changed or removed assertions, and their coverage **`codegraph-adapter/test/projection-codegraph-status.test.ts`** (inspect section, rewritten against an injected provider). The `projectionCodeGraphStatusIssues` describe is kept unchanged, because S2+S3 still evaluate CLI `status -j` with it. - **"unavailable CodeGraph" (nonexistent binary)** → "a provider that cannot load". The fake is a `Proxy` whose `version` getter throws `CodeGraphProviderUnavailableError`. That is the error `pinnedCodeGraphProvider()` throws when the SDK, the platform bundle, its manifest or a Decision 7 export is missing. The real loader cannot be made to fail in a running process. The Decision 7 bump guard (`read-session.test.ts`) asserts that every listed module path and export exists in the pinned bundle. - **"missing index from --version alone, never status or sync"** → "missing index from the provider version alone and opens nothing" (open count 0). - **"ready index calls only --version and status"** → "ready index from one read-only session and closes it" (open = close = 1). In addition, `read-session.test.ts` reads a real index with no holder process, and its git spy proves no `git status` or `git diff` runs. - **Per-issue `test.each`:** - not-initialized, index-incomplete, extraction, project-path, worktree-mismatch and pending → incompatible-index `test.each` (bundle version, build version, unknown build version, partial, pending refs, stale extraction, other project root), plus the pending-changes test. - `not-initialized` → `index-missing` (item 1). - **`worktree-mismatch` is dropped** for the session. `openSync(root)` never walks up to another worktree's index (ADR-0058 table), so the session sets `worktreeMismatch: null`. The evaluator's `worktree-mismatch` branch stays covered by the kept `projectionCodeGraphStatusIssues` tests. - **"binary version diagnostics do not copy raw output"** → "issue messages are the closed vocabulary, never provider output". The open-failure test also asserts the raw provider error never reaches the facts. - **"stale extraction and pending as issues"** → the stale-extraction case of the `test.each` plus the pending-changes test. - **"binary of another version"** → the bundle-version case. - **"failing or invalid status is unavailable" and "status JSON not an object"** → no status JSON is parsed any more. Replaced by "an open that fails" and "a read that fails after the open", both `index-open-failed`. - **"always passes CODEGRAPH_NO_DOWNLOAD to --version and status"** → removed, because readiness starts no process at all: no environment to pass. `code-facts-not-ready.test.ts` asserts an empty process log for every readiness path. The remaining CLI read, the import `query` until S2, still passes `CODEGRAPH_NO_DOWNLOAD=1`, and `architecture-debt-code-facts.test.ts` still asserts it (`query=1`). - **"packaged shim with no platform bundle does not download"** → removed, because readiness no longer runs the npm shim. The SDK loader (`npm-sdk.js`) never downloads ("we never trigger a network download from inside require()"); it only reuses an installed or cached bundle. A missing bundle surfaces as `CodeGraphProviderUnavailableError` → `codegraph-unavailable`, which the Proxy test covers. The shim still builds the index (`init`, `sync`), and that is unchanged. **`codegraph-adapter/test/build-lock.test.ts`.** Live, stale and malformed lock, ported from the CLI call log to provider open counts: a live lock never opens the index, a stale or malformed one opens and closes it once. **`codegraph-adapter/test/codegraph-adapter.test.ts`.** The two shim tests inject `readProvider`; their `sync` and `query` assertions are unchanged. **`surfaces/cli/test/code-facts-not-ready.test.ts`** - Process-log expectations change from `--version\nstatus\n` to empty: no readiness process. - The checkpoint and debt gate's issues are compatibility-only. - "present uninitialized index" → "`.codegraph` without a database is `index-missing`" (item 1). - The projection-snapshot gate injects `readProvider`. - The real incomplete index (`initSync`) still yields version-mismatch plus index-incomplete. **`surfaces/cli/test/doctor-code-facts.test.ts`.** An empty `.codegraph` is now `index-missing` with `indexPresent: false` (item 1). Doctor `ok` is unchanged. **`runtime-daemon/test/architecture-debt-code-facts.test.ts`.** Readiness comes from the injected provider, which opens and closes twice around the reads. The process log keeps only `query=1` (S2), and a missing or stale index starts nothing. **`runtime-daemon/test/flow-selector-check.test.ts`, `surfaces/cli/test/ref-impact.test.ts`.** Their subprocess runs the dev launcher `bin/archctx` instead of `src/main.ts`. The snapshot's readiness now reads in-process (item 3), so on darwin the CLI process needs the same SQLite library as the test process. The assertions are unchanged. **`surfaces/cli/test/projection-protocol-lifecycle.test.ts`.** Cleanup uses rmSync retries on win32; no assertion changed. ## New tests - `read-session.test.ts`: - each open state, the dangling link, and the coherent-read case (an injected revision change between reads gives not-ready and a retryable refusal); - compatibility/revision, freshness counts and memoization, an unreadable source, and close-on-return/throw with refusal after close; - a real index read in-process with no holder: ready; edit, add and delete counted; an oversize file fresh; `.git/index` mtime and the database sha256 unchanged; the git spy; - the Decision 7 bump guard. - `scripts/codegraph-selector-node-integration.mjs` (`verify:pre`, all three OSes) runs the read session under Node with no holder: compatibility and freshness, pending counts, the compatibility-only gate, the git spy, and `.git/index` and the database unchanged. A live-writer case did not fit this script cheaply and is not added; the injected revision test covers G4. **Test budget** (`git diff --numstat origin/main`, head 8649579) - Product (`src`, `bin`, `main.ts`, contracts): +556 / −144. - Config: `bunfig.toml` +1 / −1. `bun.lock` unchanged. - Tests: +656 / −302, including the 109-line fixture. - Script: +74 / −2. ## Verification (Bun 1.4.4) - `bun install --frozen-lockfile`: accepted. - `bun run typecheck`: exit 0. - Head 9db4488: `bun test --timeout 120000` over codegraph-adapter, code-facts-not-ready, doctor-code-facts, architecture-debt-code-facts, flow-selector-check, ref-impact, complete-architecture-debt, projection-protocol-lifecycle, docs-anchors, local-product-e2e and the contracts tests: 699 pass, 0 fail. - Head 8649579: `bun run typecheck` exit 0. codegraph-adapter, code-facts-not-ready, doctor-code-facts, architecture-debt-code-facts, flow-selector-check, ref-impact and complete-architecture-debt: 162 pass, 0 fail. - `bun run test:codegraph-selector-node`: verified. - `node scripts/packaged-cli-smoke.mjs`: OK. - `node scripts/package-boundary-audit.mjs`: passed. ## Risk - On macOS, the Bun dev and test runtime needs a Homebrew SQLite (the error says how to install it). The product runtime, Node, is unchanged. - The pinned provider-internal modules break loudly on a provider bump, through the bump guard. - A `.codegraph` without a database moves from not-ready to `index-missing`, the same refusal as no index at all. ## Rollback Revert this PR. There is no data migration; the WAL sidecar files are managed by SQLite.
Closes #576.
Goal
Ledger read mode (
ARCHCONTEXT_LEDGER_READ_MODE=ledger) validates a model the same way YAML validation does. This PR implements the joint decision in 576#issuecomment-6851382166. There is one model validator over a set of model files, and both read modes call it. The ledger validator is removed, not patched up to parity, and there is no compatibility path.Change
packages/local-runtime/model-store-yaml/src/index.tsaddsvalidateModelFiles(files: ModelFile[]): ModelValidationResult. It holds the body of the formerYamlModelStore.validateModel, moved out of the class with the same checks in the same order: required files, per-file node JSON schema, theschemaVersionchecks, the flow version, runtime topology, state machines (including the state machines: a YAML file nested under state-machines/ is silently skipped by validate and the loader #571state-machine-path-nestederror), ADRappliesTo, dependency constraints, review policy, warnings andmodelDigest.YamlModelStore.validateModel(workspace)is nowvalidateModelFiles(listModelFiles(workspace.root)).readFileSync.listModelFilesincludes.archcontext/manifest.yamland.archcontext/product.yamlexactly when they exist as files, so YAML results do not change. The ledger validator already used the set check.packages/local-runtime/runtime-daemon/src/index.ts,ArchitectureLedgerReadModelStore.validateModel, now returns{ ...validateModelFiles(readback.files), architectureLedger: {…} }.readback.filesis the ledger projection of nodes, relations and constraints, merged with the Git files the ledger does not own. This is the same file set as before.validateLedgerModelFiles, the ledger-only subset validator. Also removed:modelDigestForFiles, which repeated the validator'smodelDigestcalculation, and the fourarchitecture-domainimports that only the removed validator used.validateModelFilesorders its own input withorderModelFiles(exported frommodel-store-yaml): by path, in UTF-16 code unit order.collectArchContextFilesuses the same comparator,compareModelPaths, solistModelFilesproduces the same order as before (default.sort()). Error order andmodelDigesttherefore never depend on the caller. The ledger path used to sort its merged file set withlocaleCompare. ThatlocaleComparesort is gone; the ledger path now callsorderModelFiles. It still sorts and is not just deleted, becauseloadModelreturns the same list in ledger read mode (completeTask reads constraints and the review policy from it), and an unsorted list would leaveloadModelordered differently in the two modes.validateLedgerModelFileshad no check that applied only to ledger files. Every check it ran is in the shared validator, so nothing is added on top of it.Where the shared validator lives, and why
It lives in
model-store-yaml, next toModelFile:model-store-yaml.runtime-daemonimportslistModelFiles,YamlModelStoreandtype ModelFilefrom it.model-store-yamlalready depends on every input the validator needs: the node schema JSON,runtimeTopologyModelIssuesfrom contracts,architecture-domainandstate-machine-engine.A core home does not work.
@archcontext/core/architecture-domaincannot host it, becausestate-machine-engineimportsarchitecture-domain(parseJsonOrStableYaml) and the move would create an import cycle. No other core package ownsModelFile.Refactor stated (owner's rule)
schemaVersionof projected files with the sharedextractSchemaVersion(newly exported frommodel-store-yaml). Its duplicatedschemaVersionFromModelBodyis deleted. The validator readsfile.schemaVersionfor the missing-version, flow-version and nested-path checks, so both modes must derive it the same way. On the canonical YAML that the projection emits, the two functions return the same value.validateModelmoved out of the class intovalidateModelFiles. It was re-indented as part of the move. Its logic is unchanged.Tests
runtime-daemon/test/local-runtime.test.ts, new: "ledger read mode validates with the YAML validator: a rejected model fails the same way and a valid model passes (ledger read mode: validateModel has no parity with YAML validation (schemas, topology, state machines) #576)". It usestest.each, so each row fails on its own, with one row per branch:$.mount.target: required);target: topology-relation-target-kind-invalid);state-machine-path-nested);sub/B.yaml,sub/a.yaml). The expected order is B before a in both modes. On the round-1 head this row fails: ledger mode returned a before B.For each row, the test first validates with
YamlModelStoreand asserts the expected errors. It then builds the ledger state from those files withplanYamlToArchitectureLedgerImportand deletesnodes/andrelations/from Git, so the ledger is their only source. Next it validates through aledger-authoritativedaemon and assertsreadAuthority: "ledger", the entity count, and{ valid, errors }equal to the YAML result. It also asserts that each mode'smodelDigestis the digest of its own file set in path order. The test oracle builds that order with a default.sort()on the paths, not withorderModelFiles. The two modes have different digests by design, because their per-file digests come from different bodies; the existing test "ledger-authoritative runtime read surfaces …" asserts that they differ. Against theorigin/mainsource, the schema-violation row fails: its ledger result isvalid: true, errors: [].model-store-yaml/test/validate-model.test.ts, new:validateModelFilesgets the same files in reverselocaleCompareorder and returns an identical result: the same errors, in the same order (nodes/B.yamlfirst), and the samemodelDigest. On the round-1 head this test fails.model-store-yaml/test/validate-model.test.ts, new: required-files branch. A model withoutproduct.yamlfails withmissing .archcontext/product.yamland amissing-filediagnostic. This branch had no YAML-level test before.Changed existing test:
runtime-daemon/test/ledger-layer-validation.test.ts.validateModelFiles, becausevalidateLedgerModelFilesis deleted.{ schemaVersion, id, layer }, which fails the node schema. The old ledger validator did not check the node schema; that gap is this issue's defect. The fixture now carries the requiredkind,name,statusandsummary.!on the now-optionallayerOrderErrors.Verification
Head
5c3670ae1054f2ecadf3b8c0334d6ab5768bbdb7(round 2), macOS (darwin arm64), bun 1.4.3:bun run typecheckbun test packages/local-runtime/model-store-yaml packages/core/changeset-engine packages/core/context-compilervalidateModel)bun test packages/local-runtime/runtime-daemonbun test tests/Test budget (
git diff --numstat origin/main...HEAD): test +115/−8, product +99/−139, no new test file. The product count includes the moved, re-indented validator body.test.eachparity test (about 0.9s in total) and 2 single tests.Architecture slice unchanged, see #576. No
.archcontext/model/**or capability entrypoint changes.Risk
ledgeris not the default read authority in 0.14.x, because YAML is the only authority.nodeSchemaErrors. The YAML results already did.changeSetBaseModelBlockingErrorsconsumes this field the same way in both modes.Rollback
Revert this PR. The change touches no stored data and no schema.
Out of scope (possible follow-ups)
runtime-daemon/src/index.tshas unused imports that existed before this PR (errorEnvelope,auditConsentRequiredEnvelope). They were left as they are.