# Chisel calibration validation 2 Revision reviewed: `6bb6b5efcc0e36b52e3c097f532d9f2c00914c6c` This is an independent reading of the final rubric. I did not seek or infer earlier scores. ## Scores | Component | Lens | Score | Evidence confidence | |---|---|---:|---| | `fabro-workflow` | `ownership-boundaries` | 2 | High | | `fabro-workflow` | `domain-model` | 2 | High | | `fabro-http` | `domain-model` | 4 | High | | `fabro-http` | `duplication-knowledge` | 4 | Medium | | `fabro-web-app` | `ownership-boundaries` | 4 | Medium | | `repository-ci` | `ownership-boundaries` | 2 | High | | `repository-ci` | `domain-model` | 2 | High | | `fabro-checkpoint` | `ownership-boundaries` | 4 | Medium | | `fabro-checkpoint` | `simplicity` | 4 | Medium | | `fabro-checkpoint` | `domain-model` | 3 | Medium | | `fabro-checkpoint` | `duplication-knowledge` | 2 | Medium | ## Disputed assignments ### `fabro-workflow` × `ownership-boundaries` — 2 **Direct evidence.** `WorkflowLifecycle` is a real central owner for engine callback ordering: it contains the event, hook, fidelity, status, circuit breaker, git, and artifact delegates and orders them in every callback (`src/lifecycle/mod.rs:53-80`, `223-470`). The full run lifecycle nevertheless crosses that owner on normal paths. `WorkflowLifecycle::on_run_end` only runs the hook (`src/lifecycle/mod.rs:467-469`); `pipeline::finalize` separately builds and emits the terminal event and stops the sandbox (`src/pipeline/finalize.rs:524-635`); `RunSession::run` separately owns initialize/execute/finalize, progress flushing, steering drain, and a second sandbox cleanup guard (`src/operations/start.rs:796-953`); detached bootstrap and completion guards own additional terminal-failure paths (`src/operations/start.rs:956-1139`). A routine change to terminal ordering or cleanup must account for these owners. **Strongest counterevidence.** The split is deliberate. In particular, `finalize` documents why the terminal event must follow metadata flushing, and the scope guards cover panic/interruption paths that an async lifecycle callback cannot reliably cover. **Why adjacent scores do not fit.** Score 3 does not fit because the split is on every ordinary terminal path, not an isolated compatibility path. Score 1 does not fit because the owners and dependency direction are identifiable: `RunSession` is the outer orchestrator and `WorkflowLifecycle` consistently owns engine callbacks. **Rule discrimination.** Decision rule 2 is useful here, but “complete routine lifecycle operations” must include terminal emission and resource cleanup, not only engine callbacks. Without that reading, the positive orchestrator example could make 3 and 2 hard to distinguish. ### `fabro-workflow` × `domain-model` — 2 **Direct evidence.** The canonical execution result is the typed `StageOutcome`, re-exported in `src/outcome.rs:1-12`. The common stage-completion event instead stores `status: String` (`src/event/events.rs:264-293`). `EventLifecycle::after_node` converts the typed value to a string for every successful completion (`src/lifecycle/event.rs:319-378`), and `event_body_from_event` reparses it into `StageOutcome` (`src/event/convert.rs:309-348`). Unknown strings are silently reinterpreted as a non-retryable failure (`src/event/convert.rs:14-24`). The same string intermediate is used for synthetic terminal stages (`src/lifecycle/event.rs:183-242`). **Strongest counterevidence.** Durable `fabro_types::StageCompletedProps` is typed, and ordinary producers derive the string from a typed value rather than accepting arbitrary user text. **Why adjacent scores do not fit.** Score 3 does not fit because the conversion and invalid intermediate occur on the common event path for every completed stage. Score 1 does not fit because `StageOutcome` supplies a stable canonical meaning and most execution code uses it directly. **Rule discrimination.** Decision rule 4 and the repository example are decisive. The rule would be non-discriminating if “compatibility escape hatch” were allowed to describe the central `Event` type merely because the durable type is healthier. ### `fabro-http` × `domain-model` — 4 **Direct evidence.** `ProxyPolicy` is a closed two-variant vocabulary (`src/lib.rs:23-27`). The environment boundary parses case-insensitively and rejects every other value with a typed `HttpClientBuildError` (`src/lib.rs:29-70`). Explicit policy has a documented precedence in `resolve_with_env_value`, and both async and blocking builders resolve the policy immediately before applying it (`src/lib.rs:38-59`, `160-166`, `172-193`). The common production and test constructors all pass through those builders (`src/lib.rs:195-213`). **Strongest counterevidence.** The builders also expose the lower-level `no_proxy()` and `proxy()` methods (`src/lib.rs:96-106`), so callers can express transport configuration outside the high-level enum. **Why adjacent scores do not fit.** Score 3 does not fit because the lower-level methods are intentional reqwest-facade escape hatches; the common constructors and environment boundary do not rely on an invalid or ambiguous policy value. There is positive production enforcement rather than a test-only contract. **Rule discrimination.** Decision rule 4 discriminates well if “low-level escape hatch” is read literally. If any alternate builder method were treated as a second domain meaning, scores 3 and 4 would become difficult to distinguish for facades. ### `fabro-http` × `duplication-knowledge` — 4 **Direct evidence.** `define_builder!` is one production mechanism for all shared async/blocking builder methods and for applying proxy policy (`src/lib.rs:72-170`); the two concrete builders are declarations of that mechanism (`src/lib.rs:172-193`). `ProxyPolicy::resolve` is the single authority for explicit-versus-environment precedence (`src/lib.rs:38-59`), and the four convenience constructors delegate to the builders (`src/lib.rs:195-213`). Workspace boundary evidence reinforces this authority: `clippy.toml` disallows raw reqwest client constructors in favor of these functions/builders. **Strongest counterevidence.** The tokens `system` and `disabled` also appear in the human-readable error text, and the async/blocking test constructors repeat the choice of `ProxyPolicy::Disabled`. **Why adjacent scores do not fit.** Score 3 does not fit because the repeated tokens and two one-line convenience constructors do not form independent authorities for a recurring transformation. The macro and resolver are what enforce behavior. **Rule discrimination.** Decision rule 5 is useful but leaves a small judgment gap around repeated diagnostic vocabulary. Here that repetition is non-discriminating: adding a variant would make the exhaustive application match fail to compile, while one diagnostic sentence is not a second policy engine. This is why confidence is Medium rather than High. ### `fabro-web-app` × `ownership-boundaries` — 4 **Direct evidence.** Shared HTTP configuration, authentication redirect, and error normalization live in `app/lib/api-client.ts:64-160,213-309`. Read state and cache keys live in `app/lib/queries.ts` and `app/lib/query-keys.ts`; for example, `useRun` owns the run-detail fetch/cache lifecycle (`queries.ts:182-187`). Shared run mutations and their cache updates live in `app/lib/mutations.ts:42-208`. Run SSE connection sharing, cleanup, and cache invalidation live in `app/lib/sse.ts:42-189` and `app/lib/run-events.ts:129-308`. Browser resources with more specialized lifecycles are likewise contained: terminal WebSocket/xterm/listener cleanup is in `app/hooks/use-terminal-session.ts:62-229`, and install polling owns its timer, interval, and abort controller in `app/hooks/use-install-effects.ts:72-127`. `RunDetail` composes these owners and retains view-local state and interaction ordering (`app/routes/run-detail.tsx:79-145,148-379`). Its size does not make it the owner of transport or resource cleanup. **Strongest counterevidence.** Several feature routes perform feature-local create/edit/delete calls and SWR invalidation directly, and `RunDetail` owns the delete dialog, pending state, toast, list invalidation, and navigation (`run-detail.tsx:193-205`) rather than using a single mutation hook for that entire interaction. **Why adjacent scores do not fit.** Score 3 does not fit without a concrete isolated lifecycle that has competing owners. The direct route mutations keep their feature interaction lifecycle local and still use the shared transport; they are not evidence that ordinary reads, SSE, or browser resources leak into route composition. **Rule discrimination.** The final repository example is discriminating: “busy route” must not itself count as boundary leakage. Confidence remains Medium because the application scope is broad, although the representative read, mutation, live-update, terminal, install, and route boundaries converge. ### `repository-ci` × `ownership-boundaries` — 2 **Direct evidence.** The Rust workflow’s Clippy job owns a repository-wide “legacy auth identity removal” guard that scans `lib/apps`, `lib/components`, `lib/foundation`, `apps`, `lib/packages`, and the OpenAPI document (`.github/workflows/rust.yml:80-91`). The workflow’s path filters do not include `apps/**`, `lib/packages/**`, or `docs/public/api-reference/fabro-api.yaml` (`rust.yml:3-35`). A routine change in a scanned TypeScript/package/API path can therefore introduce a forbidden identity without starting the job that owns the guard. The policy lifecycle is placed under a narrower Rust trigger than the responsibility it claims. **Strongest counterevidence.** The primary Rust and TypeScript build/test responsibilities otherwise have clear workflow homes, read-only permissions, and stable concurrency ownership (`rust.yml:38-147`; `typescript.yml:30-77`). The TypeScript production build’s Rust step is a legitimate composition point because it builds the Rust binary with the embedded SPA. **Why adjacent scores do not fit.** Score 3 does not fit because the trigger mismatch affects ordinary changes in multiple scanned source areas, not an isolated maintenance path. Score 1 does not fit because the two main language workflows and their jobs still have stable owners and dependency direction. **Rule discrimination.** No final rule explicitly says how to classify a check whose declared scan scope exceeds its trigger scope. The ownership lens’s “complete lifecycle” language is sufficient, but an explicit trigger/target coverage rule would make 2 versus 3 less ambiguous. ### `repository-ci` × `domain-model` — 2 **Direct evidence.** Every value in `.github/zizmor.yml` is a line-addressed identifier: `rust.yml:37`, `rust.yml:49`, and `rust.yml:62` (`.github/zizmor.yml:1-6`). At this revision those lines are respectively a blank separator, the `fmt` job key, and a `run:` step—not action references. Thus none is a current target for the configured `stale-action-refs` ignores. Routine edits to `rust.yml` can change the accidental referents again without changing the selectors. **Strongest counterevidence.** The syntax still communicates an intended workflow-and-line selector, and the main workflow job/status vocabulary is otherwise stable. **Why adjacent scores do not fit.** Score 3 does not fit because all three values in the entire scoped zizmor configuration lack their intended current referent; this is not one isolated compatibility value. Score 1 does not fit because the selector format and intended concept remain identifiable even though the instances are stale. **Rule discrimination.** Decision rule 6 is decisive and correctly keeps this under domain model rather than ownership. It would not by itself distinguish 2 from 3; the fact that every configured identifier is stale and line edits make the condition recur supplies that distinction. ## Control: `fabro-checkpoint` ### `fabro-checkpoint` × `ownership-boundaries` — 4 **Direct evidence.** `git::Store` owns the `git2::Repository` and the low-level blob/tree/commit/ref operations (`src/git.rs:101-227`). `branch::BranchStore` owns branch identity, author identity, and the complete local read-modify-write lifecycle, including parent resolution, tree read, commit, and ref update (`src/branch.rs:17-82`). Author and trailer concerns are focused modules rather than state hidden in callers (`src/author.rs`; `src/trailer.rs`). Boundary evidence points in the intended direction: `fabro-workflow` depends on these primitives, while its `RunMetadataWriter` owns the additional temp repository, remote discovery, credentials, push, and degradation lifecycle. That is a higher-level owner using a lower-level delegate, not a reverse dependency. **Strongest counterevidence.** The production metadata writer uses `Store` directly and manually sequences blob, tree, commit, and ref operations (`fabro-workflow/src/run_metadata.rs:313-361`) instead of using `BranchStore`. The crate name/description can make that look like the mapped checkpoint lifecycle has escaped the component. **Why adjacent scores do not fit.** Score 3 does not fit if responsibilities are classified by their actual state: `Store` owns local Git mechanics, `BranchStore` owns local branch writes, and `RunMetadataWriter` owns remote run metadata. No concrete resource is acquired by one of those owners and released by another. **Rule discrimination.** Decision rule 2 is ambiguous for intentionally low-level facades. Passing a branch to `Store::update_ref` should not alone mean “resupplying identity” when the caller owns the higher-level remote branch lifecycle and `Store` never claimed it. If the mapped purpose is instead read as all run-checkpoint lifecycle, this assignment could become 2; that purpose boundary should be fixed before using the control for strict agreement. ### `fabro-checkpoint` × `simplicity` — 4 **Direct evidence.** The local branch write path is linear in `BranchStore::write_with`: resolve parent, read tree, apply one caller mutation, write tree, commit, update ref (`src/branch.rs:56-81`). Single-file, multi-file, and delete operations are thin delegates to that path (`src/branch.rs:84-117`). The lower-level tree conversion is one direct flat-to-nested algorithm (`src/git.rs:229-309`), and trailer formatting/parsing uses straightforward local control flow (`src/trailer.rs:9-87`). **Strongest counterevidence.** `BranchStore` has no external production caller at this revision; the actual metadata path uses the lower-level `Store` API. There is also some unused-looking surface such as `MetadataError` and generic branch read/list/log helpers. **Why adjacent scores do not fit.** Score 3 does not fit because no direct production evidence shows routine changes navigating the unused surface or competing implementations. The production `Store` call sequence is itself linear. The rubric explicitly says a public method alone does not establish frequency, so unused API breadth cannot by itself create common-path indirection. **Rule discrimination.** The score-4 requirement for a “production mechanism” is mildly ambiguous when the clearest high-level mechanism has no production caller but its lower-level mechanism does. Treating compiled non-test code as sufficient would make the rule non-discriminating; this score instead relies on the directly used `Store` path also being traceable. ### `fabro-checkpoint` × `domain-model` — 3 **Direct evidence.** The common metadata boundary validates every path before putting it into `TreeEntries` (`fabro-workflow/src/run_metadata.rs:319-336,471-481`), explicitly selects `FileMode::Blob`, and converts author strings with the fallible `git2::Signature::now` before committing (`run_metadata.rs:337-345`). Within the control, `FileMode` and `TreeEntries` give Git tree entries a stable meaning (`src/git.rs:13-99`), and Git failures stay typed (`src/error.rs:3-32`). There is nevertheless isolated model friction. `TreeEntries::set` accepts any string path with no invariant-bearing path type (`src/git.rs:59-61`); `FileMode::from_i32` maps every unrecognized Git mode to `Blob` (`src/git.rs:30-35`); `GitAuthor` has public raw string fields (`src/author.rs:6-11`); and `BranchStore` says trees grow monotonically while also exposing `delete_entry` (`src/branch.rs:17-19,111-117`). **Strongest counterevidence.** These are not merely hypothetical invalid shapes: low-level public callers can bypass the production metadata-path validation, and Git supports meaningful modes omitted by `FileMode`. **Why adjacent scores do not fit.** Score 4 does not fit because the low-level types themselves do not reject invalid paths/authors or preserve every Git mode. Score 2 does not fit because the directly traced production metadata path validates before interpretation and does not depend on the fallback `from_i32`; the friction is in lower-level escape paths and the currently unused `BranchStore`, not every common snapshot. **Rule discrimination.** Decision rule 4 is useful but ambiguous about whether a common caller validating raw values before a low-level API counts as a “common-path invalid intermediate.” The rule should distinguish an actually reparsed/ambiguous value from a raw value that has already passed one boundary check but lacks an invariant-bearing Rust type. ### `fabro-checkpoint` × `duplication-knowledge` — 2 **Direct evidence.** The branch-name-to-full-ref transformation `refs/heads/{branch}` is repeated independently in `Store::update_ref`, `Store::resolve_ref`, and `Store::delete_ref` (`src/git.rs:182-225`). The direct production boundary repeats it again in `RunMetadataWriter::full_ref` (`fabro-workflow/src/run_metadata.rs:425-439`). A routine addition or change to branch ref handling must preserve the same transformation in each location. The trailer grammar has a second, smaller recurrence: `": "` is independently formatted, parsed, and detected in `append`, `parse`, `format_message`, and `has_trailing_trailer_block` (`src/trailer.rs:11-12,28-40,45-59,68-86`). **Strongest counterevidence.** Both grammars are tiny and stable, tests cover the trailer forms, and the three Store methods currently agree. A helper could look like cosmetic deduplication rather than a material abstraction. **Why adjacent scores do not fit.** Score 3 does not fit because branch resolution/update/deletion are ordinary Store operations and direct boundary code already supplies a fourth recurrence; this is not only a hypothetical future variant. Score 1 does not fit because the repeated transformations are stable and readily identifiable even though they lack a single authority. **Rule discrimination.** Decision rule 5 is decisive only if “direct evidence of routine recurrence” includes several current operations applying the same transformation. If it instead requires historical change evidence, the final rule would be non-discriminating for a revision-only review and this assignment would move toward 3. ## Round 2 revalidation These scores supersede the corresponding Round 1 scores. ### `fabro-http` × `duplication-knowledge` — 3 (Medium) **Decisive evidence.** `ProxyPolicy::parse` is the behavioral authority for the external `system`/`disabled` vocabulary, while `HttpClientBuildError::InvalidProxyPolicy` separately enumerates those values in its diagnostic (`src/lib.rs:29-35,63-66`). The builder macro remains one authority for applying the policy to both client kinds (`src/lib.rs:72-193`). **Adjacent scores and ambiguity.** Score 4 does not fit because the diagnostic is a concrete second representation that can drift. Score 2 does not fit because proxy behavior is not independently reimplemented: the shared resolver and macro enforce it, and the two no-proxy convenience constructors are call sites rather than separate authorities (`src/lib.rs:195-213`). The remaining ambiguity is whether changing the closed proxy vocabulary is routine enough to make the diagnostic synchronization central; I treat it as isolated. ### `repository-ci` × `ownership-boundaries` — 2 (High) **Decisive evidence.** The Rust workflow's legacy-auth check scans `apps`, `lib/packages`, and `docs/public/api-reference/fabro-api.yaml` (`rust.yml:80-91`), but its push and pull-request path filters omit all three (`rust.yml:3-35`). Under decision rule 3, that check owns trigger coverage for every path it scans, so routine changes in those targets bypass its lifecycle. **Adjacent scores and ambiguity.** Score 3 does not fit because the missing triggers affect several routine source and contract paths, not an isolated edge. Score 1 does not fit because the Rust and TypeScript workflow owners and dependency direction remain stable. No material ambiguity remains under the new trigger-coverage rule. ### `fabro-checkpoint` × `ownership-boundaries` — 2 (High) **Decisive evidence.** The map assigns checkpoint commits, trees, metadata branches, authorship, and trailers to this component. The routine `RunMetadataWriter` caller reconstructs that mapped lifecycle from `Store` primitives: it writes blobs and a tree, creates the commit and author/message, updates the ref, and pushes (`fabro-workflow/src/run_metadata.rs:313-361`). Decision rule 2 therefore places ownership at 2 even though the crate dependency points toward `fabro-checkpoint`. **Adjacent scores and ambiguity.** Score 3 does not fit because this is the common metadata snapshot path, not an edge case. Score 1 does not fit because the dependency direction and the low-level `Store` role are stable, and `BranchStore::write_with` demonstrates a coherent lifecycle owner inside the crate (`src/branch.rs:56-81`). The only remaining ambiguity is how specialized the metadata commit is, but the map explicitly includes metadata branches. ### `fabro-checkpoint` × `simplicity` — 3 (High) **Decisive evidence.** `fabro-store` and `serde` are production dependencies with no source use (`Cargo.toml:16-24`), and `BranchStore` is a parallel high-level entry layer with no production caller outside this crate. Decision rule 4 makes those isolated simplicity frictions and caps 4 at 3. **Adjacent scores and ambiguity.** Score 4 does not fit because the unused production edges and parallel layer are concrete. Score 2 does not fit because the production `Store` path remains direct; normal callers do not navigate the unused dependencies or `BranchStore`. Whether `BranchStore` is retained for a future caller is ambiguous, but the unused dependencies alone sustain 3. ### `fabro-checkpoint` × `domain-model` — 2 (Medium) **Decisive evidence.** The mapped Git-tree entry accepts any `String` path through `TreeEntries::set` (`src/git.rs:44-61`), while the routine metadata writer must validate paths before constructing those entries (`fabro-workflow/src/run_metadata.rs:319-336,471-481`). Decision rule 5 says caller validation does not isolate an invalid-capable mapped entry. `FileMode::from_i32` also collapses every unrecognized mode to `Blob` (`src/git.rs:29-35`). **Adjacent scores and ambiguity.** Score 3 does not fit because raw paths cross the common write boundary. Score 1 does not fit because tree entries, modes, and authors retain stable meanings and the caller does validate its input. Confidence is Medium because a deliberately low-level Git store can reasonably leave some path constraints to higher-level schemas, although the revised rule weighs against that interpretation. ### `fabro-checkpoint` × `duplication-knowledge` — 3 (Medium) **Decisive evidence.** The `refs/heads/{branch}` transformation is repeated in three `Store` operations and once at the workflow boundary (`src/git.rs:182-225`; `fabro-workflow/src/run_metadata.rs:425-439`). Trailer formatting, parsing, and block detection also encode the `": "` convention separately (`src/trailer.rs:11-12,28-40,45-59,68-86`). These are concrete second representations, so decision rule 6 caps 4 at 3. **Adjacent scores and ambiguity.** Score 2 does not fit on the current evidence: adding a Store operation or trailer key may repeat a call-site convention, but does not require an ordinary mapped change to modify all existing locations. Score 4 does not fit because the representations are nevertheless concrete and can drift. The remaining ambiguity is whether broader trailer-syntax support would be routine maintenance; if so, its formatter/parser/detector synchronization would support 2.