Skip to content

cli-common state components: statedir resolver + hermetic test helper + tier-1 cache core#18

Merged
rianjs merged 7 commits into
mainfrom
feature/MON-5364-cli-common-state-components
May 19, 2026
Merged

cli-common state components: statedir resolver + hermetic test helper + tier-1 cache core#18
rianjs merged 7 commits into
mainfrom
feature/MON-5364-cli-common-state-components

Conversation

@rianjs
Copy link
Copy Markdown
Contributor

@rianjs rianjs commented May 19, 2026

Implements §6 step 1 ("commons-first") of docs/working-with-state.md: the shared cli-common state components so the per-unit CLI ports (later, separate tickets) have something to adopt. No CLI is ported here; no §3.2 migration matrix (per-port, no consumer yet); no INT-310 tag cut (the §5 release-train guardrail is unaffected).

What

Three decomposed commits, each green on its own:

  1. statedirScope (credential-scope config dir, §3) + Cache (per-binary cache dir, §4.1) over os.UserConfigDir/os.UserCacheDir. ConfigDir/CacheDir are side-effect free; *Ensured is the only mkdir (0700) and never re-chmods a pre-existing dir (per-port concern). LegacySource is the §5a migration-source seam — a documented behavior-free type; the resolver never enumerates/reads legacy paths.
    statedirtest — the single Hermetic helper isolating the full §3.1 7-var env set (HOME, USERPROFILE, AppData, LocalAppData, XDG_CONFIG_HOME, XDG_CACHE_HOME, XDG_DATA_HOME). HOME-only is a Windows real-dir leak.
  2. cache — directory-agnostic tier-1: Envelope[T] + Read/WriteResource[T]; atomic temp-file→chmod 0600→rename (dir 0700), temp removed on every failure branch; version-mismatch-as-miss; injected Locator that validates Root (non-empty + absolute → ErrInvalidRoot, closes the zero-value cwd-write hole), InstanceKey, and resource name (regex + .. reject → ErrInvalidName). Classify returns only Fresh|Stale|Manual; Status.String() parity for all five. Tier 2 deferred (rule-of-three).
  3. docs — one status line marking §6 step 1 delivered.

Constraints honored

  • Pure stdlibgo.mod/go.sum byte-identical (the make check tidy gate enforces this).
  • make check green: tidy clean, golangci-lint 0 issues, go test -race ./... passes including the existing credstore suite (no regression). 45 new tests.
  • No silent CLI port; no tag / release-train preemption.

Review provenance

Codex architect plan review (session 019e4019, gpt-5.5 xhigh) converged round 2 (blockers=0 majors=0 minors=0 nits=1). Round 1 caught 2 majors (zero-value Locator.Root cwd-write hole; Classify state-set overclaim) + 2 minors + 1 nit — all fixed before implementation. Full disposition on #17.

Closes #17
[MON-5364]

rianjs added 3 commits May 19, 2026 08:10
Adds the cli-common state path components (working-with-state.md §5a):

- statedir: Scope (credential-scope config dir, §3) and Cache (per-binary
  cache dir, §4.1) over os.UserConfigDir/os.UserCacheDir — no hand-rolled
  ~/.config, no %APPDATA% branch. ConfigDir/CacheDir are side-effect free;
  *Ensured is the only mkdir path (0700) and never re-chmods a pre-existing
  dir (per-port concern). LegacySource is the migration-source seam — a
  documented type with no behavior; the resolver never enumerates or reads
  legacy paths (per-port §3.2 policy stays per-CLI).
- statedirtest: the single Hermetic helper isolating the full 7-var env set
  (HOME, USERPROFILE, AppData, LocalAppData, XDG_CONFIG_HOME,
  XDG_CACHE_HOME, XDG_DATA_HOME) so os.User*Dir never resolves to a real
  dir on any OS. HOME-only is a Windows real-dir leak.

Pure stdlib; no go.mod/go.sum change.

[MON-5364]
Tier-1 cache core (working-with-state.md §5b), lifted from jtk's
envelope/freshness shapes and made directory-agnostic via an injected
Locator (receives Root, never derives it):

