fabro/.chisel/calibration/work/validation-3.md
2026-07-27 11:58:54 -04:00

447 lines
25 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Chisel calibration validation 3
Revision reviewed: `6bb6b5efcc0e36b52e3c097f532d9f2c00914c6c`.
This is an independent reading of the final rubric and the assigned component
scopes. I traced representative production entry points and direct boundary
callers. I did not inspect prior calibration scores or any other file in
`.chisel/calibration/work/`.
## Score summary
| 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 | 3 | Medium |
| `fabro-web-app` | ownership-boundaries | 4 | Medium |
| `repository-ci` | ownership-boundaries | 4 | Medium |
| `repository-ci` | domain-model | 2 | High |
| `fabro-checkpoint` | ownership-boundaries | 2 | High |
| `fabro-checkpoint` | simplicity | 3 | Medium |
| `fabro-checkpoint` | domain-model | 2 | High |
| `fabro-checkpoint` | duplication-knowledge | 3 | Medium |
## `fabro-workflow`
### `ownership-boundaries`: 2 (High)
- **Evidence:** `src/lifecycle/mod.rs:53-80,221-469` provides a real central
`WorkflowLifecycle` and explicitly orders focused event, hook, fidelity, Git,
artifact, status, and circuit-breaker delegates. Its terminal callback,
however, only forwards `on_run_end` to the hook. Normal terminal persistence,
metadata completion, terminal event emission, and sandbox stopping instead
live in `src/pipeline/finalize.rs:524-635`. Bootstrap and execution failures
take another terminal path in `src/operations/start.rs:176-345`, while
`RunSession::run` also installs cleanup and drain guards at
`src/operations/start.rs:889-947`. A routine terminal-lifecycle change must
therefore coordinate the lifecycle orchestrator, finalizer, and detached
failure/guard paths.
- **Strongest counterevidence:** The normal phase sequence is plainly owned by
`RunSession::run` (`initialize -> execute -> finalize -> pull_request`), and
callback ordering inside graph execution has one obvious owner,
`WorkflowLifecycle`.
- **Why 3 does not fit:** Terminal completion, failure, persistence, and cleanup
are common paths, not isolated edge compatibility. The split therefore
remains central even though each individual phase is understandable.
- **Why 1 does not fit:** Stable phase owners and a stable dependency direction
are readily identifiable; the problem is coordination among them, not the
absence of ownership.
- **Rule discrimination:** The repository example correctly requires terminal
inspection and rule 1 makes the common terminal split score-capping. Decision
rule 2 is less literal here because no single identity is resupplied across
every split, but the score does not depend on that rule.
### `domain-model`: 2 (High)
- **Evidence:** `src/lifecycle/event.rs:319-390` starts with the typed
`StageOutcome` on an `Outcome`, serializes it with
`outcome.status.to_string()`, and stores the result in the
`Event::StageCompleted.status: String` field declared at
`src/event/events.rs:264-293`. Every successful stage then passes through
`src/event/convert.rs:14-24,309-348`, which reparses the string and silently
converts an unknown value into a non-retryable failure. This is the ordinary
durable-event path, not an import-only compatibility path.
- **Strongest counterevidence:** The destination event model already has the
canonical `fabro_types::StageOutcome`, parallel-branch completion carries it
directly, and other core run concepts use typed IDs, reasons, timings, and an
opaque `ResumeState` (`src/pipeline/types.rs:252-285`).
- **Why 3 does not fit:** The invalid intermediate occurs for each ordinary
successful stage before durable interpretation, so it is central rather than
an isolated escape hatch.
- **Why 1 does not fit:** `StageOutcome` itself has a stable, typed meaning; the
defect is the recurring string round trip between two typed points.
- **Rule discrimination:** Decision rule 4 is directly discriminating here:
this is exactly a common-path invalid intermediate.
## `fabro-http`
### `domain-model`: 4 (High)
- **Evidence:** `src/lib.rs:23-61` gives proxy behavior a closed
`ProxyPolicy::{System, Disabled}` vocabulary. The environment boundary
accepts case-insensitive valid names, rejects every other value with a typed
`HttpClientBuildError`, handles non-Unicode values explicitly, gives explicit
policy precedence over the environment, and resolves absence to `System`.
Both generated builders invoke this resolver before constructing a client
(`src/lib.rs:72-193`), and the test-client entry points select
`ProxyPolicy::Disabled` rather than passing an unchecked string
(`src/lib.rs:195-213`).
- **Strongest counterevidence:** The facade deliberately exposes reqwest's
lower-level `Proxy` and `.no_proxy()` operations, so callers can compose
transport details outside the two-value environment policy.
- **Why 3 does not fit:** Those operations are typed builder choices, not
unvalidated representations of the `FABRO_HTTP_PROXY_POLICY` value. Every
common construction path still validates that boundary before use; I found no
material meaning or validation friction.
- **Why 1-2 do not fit:** There is one stable meaning, one resolver, and no
recurring conversion through an invalid intermediate.
- **Rule discrimination:** Decision rule 4 could be read ambiguously if every
low-level builder method is called a policy escape hatch. The rubric's own
`ProxyPolicy` example resolves that ambiguity in favor of the closed,
validated environment-policy model.
### `duplication-knowledge`: 3 (Medium)
- **Evidence:** `define_builder!` at `src/lib.rs:72-193` is one authoritative
production mechanism for the shared async/blocking builder surface and for
applying the resolved proxy policy. The four convenience constructors route
through those builders. The remaining repeated knowledge is narrow:
`"system"` and `"disabled"` appear both in the parser and in the manually
maintained `InvalidProxyPolicy` expectation text
(`src/lib.rs:29-35,63-69`).
- **Strongest counterevidence:** The macro removes the materially risky
async/blocking synchronization, and the compiler forces the policy-application
match to cover every enum variant. The two test helpers' use of
`ProxyPolicy::Disabled` is ordinary reuse, not a second policy authority.
- **Why 4 does not fit:** The user-facing valid-value list is a small second
representation that can drift from the parser, so there is some isolated
repeated domain knowledge.
- **Why 2 does not fit:** There is no direct evidence that routine changes
repeatedly synchronize separate async/blocking implementations. A future
enum variant is hypothetical, and rule 5 specifically says exhaustive
compiler-checked branches and hypothetical variants do not establish
competing authorities.
- **Rule discrimination:** Rule 5 cleanly rules out 2 but is non-discriminating
between 3 and 4 for a duplicated allowed-value error message. I treat that
message as real but isolated maintenance friction, hence 3.
## `fabro-web-app`
### `ownership-boundaries`: 4 (Medium)
- **Evidence:** `app/entry.tsx:17-48` owns root creation, global SWR policy,
build-version guarding, toast mounting, and the single normal/install router
choice. `app/router.tsx:97-184` owns normal route composition, while
`app/install-router.tsx:6-22` owns the install graph. Shared HTTP translation
and unauthorized handling live in `app/lib/api-client.ts:213-309`; shared
reads such as `useRun` live in `app/lib/queries.ts:182-187`; recurring run
mutations and cache follow-up live in
`app/lib/mutations.ts:65-149`. Route components compose these owners.
Separately, `scripts/build.ts:183-249,289-368` contains the complete
app-local build, atomic publication, and old-build pruning lifecycle and
publishes only `apps/fabro-web/dist`; boundary tooling mirrors that output
into the Rust SPA rather than the web build writing across the boundary.
- **Strongest counterevidence:** Some route-specific CRUD mutations import
`apiData` and generated API objects directly, and the install feature spans
`install-app.tsx`, `install-api.ts`, `install-query.ts`, and effect hooks.
`run-detail.tsx` is also a busy composition point.
- **Why 3 does not fit:** The direct calls remain at the route-specific UX
owner and still use the shared transport/error boundary; shared read and
recurring run-lifecycle responsibilities are not reimplemented there.
Install state, transport, query, and browser effects have distinct homes.
I found no isolated lifecycle that must leave its owner and resupply identity.
- **Why 1-2 do not fit:** Runtime, routing, transport, queries, route UX, and
build publication all have stable owners with dependencies pointing from
composition toward shared services.
- **Rule discrimination:** The final repository example is useful and
discriminating: a large route is not by itself boundary leakage. The score
would change if direct routes reimplemented shared transport or cache
lifecycles, but representative boundary checks did not show that.
## `repository-ci`
### `ownership-boundaries`: 4 (Medium)
- **Evidence:** `.github/workflows/rust.yml:48-147` owns Rust formatting,
lint/architecture checks, generated docs, Linux tests, twin E2E selection,
and manual macOS tests. `.github/workflows/typescript.yml:36-77` owns web and
generated-client typechecks, web tests, and the production embedded-SPA
integration build. Each workflow owns its concurrency and least-privilege job
permissions. The TypeScript workflow's `cargo dev build` is the intentional
integration boundary that consumes the web bundle; it does not create a
competing implementation of the web build.
- **Strongest counterevidence:** The TypeScript build job invokes Rust build
tooling, path scopes overlap around `lib/apps/fabro-spa/**`, and
`.github/zizmor.yml` is configuration whose consumer is not shown in these
files.
- **Why 3 does not fit:** Cross-language integration is part of the mapped CI
purpose and has one concrete home. The stale configuration values discussed
below are domain-model findings, while duplicated push/pull selectors are
duplication findings; counting either again as ownership friction would
violate the rubric's primary-lens rule.
- **Why 1-2 do not fit:** The Rust and TypeScript responsibilities and their
dependency direction are stable. Routine validation changes have an obvious
workflow owner rather than requiring competing lifecycle owners.
- **Rule discrimination:** The instruction not to penalize an unevidenced
missing lifecycle matters for the unseen zizmor consumer. The rubric is
otherwise discriminating once repeated selector policy is kept out of the
ownership lens.
### `domain-model`: 2 (High)
- **Evidence:** Both Rust trigger selectors name `openapi/**`
(`.github/workflows/rust.yml:6-19,22-35`), but that revision has no tracked
`openapi/` target. The actual Rust generator and TypeScript generator consume
`docs/public/api-reference/fabro-api.yaml`
(`lib/foundation/fabro-api/build.rs:159` and
`lib/packages/fabro-api-client/package.json:7`), a path omitted from both
workflow trigger models. This makes a core API-spec change invisible to the
intended CI trigger. In addition, all three
`.github/zizmor.yml:4-6` line selectors target
`.github/workflows/rust.yml` lines 37, 49, and 62, which are respectively
`workflow_dispatch`, the `fmt` job key, and a shell `run`, not action
references for `stale-action-refs`.
- **Strongest counterevidence:** Most configured branches, paths, action SHAs,
runner labels, job names, and commands have clear current targets, and both
workflow documents have a stable overall schema.
- **Why 3 does not fit:** The dead OpenAPI selector sits in both central Rust
push and pull-request triggers and omits the actual source of truth. It is not
merely an isolated stale lint suppression.
- **Why 1 does not fit:** The CI configuration language and almost all values
remain interpretable; the problem is recurring invalid/no-target identifiers,
not the absence of a stable configuration model.
- **Rule discrimination:** Decision rule 6 correctly classifies the no-target
identifiers as domain pressure, but it does not itself distinguish 2 from 3.
The centrality of the API source-of-truth trigger is what selects 2.
## Control: `fabro-checkpoint`
### `ownership-boundaries`: 2 (High)
- **Evidence:** Inside the component, `BranchStore` owns a branch string and
author and delegates Git objects to `Store`
(`src/branch.rs:17-82`), which is a sensible direction. At the production
boundary, however, no production caller constructs `BranchStore`.
`fabro-workflow/src/run_metadata.rs:272-451` instead keeps `Store`, branch,
author, `parent_oid`, and discovery state as separate fields, manually writes
blobs and trees, supplies parents to `Store::write_commit`, resupplies the
branch to `Store::update_ref`, and owns fetch/push discovery. Other checkpoint
commit and trailer lifecycle work also remains in `fabro-workflow`. Thus the
mapped checkpoint/metadata-branch lifecycle crosses the scoped owner on the
normal production path.
- **Strongest counterevidence:** `Store` is itself a mapped public entry point,
the dependency direction remains `fabro-workflow -> fabro-checkpoint`, and
remote authentication/push orchestration reasonably belongs near a workflow
run rather than in a low-level Git object store.
- **Why 3 does not fit:** The caller-held branch and parent identity are used on
every metadata snapshot, not only in an isolated migration or uncommon
fallback.
- **Why 1 does not fit:** Low-level Git ownership and the higher workflow
orchestration are both stable and understandable; they simply split one
routine persistence lifecycle.
- **Rule discrimination:** Decision rule 2 is directly discriminating:
`RunMetadataWriter` retains and repeatedly resupplies the identity needed to
complete operations on `Store`. The mapped breadth of “metadata branches”
makes this more than ordinary parameter passing.
### `simplicity`: 3 (Medium)
- **Evidence:** The production low-level path is traceable:
`Store::write_blob -> TreeEntries::set -> Store::write_tree ->
Store::write_commit -> Store::update_ref`
(`src/git.rs:123-188`). `BranchStore::write_with` also gives branch-oriented
writes one linear read/modify/write implementation
(`src/branch.rs:56-117`). The recursive flat-tree conversion is justified by
Git's nested tree representation. The friction is isolated: `BranchStore` is
a sizeable second entry layer with tests but no production caller at this
revision, and `Cargo.toml:18` declares `fabro-store` although scoped
production code does not reference it.
- **Strongest counterevidence:** The two entry points represent legitimate
abstraction levels, and the mapped cartography names both. None of the normal
`Store` operations requires navigating configuration machinery or dynamic
dispatch.
- **Why 4 does not fit:** The unused higher layer/dependency is concrete,
avoidable surface and configuration burden, even though it is off the current
production common path.
- **Why 2 does not fit:** Routine production writes do not repeatedly choose
between `Store` and `BranchStore`; the observed caller consistently uses
`Store`, and that path is direct.
- **Rule discrimination:** The “public method alone does not establish
frequency” rule prevents treating `BranchStore` as a competing common path.
It is less discriminating between 3 and 4; the concrete unused dependency and
unused entry layer are why I select 3.
### `domain-model`: 2 (High)
- **Evidence:** `GitAuthor::from_options` accepts arbitrary name/email strings
(`src/author.rs:22-30`), while `BranchStore::new` only interprets them by
calling `Signature::now(...).expect(...)`
(`src/branch.rs:26-39`). `TreeEntries` stores paths as unrestricted `String`
and `BranchStore::write_entry/write_entries` put caller strings into it
without validation (`src/git.rs:46-90`,
`src/branch.rs:84-109`); interpretation and possible rejection occur later
while rebuilding Git trees. `FileMode::from_i32` also maps every unknown Git
mode to `Blob` (`src/git.rs:21-36`) rather than preserving or rejecting an
unknown shape. These invalid-capable intermediates sit on the mapped storage
entry paths.
- **Strongest counterevidence:** `FileMode` is closed for values the component
writes, normal metadata callers validate paths before constructing
`TreeEntries`, Git itself rejects malformed signatures/trees, and object IDs
use git2's typed `Oid`.
- **Why 3 does not fit:** Raw author and path values are carried by the ordinary
entry-point types and interpreted later; they are not confined to a separate
compatibility importer.
- **Why 1 does not fit:** Authors, tree entries, modes, branches, and commits all
have stable intended meanings. The issue is delayed validation and lossy
fallback, not an unidentifiable core concept.
- **Rule discrimination:** Decision rule 4 is discriminating here: these are
common-path invalid-capable intermediate shapes rather than a low-level
escape hatch unused by the entry path.
### `duplication-knowledge`: 3 (Medium)
- **Evidence:** Important transformations are mostly authoritative:
`FileMode::{as_i32,from_i32}` contains the mode mapping,
`BranchStore::write_with` contains branch read/modify/write, and
`GitAuthor::default` contains the default identity. The narrow repeated
knowledge is the bare-branch to full-ref transformation
`format!("refs/heads/{branch}")` in each of
`Store::{update_ref,resolve_ref,delete_ref}`
(`src/git.rs:182-225`), with another full-ref rendering at the direct
workflow metadata boundary. Trailer rendering also spells
`"{}: {}"` in both `append` and `format_message`
(`src/trailer.rs:9-65`).
- **Strongest counterevidence:** The repeated ref syntax is stable low-level Git
syntax, the three ref methods implement different operations, and the
apparent duplication in single-entry/multi-entry or tip/commit reads has
intentionally different result shapes. Unifying those operations would risk
a parameterized mega-helper.
- **Why 4 does not fit:** Full-ref and trailer-line rendering have small but real
second representations rather than one helper/type enforcing each
transformation.
- **Why 2 does not fit:** There is no direct evidence of routine changes
repeatedly synchronizing those stable renderings, and hypothetical future ref
methods do not satisfy decision rule 5. The repeated knowledge is isolated
from ordinary checkpoint-format extension.
- **Rule discrimination:** Rule 5 usefully rules out 2 but is
non-discriminating between 3 and 4 for repeated, stable protocol syntax. I
score 3 because the repetitions are concrete, while keeping confidence
Medium because their maintenance materiality is limited.
## Round 2 revalidation
I independently reapplied the simplified decision rules to only the requested
assignments. Scores below supersede the corresponding Round 1 judgments for
this revalidation.
| Component | Lens | Round 2 score | Confidence |
| --- | --- | ---: | --- |
| `fabro-http` | duplication-knowledge | 3 | High |
| `repository-ci` | ownership-boundaries | 2 | High |
| `fabro-checkpoint` | ownership-boundaries | 2 | High |
| `fabro-checkpoint` | simplicity | 3 | High |
| `fabro-checkpoint` | domain-model | 2 | High |
| `fabro-checkpoint` | duplication-knowledge | 3 | Medium |
### `fabro-http` × `duplication-knowledge`: 3 (High)
- **Decisive evidence:** `define_builder!` remains the one mechanism for the
materially recurring async/blocking builder policy
(`src/lib.rs:72-193`). The parser and `InvalidProxyPolicy` message still hold
a concrete second representation of the allowed `"system"`/`"disabled"`
vocabulary (`src/lib.rs:29-35,63-69`).
- **Adjacent scores:** 4 does not fit because revised rule 6 explicitly caps a
concrete second semantic representation at 3. Score 2 does not fit because an
ordinary mapped change does not currently synchronize separate async and
blocking implementations; adding a future policy variant is not direct
recurrence evidence.
- **Remaining ambiguity:** None material. Revised rule 6 now resolves the prior
3-versus-4 uncertainty.
### `repository-ci` × `ownership-boundaries`: 2 (High)
- **Decisive evidence:** The Rust workflow's architecture check scans
`apps`, `lib/packages`, and
`docs/public/api-reference/fabro-api.yaml`
(`.github/workflows/rust.yml:80-91`), but its push and pull-request triggers
omit all three routine target paths (`rust.yml:6-19,22-35`). Its Cargo jobs
also consume the real API specification through
`lib/foundation/fabro-api/build.rs`, yet that specification does not trigger
the workflow. The TypeScript workflow likewise consumes the generated API
client and performs the embedded integration build without making the source
specification a trigger. Under revised rule 3, each check owns this coverage;
the omitted routine targets are therefore central ownership pressure.
- **Adjacent scores:** 3 does not fit because API, app, and package changes are
routine targets of checks the workflow actually runs, not isolated edge
inputs. Score 1 does not fit because Rust and TypeScript job ownership and
dependency direction otherwise remain stable.
- **Remaining ambiguity:** None material. The nonexistent `openapi/**` value is
still a separate domain-model finding; the ownership finding rests on the
real scanned/consumed paths that fail to trigger.
### `fabro-checkpoint` × `ownership-boundaries`: 2 (High)
- **Decisive evidence:** The mapped higher owner is `BranchStore`, but the
routine production metadata caller instead retains `Store`, branch, author,
parent, and discovery state and reconstructs blob/tree/commit/ref lifecycle
from `Store` primitives in
`fabro-workflow/src/run_metadata.rs:272-451`. Revised rule 2 names this shape
directly.
- **Adjacent scores:** 3 does not fit because reconstruction occurs on every
metadata snapshot, not at an isolated edge. Score 1 does not fit because the
low-level `Store` and workflow-level caller are stable, identifiable owners;
the concern is the lifecycle split between them.
- **Remaining ambiguity:** The workflow reasonably owns remote authentication,
but that does not remove its reconstruction of the mapped checkpoint and
metadata-branch persistence lifecycle.
### `fabro-checkpoint` × `simplicity`: 3 (High)
- **Decisive evidence:** The current production `Store` write sequence is
linear and direct (`src/git.rs:123-188`). `BranchStore` is a parallel mapped
entry layer with no production caller at this revision, and `Cargo.toml:18`
declares the unused production dependency `fabro-store`. Revised rule 4
classifies exactly this as isolated simplicity friction that caps 4 at 3.
- **Adjacent scores:** 4 does not fit because the parallel unused layer and
dependency are concrete. Score 2 does not fit because routine callers do not
navigate competing paths or machinery; they consistently follow the direct
`Store` path.
- **Remaining ambiguity:** None material after rule 4. `BranchStore` being a
mapped entry does not make it frequent when the boundary search finds no
production caller.
### `fabro-checkpoint` × `domain-model`: 2 (High)
- **Decisive evidence:** Mapped entry shapes accept unrestricted author and path
strings: `GitAuthor::from_options` stores raw values before
`BranchStore::new` interprets them with `Signature::now(...).expect(...)`
(`src/author.rs:22-30`, `src/branch.rs:26-39`), and
`TreeEntries`/`write_entry` carry unchecked string paths until Git-tree
construction (`src/git.rs:46-90`, `src/branch.rs:84-109`). Revised rule 5
says caller validation and a typed destination do not isolate this
invalid-capable mapped entry.
- **Adjacent scores:** 3 does not fit because the invalid-capable shapes are on
mapped entry paths, not a compatibility-only edge. Score 1 does not fit
because the intended meanings of authors, paths, modes, and commits remain
stable.
- **Remaining ambiguity:** None material. Normal callers supplying valid values
does not make the entry type canonical by construction.
### `fabro-checkpoint` × `duplication-knowledge`: 3 (Medium)
- **Decisive evidence:** Bare branch names are independently rendered as
`refs/heads/{branch}` in `Store::update_ref`, `resolve_ref`, and `delete_ref`
(`src/git.rs:182-225`), and trailer lines are independently rendered in
`trailer::append` and `format_message` (`src/trailer.rs:9-65`). These are
concrete second semantic representations, so revised rule 6 excludes 4.
- **Adjacent scores:** 4 does not fit because the second renderings are real.
Score 2 does not fit because no evidenced ordinary mapped change must
synchronize the stable Git ref or trailer syntax across those locations;
future ref operations are hypothetical, while the existing operations have
distinct behavior.
- **Remaining ambiguity:** Limited ambiguity remains over whether stable
protocol syntax is material enough to count as semantic repetition at all.
Rule 6 does not define that threshold, so confidence remains Medium; if it
counts, 3 is the rule-directed score.