mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-10-09 03:20:56 +00:00
450 lines
24 KiB
Markdown
450 lines
24 KiB
Markdown
# 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.
|