- Envelope[T] + ReadResource[T]/WriteResource[T]; atomic
  temp-file-in-same-dir -> chmod 0600 -> rename (dir 0700), temp removed
  on every failure branch; version-mismatch-as-miss (self-healing schema
  bump); reads never check freshness.
- Locator validates all three path inputs before composing: Root must be
  non-empty and absolute (a zero-value Locator must not write relative to
  cwd -> ErrInvalidRoot); InstanceKey and resource name pass the
  ^[A-Za-z0-9][A-Za-z0-9.-]*$ guard plus ".." rejection (ErrInvalidName).
- Classify returns only Fresh|Stale|Manual; Uninitialized is caller-derived
  (ErrCacheMiss) and Unavailable is registry-derived (tier-2) — both kept
  for parity, exercised only via String(). Age: -/Ns/Nm/Nh/Nd.

Tier 2 (registry/DAG/fetchers/refresh) deferred (rule-of-three).
Pure stdlib; no go.mod/go.sum change.

[MON-5364]
One status line on §6 step 1 recording that the cli-common state
components (statedir, statedirtest, cache) now exist. No semantic change
to the standard; no CLI ported; no INT-310 tag cut (the §5 release-train
guardrail is unaffected).

Closes #17
[MON-5364]
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 19, 2026

Major

  • statedir/resolver.go:40 only rejects / plus the current OS separator. On Unix/macOS that accepts \, which is a Windows path separator, so the shared “single path component” contract becomes OS-dependent. Reject both separators everywhere, e.g. strings.ContainsAny(v, "/\\"), and add a\\b to TestInvalidNames.

Minor

  • cache/envelope.go:80 overstates the atomic-write guarantee: a process crash can still leave a temp file, and the tests only prove no temp leak on success. The final-file atomicity is fine; either narrow the comment/PR claim to “failure branches remove temps” or add a targeted failure-path test if you want to keep the stronger verification claim.

Nit

  • docs/working-with-state.md:361 says Read/WriteResource[T]; the exported API is ReadResource[T]/WriteResource[T].
  • statedirtest/statedirtest.go:32 has the no-t.Parallel warning in the package comment, but not on Hermetic itself. Repeat it in the function doc since that is what callers will hover/read.

Everything else lines up: no Migrate engine, LegacySource is behavior-free, Locator.Root validation has cwd-untouched coverage, Classify state boundaries are correct, *Ensured does not re-chmod, no CLI is ported, no tag/release-train preemption, and go.mod/go.sum are untouched.

Address Codex PR review:
- validateComponent now rejects both / and \ on every OS (was OS-dependent:
  \ slipped through on Unix). Add a\b to the invalid-name tests
  (statedir + cache).
- Narrow the atomicWriteEnvelope doc: rename is atomic, error branches
  remove temps, but a hard crash can orphan a *.tmp (superseded by the
  next write, never read as an envelope).
- Repeat the no-t.Parallel constraint on Hermetic's own doc comment.
- docs §6: ReadResource[T]/WriteResource[T] (was Read/WriteResource[T]).

[MON-5364]
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 19, 2026

TDD coverage assessment (independent Sonnet pass)

All 48 tests pass (go test -race ./...). Overall statement coverage: 80.3% (84.1% for cache alone). The core behavioral contracts are well-exercised; the gaps below are real, not imagined.


Gap

statedir.Cache.CacheDirEnsured — 0% coverage (statedir/resolver.go:110)

CacheDirEnsured is a public, exported function with a two-branch implementation (validates name → calls os.MkdirAll). There is a test for ConfigDirEnsured (creates dir, checks 0700 perms) but no equivalent for CacheDirEnsured. Given the codebase's stated concern about per-tool cache dir isolation, the happy-path (creates dir, correct perms) and the no-create mirror (CacheDir is tested, CacheDirEnsured is not) are both absent. This is the only fully-untested exported symbol.


Weak

atomicWriteEnvelope — 55.6% coverage (cache/envelope.go:90)

The happy path and pre-validation (invalid root/name) are covered. The three error branches within the atomic write itself are not exercised:

  • tmp.Write failure (line 112–115): would require a read-only fd or a filesystem that errors on write.
  • tmp.Close failure (line 118–121): rarely injectable without a mock, but the branch exists.
  • os.Chmod failure (line 123–126): injectable on Linux by making the parent dir read-only after CreateTemp. This branch is security-relevant — if Chmod fails and the subsequent Remove also fails, a world-readable temp file may linger.

The Chmod failure path is the most practically injectable and security-relevant.

ReadResource I/O error (not-miss) path — uncovered (cache/envelope.go:54–55)

The branch at line 54–55 handles os.ReadFile returning an error that is not os.IsNotExist (e.g. permission denied). TestReadMissing covers the not-exist case; no test covers the permission-denied / generic I/O error case. This is injectable: create the file, chmod 000 it, call ReadResource. The uncovered branch means the (zero, wrapping-error) contract for this case is unverified.

ConfigDir / CacheDiros.UserConfigDir/os.UserCacheDir error branch uncovered (resolver.go:67–70, resolver.go:101–104)

Both functions have an error branch for when the stdlib helper itself fails (e.g. $HOME unset on Linux). The Hermetic helper sets all 7 vars, so this branch is never triggered. A negative test unsetting HOME and XDG_CONFIG_HOME after Hermetic would cover it. Currently at 83.3% for both functions.


Nit

TestClassify_OnlyFreshStaleManual — exact-boundary instant not tested

The Classify stale condition is now.Sub(fetchedAt) >= d (inclusive). The table covers elapsed-stale and fresh but not fetchedAt = now.Add(-1*time.Hour) (exactly at the boundary, should be stale). Not a correctness risk given the >= is clear in the source, but a boundary-value case is idiomatic for this kind of classifier.

TestUnsafeComponents — single-dot . not in the locator bad list (cache/cache_test.go:147)

statedir/resolver_test.go tests . as an invalid name for Scope/Cache, but the equivalent cache.Locator test omits . from its bad list. validComponent in locator.go does reject . via the regex (confirmed), so there is no bug — the test just doesn't verify it, unlike its counterpart.


Summary table

Severity Count
Gap 1 (CacheDirEnsured entirely untested)
Weak 3 (atomicWriteEnvelope error branches; ReadResource I/O-not-miss; os.User*Dir failure path)
Nit 2 (exact-boundary instant for Classify; single-dot omission in TestUnsafeComponents)

Verdict: Coverage is solid for happy paths and the security-sensitive validation logic; the one concrete gap is the fully-untested CacheDirEnsured export, and the most actionable weak spot is the unverified ReadResource I/O-error-but-not-miss contract.

Address independent TDD assessment:
- TestCacheDirEnsured_Creates0700 — the previously fully-untested
  CacheDirEnsured export.
- TestReadResource_IOErrorIsNotMiss — resource path is a directory, so
  os.ReadFile returns a non-NotExist error: verifies the
  "I/O error, not a miss" contract.
- Classify exact-boundary row (now-ttl == d -> stale, the >= edge).
- Add "." to the cache unsafe-component list.

[MON-5364]
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 19, 2026

Re: TDD coverage assessment (#18 (comment)) — addressed in the latest push:

Fixed

  • Gap — CacheDirEnsured 0% coverageTestCacheDirEnsured_Creates0700 added (creates dir, asserts 0700, non-Windows).
  • Weak — ReadResource I/O-error-but-not-missTestReadResource_IOErrorIsNotMiss (resource path is a directory → os.ReadFile returns a non-NotExist error; asserts non-miss).
  • Nit — exact Classify boundary → table row now-ttl == d → Stale (the >= d edge).
  • Nit — single . → added to the cache unsafe-component list.

Declined, with rationale

  • Weak — atomicWriteEnvelope Chmod/rename failure branches: deliberately not unit-tested. Forcing os.Chmod/os.Rename to fail portably + hermetically requires fragile FS fault injection (running as root defeats perm tricks; behavior diverges Linux/macOS/Windows) and would be a flaky test asserting straightforward _ = os.Remove(tmp); return wrapErr cleanup. The success path, the validation-error paths, and no-temp-leak-on-success are covered; the residual branches are linear defer-cleanup with no logic.
  • Weak — os.UserConfigDir/os.UserCacheDir error branch: only returns an error when HOME/AppData are entirely unset, which directly fights the statedirtest.Hermetic helper (whose contract is that those vars are always set). Triggering it would require bypassing the hermetic helper the rest of the suite depends on; the branch is a one-line %w wrap.

make check green (tidy + lint 0 + -race all packages incl. credstore).

@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 19, 2026

Codex re-review — post Major-fix + TDD additions

Findings

  • None.

Round-1 fixes landed cleanly in the current PR diff. The separator validation is now OS-independent and tested, atomic-write docs no longer overclaim crash cleanup, Hermetic carries the no-t.Parallel warning on the function, and the delivered-doc API names are correct.

Architectural checks also hold: LegacySource remains behavior-free, no Migrate engine appeared, cache stays tier-1 only, Classify still only returns Fresh|Stale|Manual, StatusUnavailable remains string/enum parity, *Ensured keeps the no-rechmod boundary, no CLI is ported, no release-train/tag step is preempted, and no dependency files are touched. I did not rerun tests locally; this is a diff review.

STATUS: blockers=0 majors=0 minors=0 nits=0 — converged (session 019e4019).

Copy link
Copy Markdown

@monit-reviewer monit-reviewer left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated PR Review

Reviewed commit: a517574

Approved with 6 non-blocking suggestions below. Address at your discretion.

Summary

Reviewer Findings
harness-engineering:harness-architecture-reviewer 1
harness-engineering:harness-enforcement-reviewer 2
harness-engineering:harness-knowledge-reviewer 2
harness-engineering:harness-self-documenting-code-reviewer 1
harness-engineering:harness-architecture-reviewer (1 findings)

💡 Suggestion - cache/locator.go:26

safeComponent regex rejects underscores ([A-Za-z0-9.\-] has no _). Non-hostname identifiers like my_tool or AWS account-scoped IDs would fail with ErrInvalidName — a silent footgun for future adopters. Consider adding _ to the character class.

harness-engineering:harness-enforcement-reviewer (2 findings)

💡 Suggestion - cache/envelope.go:56

Uses os.IsNotExist(err) instead of errors.Is(err, os.ErrNotExist). The legacy helper does not unwrap error chains, so a wrapped os.ErrNotExist would fall through to the generic I/O error branch instead of being treated as a cache miss.

💡 Suggestion - cache/envelope.go:68

ReadResource does not verify that env.Resource matches the requested name or that env.Instance matches loc.InstanceKey. A manually-edited or misplaced file will silently return an envelope whose metadata disagrees with the requested key. Adding a consistency guard (return ErrCacheMiss on mismatch) would prevent consumer confusion.

harness-engineering:harness-knowledge-reviewer (2 findings)

💡 Suggestion - cache/locator.go:26

safeComponent regex permits names ending with a dot (e.g. "foo."). On Windows, NTFS/FAT silently strips trailing dots, so filepath.Join(root, "foo.", "r.json") resolves identically to filepath.Join(root, "foo", "r.json"). Anchor the last character to [A-Za-z0-9] or add a !strings.HasSuffix(s, ".") guard.

💡 Suggestion - cache/cache_test.go:147

TestUnsafeComponents does not include a name with an embedded .. (e.g. "a..b"). The implementation correctly rejects it via the strings.Contains(s, "..") guard, but the test never exercises that branch. A future refactor consolidating the guard into the regex alone would go undetected. Adding "a..b" to the bad slice would pin this invariant.

harness-engineering:harness-self-documenting-code-reviewer (1 findings)

💡 Suggestion - cache/envelope.go:48

The nolint comment says "not user input", but InstanceKey and resource names can originate from user-supplied config (e.g. a hostname). The accurate rationale is that the path has already been validated by Locator.resourceFile. The current comment may lead a future reader to skip re-evaluation if new unvalidated call sites are added.

4 info-level observations excluded. Run with --verbose to include.

4 PR discussion threads considered.


Completed in 2m 32s | $0.90 | sonnet | daemon 0.2.120 | Glorfindel
Field Value
Model sonnet
Reviewers hybrid-synthesis, documentation:docs-reviewer, harness-engineering:harness-architecture-reviewer, harness-engineering:harness-enforcement-reviewer, harness-engineering:harness-knowledge-reviewer, harness-engineering:harness-self-documenting-code-reviewer, security:security-code-auditor
Engine claude · sonnet
Reviewed by pr-review-daemon · monit-pr-reviewer
Duration 2m 32s wall · 8m 01s compute (Reviewers: 1m 58s · Synthesis: 31s)
Cost $0.90
Tokens 139.6k in / 33.1k out
Turns 7

Per-workstream usage

Workstream Model In Out Cache read Cache create Cost
hybrid-synthesis sonnet 27.9k 2.0k 0 27.9k (1h) $0.14
documentation:docs-reviewer haiku 3.8k 6.1k 0 0 $0.04
harness-engineering:harness-architecture-reviewer sonnet 21.7k 3.8k 2.1k 19.6k (1h) $0.15
harness-engineering:harness-enforcement-reviewer sonnet 21.7k 5.1k 2.1k 19.6k (1h) $0.16
harness-engineering:harness-knowledge-reviewer sonnet 21.7k 7.3k 2.1k 19.6k (1h) $0.20
harness-engineering:harness-self-documenting-code-reviewer sonnet 21.3k 4.2k 2.1k 19.1k (1h) $0.15
security:security-code-auditor haiku 21.5k 4.6k 0 21.5k (1h) $0.06

Re-reviews only run when @monit-reviewer is re-requested as a reviewer — push as many commits as you need, then re-request when ready. PRs targeting branches other than main, master are skipped, even when @monit-reviewer is re-requested.

Comment thread cache/locator.go
// starting alphanumeric. Path separators, whitespace, and control characters
// are rejected rather than trusted (the values are caller-supplied — e.g. a
// hostname derived from config).
var safeComponent = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9.\-]*$`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-architecture-reviewer): safeComponent regex rejects underscores ([A-Za-z0-9.\-] has no _). Non-hostname identifiers like my_tool or AWS account-scoped IDs would fail with ErrInvalidName — a silent footgun for future adopters. Consider adding _ to the character class.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Declined: the [A-Za-z0-9.\-] set is a deliberate verbatim lift of jtk's isSafeInstanceKey (instance keys are hostnames / Atlassian cloud-ids — no underscores). Broadening it for hypothetical my_tool/AWS-account adopters is speculative and out of this ticket's scope (commons buildout, no consumer). If a real adopter needs _, that's a tracked widening with its own test, not a silent footgun introduced now.

Comment thread cache/locator.go
// starting alphanumeric. Path separators, whitespace, and control characters
// are rejected rather than trusted (the values are caller-supplied — e.g. a
// hostname derived from config).
var safeComponent = regexp.MustCompile(`^[A-Za-z0-9][A-Za-z0-9.\-]*$`)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-knowledge-reviewer): safeComponent regex permits names ending with a dot (e.g. "foo."). On Windows, NTFS/FAT silently strips trailing dots, so filepath.Join(root, "foo.", "r.json") resolves identically to filepath.Join(root, "foo", "r.json"). Anchor the last character to [A-Za-z0-9] or add a !strings.HasSuffix(s, ".") guard.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed (b9 commit): validComponent now also !strings.HasSuffix(s, "."), and statedir.validateComponent rejects a trailing dot too. Pinned by trailingdot. in both unsafe lists. Good cross-OS catch.

Comment thread cache/envelope.go
return Envelope[T]{}, ErrCacheMiss
}
return Envelope[T]{}, fmt.Errorf("cache: reading resource file: %w", err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-enforcement-reviewer): Uses os.IsNotExist(err) instead of errors.Is(err, os.ErrNotExist). The legacy helper does not unwrap error chains, so a wrapped os.ErrNotExist would fall through to the generic I/O error branch instead of being treated as a cache miss.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: os.IsNotExist(err)errors.Is(err, os.ErrNotExist) (chain-safe; future-proof if a wrapped read helper is introduced).

Comment thread cache/envelope.go
}
return env, nil
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-enforcement-reviewer): ReadResource does not verify that env.Resource matches the requested name or that env.Instance matches loc.InstanceKey. A manually-edited or misplaced file will silently return an envelope whose metadata disagrees with the requested key. Adding a consistency guard (return ErrCacheMiss on mismatch) would prevent consumer confusion.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: ReadResource now returns ErrCacheMiss when env.Resource != name or env.Instance != loc.InstanceKey, folded into the existing version-mismatch self-healing guard. New TestIdentityMismatchIsMiss covers it.

Comment thread cache/cache_test.go
}
if _, err := cache.ReadResource[payload](loc, "r"); !errors.Is(err, cache.ErrInvalidRoot) {
t.Fatalf("root=%q ReadResource err = %v, want ErrInvalidRoot", root, err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-knowledge-reviewer): TestUnsafeComponents does not include a name with an embedded .. (e.g. "a..b"). The implementation correctly rejects it via the strings.Contains(s, "..") guard, but the test never exercises that branch. A future refactor consolidating the guard into the regex alone would go undetected. Adding "a..b" to the bad slice would pin this invariant.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: a..b added to TestUnsafeComponents so the strings.Contains(s,"..") invariant is pinned independently of the regex.

Comment thread cache/envelope.go
path, err := loc.resourceFile(name)
if err != nil {
return Envelope[T]{}, err
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Low (harness-engineering:harness-self-documenting-code-reviewer): The nolint comment says "not user input", but InstanceKey and resource names can originate from user-supplied config (e.g. a hostname). The accurate rationale is that the path has already been validated by Locator.resourceFile. The current comment may lead a future reader to skip re-evaluation if new unvalidated call sites are added.

Reply to this thread when addressed.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed: nolint rationale corrected to "path already validated + composed by Locator.resourceFile" (was misleadingly "not user input" — instance keys can be config-derived).

rianjs added 2 commits May 19, 2026 08:23
…miss

Address pr-review-daemon (APPROVE) low findings worth taking:
- validComponent / statedir.validateComponent now reject a trailing dot:
  Windows strips it, so "foo." and "foo" would collide cross-OS.
- ReadResource treats env.Resource != name or env.Instance !=
  loc.InstanceKey as a miss (same self-healing rule as version mismatch):
  a misplaced/hand-edited file is never returned under the wrong key.
- os.IsNotExist -> errors.Is(err, os.ErrNotExist) (chain-safe idiom).
- nolint rationale corrected: "validated by Locator.resourceFile", not
  "not user input".
- Tests: identity-mismatch-as-miss; "a..b" and trailing-dot pinned in
  the unsafe-component lists (statedir + cache).

Declined: adding "_" to the safe set — deliberate jtk isSafeInstanceKey
parity (hostnames/cloud-ids); broadening it is out of ticket scope.

[MON-5364]
…match

Final Codex alignment nit: the public doc comments still described miss
behavior as absent-or-version-mismatch only; the contract now also covers
a stored resource/instance that disagrees with the requested key. Comment
only; behavior already shipped in the prior commit.

[MON-5364]
@rianjs
Copy link
Copy Markdown
Contributor Author

rianjs commented May 19, 2026

Final Codex alignment pass (post TDD + daemon changes) — session 019e4019

No architectural drift. Codex confirmed the late changes hold the line: still tier-1 only (no Migrate engine, no registry/DAG), LegacySource behavior-free, Classify only Fresh|Stale|Manual, the identity-mismatch path is consistent with the cache self-healing design (refuses wrong disposable data, next fetch repopulates — not a data-loss footgun), no CLI ported, no INT-310 tag/release-train preemption, go.mod/go.sum untouched.

One nit (fixed): the ReadResource/ErrCacheMiss doc comments lagged the shipped identity-mismatch behavior — public contract text now updated.

STATUS: blockers=0 majors=0 minors=0 nits=0 — converged. pr-review-daemon: APPROVE (all 6 low findings addressed in-thread; 5 taken, 1 declined with rationale).

@rianjs rianjs merged commit 91cc5d4 into main May 19, 2026
4 checks passed
@rianjs rianjs deleted the feature/MON-5364-cli-common-state-components branch May 19, 2026 12:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

cli-common state components: path/dir resolver + hermetic test helper + tier-1 cache core

2 participants