local-fs comment integrity — portable mtime pinning + atomic post_comment #26
Labels
No labels
component:adapters
component:lifecycle
component:qa
component:setup
component:shared
component:worktrees
phase/accepted
phase/backlog
phase/deployed
phase/developing
phase/integrating
phase/planning
phase/qa
phase/requirements
phase/uat
priority:critical
priority:critical
priority:high
priority:high
priority:low
priority:low
priority:medium
priority:medium
type:bug
type:chore
type:docs
type:feature
type:infra
type:tech-debt
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
jbr870/devwork-skills#26
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
PREQ: local-fs comment integrity — portable mtime pinning + atomic post_comment
Created: 2026-08-13
Problem
Projects using the
local-fsforge adapter cannot trust the integrity of their comment record.The adapter pins each comment file's mtime to a millisecond timestamp — that pin is what makes
created_atmeaningful and what makes a later edit detectable (mtime > created_ms). The pinfails two coupled ways:
Portability: on a box whose coreutils are uutils (Rust rewrite),
date +%s%3Nignores the%3Nwidth and emits nine nanosecond digits; the pin then dies (touch: Unable to parse date).All four forge flow tests fail at pristine HEAD on such a box. Worse, the readiness preflight
passes there — its probe checks charset/length loosely, so the guard declares a box ready on
which the adapter cannot pin a single mtime, converting one up-front message into mid-phase
failures. (Observed: finding
F-PO-21-3-1during the #21 run; uutils coreutils 0.8.0.)Atomicity: posting a comment is several steps — pick the next number, write the file, pin
the mtime. A failure or crash between the steps leaves a visible partial comment: a record
with a malformed
created_msthat consumers ordering oncreated_mscannot read, and that canraise a false edit signal on a record nobody ever edited. (Observed: an orphaned partial
test-plan:v1in the axana dogfood run — whose trigger was itself an mtime-pin failure, i.e.failure mode 1 feeding failure mode 2.)
Users:
local-fsproject (no remote forge) — theirissue record is these files.
local-fsis the adapter the forge contract is developedagainst, so a corrupt record here poisons contract development and dogfood runs.
Current state: on GNU boxes everything works; on uutils boxes runs die mid-phase after a
green readiness check; any mid-write failure leaves corrupt records that must be found and
removed by hand.
Proposed Solution
Make
local-fscomment writes crash-safe and portable-or-loudly-unsupported:precision (uutils-style) still yields a correct millisecond pin.
local-fsreadiness probe verify capability by doing: it performs a realscratch-file mtime pin and reads it back — the same operation the adapter depends on — rather
than shape-checking tool output. A box that cannot perform the pin fails preflight loudly,
never mid-run; a probe that cannot execute at all (e.g. no writable temp location) reports
not-ready, never ready.
written and pinned; an interruption at any earlier step leaves no comment visible through the
adapter's read path, and never silently overwrites an existing comment. Prove it with a crash
test that injects faults at each step boundary.
Scope: Standard — both fix directions from #23 plus the atomicity work from #13, because the
two defects are one mechanism (the pin) and its blast radius (the partial write). Reaffirmed at
review: the observed corruption was pin-triggered, but any mid-write failure corrupts the record
the same way, and
local-fsis the adapter the contract is developed against — fix the class.User Stories
tell me at readiness time that it cannot, so that I never lose a run to mid-phase pin failures
after a green check.
issue record never contains partial, unreadable entries.
created_msor checking edit state), I wantevery visible comment correctly pinned, so that edit detection never fires on a record nobody
edited.
Acceptance Criteria
Where an AC says a comment is or is not "visible", visibility means through the adapter's
comment-read path (the forge contract's read operations). Temp/staging residue at the raw
filesystem level is permitted, provided it never affects subsequent operations (see the
clean-retry criterion).
dateemits more than millisecond fractional precision for%s%3N(uutils-style), when a comment is posted via
local-fs, then the comment is created with acorrect millisecond pin (
created_atreadable, edit detection clean) and the forge flowtests pass at pristine HEAD on that box. Tolerance rule: any tool output carrying
at-least-millisecond information normalizes to a correct pin; output carrying less
fails preflight per the readiness criterion below.
guard runs with
forge.adapter: local-fsdeclared, then the guard reports the box notready, naming the failing tool with its observed vs. expected behaviour — it never reports
ready on a box where the adapter cannot pin. If the probe itself cannot execute (e.g. no
writable temp location), the guard likewise reports not-ready — never ready.
local-fsreadiness probe runs, then it verifies capability by performing a realscratch-file mtime pin and reading it back — the same operation the adapter performs — so a
probe pass demonstrates the pin operation itself succeeding on that box.
(fault-injected at minimum: after number allocation, after a partial content write, before
the pin), when the issue's comments are subsequently read, then no partial comment is
visible — the record shows either the complete, pinned comment or nothing.
issue, then it succeeds cleanly — no residue-induced numbering anomaly, no blocked or
corrupted post.
on the same comment identity, then the existing comment is never silently overwritten —
concurrent posts either land as distinct comments or the loser fails loudly.
full), when the failure occurs, then the caller receives an explicit error — it never
proceeds believing the comment exists.
checked, then it reports unedited (no false edit signal).
pass (no regression from the hardening).
Out of Scope
date/touchremains adeclared
local-fsprerequisite, where capability is defined behaviorally — passing thereadiness probe's real pin-and-read-back — not by provenance ("GNU" vs otherwise). uutils
counts as capable once the hardening lands; a toolchain that cannot express or apply
millisecond mtimes does not, and the fix for such a box is honest detection, not dropping the
prerequisite.
manual cleanup; this feature prevents new corruption, it does not remediate old records.
created_ms/ edit-detection semantics — the mechanism stays;only its reliability changes.
tea-cli,glab-cli,gh-cli) — they do not pin mtimes.local-fswrite paths other than comment posting (issue creation, body edits) —known follow-up if the same pattern is observed there; this feature fixes the path with the
observed corruption.
(loud failure is acceptable for the losing writer), not universal concurrent success.
Dependencies
filesystem itself.
jq,git; probe-verifieddate/touchcapability forlocal-fsonly) — the fix must not add new tool prerequisites.smoke.sh,disposition-flow.sh,promotion-flow.sh,release-flow.sh) are the regression harness; the crash test joins them. The uutils behaviouris reproducible in the harness via a PATH shim that emulates
date +%s%3Nemitting fullnanosecond digits — no real uutils box required for regression coverage.
Timeline
Notes
F-PO-21-3-1on #21) and #13 (deferred backlog,dogfood retrospective 2026-08-07 F5). #23 is the trigger; #13 is the blast radius — #13's
observed failure was an mtime-pin failure.
(probe-by-doing, atomicity retained, no-silent-overwrite, pre-existing partials out of scope,
contract-level visibility + clean-retry, loud post failure, probe-error = not ready, behavioral
capability definition, AC1/AC2 precision).
no separate criterion.
Linked: this issue is parent #23 (recorded by the devwork pipeline).
Linked: this issue is parent #13 (recorded by the devwork pipeline).
Test Plan: local-fs-comment-integrity-portable-mtime-pinning-atomic-post-comment
Prerequisites
[State the scenarios need — environment specifics (paths, shim contents) are verified at
execution time, not guessed here.]
forge.adapter: local-fs, with at least one issuethat already carries at least one comment (needed for the no-overwrite scenario).
PATHshim), able to emulate:(a) a
datethat ignores fractional-width specifiers and emits full nanosecond digits(uutils-style); (b) tools that cannot express or apply millisecond mtimes at all;
(c) a toolchain whose output looks well-formed but whose mtime pin does not actually
take effect.
step boundaries), and a way to induce a write failure (e.g. an unwritable target).
Required Test Data
Test Scenarios
Scenario 1: Comment posting succeeds on a uutils-style toolchain
Acceptance criterion: "Given a box whose
dateemits more than millisecond fractionalprecision for
%s%3N(uutils-style), when a comment is posted vialocal-fs, then the commentis created with a correct millisecond pin … and the forge flow tests pass at pristine HEAD on
that box."
dateemits full nanosecond digits where millisecond precision wasrequested (uutils emulation).
timestamp of millisecond precision.
smoke,disposition-flow,promotion-flow,release-flow)at pristine HEAD in this environment.
Expected outcome: the quirky-but-capable toolchain is tolerated — posting works, timestamps
are correct, nothing downstream fails.
Scenario 2: Incapable box fails readiness loudly, naming the tool
Acceptance criterion: "Given a box whose tools cannot perform millisecond mtime pinning at
all, when the readiness guard runs with
forge.adapter: local-fsdeclared, then the guardreports the box not ready, naming the failing tool with its observed vs. expected behaviour…"
support at all).
forge.adapter: local-fs.expected.
Expected outcome: one loud up-front message instead of mid-phase failures.
Scenario 2b (edge): Probe unable to execute also reports not-ready
Acceptance criterion: same criterion, probe-error clause: "If the probe itself cannot
execute (e.g. no writable temp location), the guard likewise reports not-ready — never ready."
location for a scratch file).
forge.adapter: local-fsdeclared.Expected outcome: an inconclusive probe is never treated as a pass.
Scenario 3: Probe verifies by doing — output shape alone cannot fool it
Acceptance criterion: "Given the
local-fsreadiness probe runs, then it verifies capabilityby performing a real scratch-file mtime pin and reading it back — the same operation the adapter
performs…"
whose mtime pin does not actually take effect on the file.
forge.adapter: local-fsdeclared.operation, not by inspecting output shape.
Expected outcome: probe-pass demonstrates the pin operation itself succeeding on that box.
Scenario 4: Interrupted post leaves no visible partial comment
Acceptance criterion: "Given comment posting is interrupted at any step boundary before its
commit point (fault-injected at minimum: after number allocation, after a partial content write,
before the pin), when the issue's comments are subsequently read, then no partial comment is
visible…"
Expected outcome: at every injection point, the record shows either a complete pinned comment
or nothing at all.
Scenario 5: The next post after an interruption succeeds cleanly
Acceptance criterion: "Given a prior posting attempt was interrupted, when the next comment
is posted on the same issue, then it succeeds cleanly — no residue-induced numbering anomaly, no
blocked or corrupted post."
attributable to the earlier interruption, and the new comment's ordering is correct.
Expected outcome: an earlier crash never poisons subsequent posts.
Scenario 6: An existing comment is never silently overwritten
Acceptance criterion: "Given a comment already exists, when any posting attempt (concurrent
or retried) would land on the same comment identity, then the existing comment is never silently
overwritten — concurrent posts either land as distinct comments or the loser fails loudly."
started from the same observed state).
the other attempt reported a loud failure — never a silent replacement.
Expected outcome: no write ever silently destroys a record.
Scenario 7: A failed post reports failure to its caller
Acceptance criterion: "Given a posting attempt fails at or before its commit point (e.g.
permission denied, disk full), when the failure occurs, then the caller receives an explicit
error — it never proceeds believing the comment exists."
Expected outcome: failure is loud and leaves nothing behind — the caller can react instead of
continuing on a phantom record.
Scenario 8: A never-edited comment reports unedited
Acceptance criterion: "Given a comment that was posted successfully and never edited, when
its edit state is checked, then it reports unedited (no false edit signal)."
distinguishes the two.
Expected outcome: edit detection fires only on real edits.
Scenario 9: No regression on a GNU-coreutils box
Acceptance criterion: "Given a GNU-coreutils box, when the forge flow tests run at pristine
HEAD, then they still pass (no regression from the hardening)."
Expected outcome: the hardening changes nothing for the already-working environment.
Notes
operations); temp/staging residue at the raw filesystem level is acceptable provided
Scenario 5 holds (per the PREQ's visibility definition).
scope; no scenario covers it.
sufficient (PREQ Dependencies).
Software Requirements: local-fs-comment-integrity-portable-mtime-pinning-atomic-post-comment
Context
Projects on the
local-fsforge adapter cannot trust their comment record: the millisecond mtimepin (
created_atanchor + edit-detection signal) dies on uutils-style coreutils after a greenreadiness check, and comment posting writes directly to the final visible path, so a crash at any
step boundary leaves a visible partial comment and same-identity writers silently overwrite.
Users are operators of local-fs projects (the files ARE their issue record) and suite maintainers
(local-fs is the contract-development adapter). Constraint: helper-tier baseline only — bash ≥ 3.2,
jq,git, POSIX utilities; no new tool prerequisites.Approaches Considered
Approach A: Harden in place, per-script
Summary: Fix the clock inline in each script, add staged-write logic separately to
post_comment.shandcomment_on_pr.sh, rewrite the readiness probe inline inreadiness-check.md.Pros: No
_lib.shAPI change; smallest per-file diff.Cons: Commit protocol duplicated across two scripts (the existing duplication is exactly how
they drift); probe logic re-implemented in guard prose instead of performing the adapter's real
operation — the shape-check-vs-doing mistake can recur; readiness guard and adapter each own a copy
of capability knowledge.
Effort: Medium
Approach B: Shared primitives in
_lib.sh+ adapter-owned probe helperSummary: Normalize the ms clock once in
_lib.sh(both write and read side), factor one atomiccommit primitive used by both posting scripts, and ship a
probe_pin.shhelper that the readinessguard invokes — the probe performs the adapter's own pin+readback using the same lib functions.
Pros: One mechanism, one home; fixes the existing post/comment_on_pr duplication; readiness
delegates to the adapter (no duplicated capability knowledge); probe-by-doing is literally the
adapter's operation.
Cons:
_lib.shgrows two functions; posting scripts change shape (small migration risk,covered by existing flow tests).
Effort: Medium
Approach C: Staging-dir WAL with commit marker
Summary: Write comments under
comments/.staging/with a journal/commit marker; read pathvalidates the marker before trusting a file.
Pros: Generalizes to multi-file transactions.
Cons: Changes the read-path contract (every consumer must learn the marker); more moving parts;
no added guarantee over an atomic no-clobber link, which POSIX already provides.
Effort: High
Decision
Selected: Approach B
Rationale: The two defects share one mechanism (the pin) and one blast radius (the partial
write); a shared primitive fixes both posting paths at once and removes the duplication that let
them diverge. Probe-by-doing via the adapter's own helper makes a probe pass attest the exact
operation the adapter performs — the readiness guard keeps zero capability knowledge of its own.
Architecture
Component Overview
_lib.sh—_lfs_now_ms/_lfs_mtime_ms(normalized clock, both directions),_lfs_pin_mtime(unchanged), new_lfs_commit_comment(staged atomic post),_lfs_fault(inert fault-injection hooks).
post_comment.sh/comment_on_pr.sh— argument parsing + directory resolution only; thewrite protocol lives in the primitive.
probe_pin.sh(new) — real scratch pin + readback; the readiness guard's sole local-fscapability check.
scan_comments.sh— unchanged read path (*.mdglob, header on line 1); gains correctnessfrom the normalized
_lfs_mtime_ms.Data Flow
Posting (single commit point):
.stage-*files in the target comments dir older than the stalenessthreshold (see Key Decisions) — bounds
kill -9orphan residue.msvia normalized clock; stage body to$cdir/.stage-{pid}-{ms}(dot-prefixed →invisible to the
*.mdread glob). Fault hookafter-partial-writesits mid-write.ms. Fault hookbefore-pinsits before this step.seq= max(seqfile value, highest existingNNNN-prefix in$cdir) — the seqfile isan advisory cache; existing files are the authority. Fault hook
after-allocsits here.ln "$stage" "$cdir/{seq}-{ms}.md"— hard link, fails if target exists (no-clobber).On exists-collision: seq+1, bounded retry. On any other failure: explicit error to stderr,
nonzero exit, stage cleaned. On success: best-effort seqfile update to seq+1 (failure → stderr
warning), remove stage, emit
{comment_id}.meta.jsonupdated_atrefresh is best-effort after commit (failure → stderr warning): thecomment is already durable; failing the post over a cosmetic timestamp would report "failed"
about a record that exists and invite a duplicating retry.
Reading: unchanged —
created_msfrom filename,edited_msfrom normalized mtime readback;edited_ms > created_msis the edit signal. Ordering stays bycreated_mswithcomment_idtiebreaker (local-fs SKILL.md already mandates this; filename order is for human inspection only).
Probing (readiness):
probe_pin.sh [--dir DIR]creates a scratch file (preferring the devworkroot's filesystem over
TMPDIR— tmpfs mtime behavior can diverge from the real fs), computes anormalized ms, pins, reads back via
_lfs_mtime_ms, compares equality. Failure names the failingtool with observed vs expected output. Inability to execute at all (no writable scratch location)
is nonzero — never ready. The guard relays the message verbatim.
External Data Contracts
None — the 2.2b inventory is empty (
external-deps.json=[]). The adapter's substrate is thelocal filesystem the tests create fresh; no external system of record, no seeded datastore whose
existing contents are load-bearing.
Key Decisions
date +%s.%Ncall; split on.; validate all-digits and frac ≥ 3 digits; ms = sec×1000 + first 3 frac digits (truncation)dateinvocations; truncating zero-padded%Nyields correct ms on GNU (3 digits) and uutils (9 digits) alike; any output carrying less than ms information fails digit validation → loud die naming observed vs expected_lfs_mtime_ms=date -r FILE +%s.%Nwith the same normalizationedited_ms(19 digits) > pinnedcreated_ms(13) forges a false edit signal on every comment.date -ris GNU/BSD/uutils, not POSIX: intended coverage is the probe's readback, which exercises_lfs_mtime_msitself — a box without-rfails the probe, not a mid-run scan_lfs_require_gnu_coreutils→ behavioral check: run the normalizer once, validate a 13-digit resultln) from stage file to final name{seq}-{ms}(the filename stem). Concurrent writers that read the same seq get differentms→ distinct identities, both land — duplicate seq prefixes are permitted and are "distinct comments" per the PREQ's no-overwrite criterioncreated_ms+comment_idtiebreak, never by filename, and PO-ordinal derivation is a count — neither is disturbed by a duplicated prefix. The no-clobber link still guards the true identityNNNN-prefix), seqfile is an advisory cache (max of both wins); updated best-effort after commit.stage-*with mtime older than 15 minutes in the target comments dir; threshold is normative in SKILL.mdkill -9(and the fault hooks) skip EXIT traps, so orphans accumulate; a legitimate in-flight stage lives milliseconds-to-seconds, so 15 min is orders of magnitude beyond any live writer — GC can never delete a concurrent writer's in-flight temp. Residue younger than the threshold is contract-permitted (PREQ visibility definition)_lfs_fault <point>in the shipped primitive; inert unlessDEVWORK_LFS_FAULTnames the point, then kills the process. Points (normative, in SKILL.md):after-alloc,after-partial-write,before-pindate/touch" → "probe-verified capabledate/touch" in local-fs SKILL.md,readiness-check.md, and repo CLAUDE.md tier tablecomment_on_pr.shincluded_lfs_commit_commentTechnical Risks
date -runsupported on some exotic non-GNU toolchain_lfs_mtime_ms→ box reports not-ready up front, never mid-run.next-comment-number)Expert Review
Reviewers
fable): two blocking concerns — duplicate-seq escape past the no-clobberlink (collision keyed on full name while
msdiffers per writer, breaking the claimed seqfileself-heal) and
kill -9orphaning stage files past the EXIT trap; flaggeddate -rnon-POSIXness and undefined identity/ordering under duplicate seq.
fable): same two gaps independently (seq recovery rule undefined; GCcriterion undefined and potentially racing live writers) plus: make stage/target co-location
normative (load-bearing for
lnatomicity), spec the fault hooks in SKILL.md, note legacypartial comments remain visible. Confirmed dependency direction (guard delegating to the
adapter's probe) and tier boundaries are right.
Changes Made
identity =
{seq}-{ms}, ordering bycreated_ms+ tiebreak (already normative), duplicateprefixes allowed as distinct comments — raised independently by both reviewers.
best-effort after commit — replaces the broken "collide and retry" self-heal story.
requirement.
TMPDIR(tmpfs mtime divergence).meta.jsonrefresh, seqfile update) emit stderr warnings when they fail.date -rcoverage intent stated (probe readback is the intended detection point).Noted (not actioned)
(Architect). Not actioned by design: the PREQ explicitly rules pre-existing-partial detection/
cleanup out of scope (one-off manual remediation) — carried as out-of-scope finding F#2 on this
Phase Outcome rather than new work.
Acceptance Criteria
local-fs/test/shim-portability.sh, new) runs post→scan roundtrip + the four flow tests under a uutils-emulatingdatePATH shimdateshim; unwritable scratch dir) assertprobe_pin.shnonzero exit + message naming the tooltouch" shim (exit 0, mtime unchanged) — probe must fail via readback; healthy box — probe exits 0. Mechanical: probe sources_lib.shand calls_lfs_pin_mtime/_lfs_mtime_ms(grep)local-fs/test/crash.sh, new) setsDEVWORK_LFS_FAULTper point, then assertsscan_commentssees nothing{seq}-{ms}.md) → commit must fail loudly with the original byte-identical; concurrent-writer case → distinct comments, original untouchedlocal-fs/test/smoke.shassertscreated_ms == edited_mspost-roundtrip (also exercised under the uutils shim via AC-1)_shared/procedures/test/{smoke,disposition-flow,promotion-flow,release-flow}.shon the GNU baseline*.mdpath outside itpost_comment.shandcomment_on_pr.shcontain no redirection into$cdirpaths and both call_lfs_commit_comment; only_lib.shtouches stage/commit mechanics(No Observability & Audit rows: project
observability.mode: none— declared with reason inCLAUDE.md. No temporary scaffolding introduced. AC-10 is the SREQ-added mechanical
route-through-helper row.)
Implementation Scope
Areas
.claude/skills/local-fs/bin/_lib.sh_lfs_commit_comment,_lfs_fault.claude/skills/local-fs/bin/post_comment.sh,comment_on_pr.sh.claude/skills/local-fs/bin/probe_pin.sh.claude/skills/_shared/procedures/readiness-check.md(Step 0 local-fs block).claude/skills/local-fs/SKILL.md(post_comment steps, prerequisites, fault hooks, GC threshold, identity/ordering note)CLAUDE.md(tier table wording: behavioral capability).claude/skills/local-fs/test/crash.sh(new),local-fs/test/shim-portability.sh+ shim fixtures (new), existing flow tests untouchedFile Boundaries
_lib.shis the shared dependency — it lands first and alone. The two posting scripts, the probehelper, and the doc edits are then independent of each other (non-overlapping files). The test
suites touch only
local-fs/test/.Dependencies & Sequencing
_lib.shprimitives (clock + commit + fault hooks) — everything depends on these.probe_pin.sh; SKILL.md/CLAUDE.md/readiness-checktext.
Constraints & Non-Goals
Constraints:
jq,git, POSIX utilities — no new tool prerequisites.The
date/touchcapability prerequisite stays, redefined behaviorally (probe-verified).exit-code check only; all capability logic lives in the helper tier.
created_ms/edit-detection semantics unchanged (reliability only).scan_comments.shglob + header rule) unchanged.Non-goals (do NOT build):
prerequisite.
remediation.
tea-cli,glab-cli,gh-cli) — they do not pin mtimes.known follow-up if the pattern recurs there.
loud failure for a losing writer is acceptable.
Test Plan: local-fs-comment-integrity-portable-mtime-pinning-atomic-post-comment
Prerequisites
[State the scenarios need — environment specifics (paths, shim contents) are verified at
execution time, not guessed here.]
forge.adapter: local-fs, with at least one issuethat already carries at least one comment (needed for the no-overwrite scenario).
PATHshim), able to emulate:(a) a
datethat ignores fractional-width specifiers and emits full nanosecond digits(uutils-style); (b) tools that cannot express or apply millisecond mtimes at all;
(c) a toolchain whose output looks well-formed but whose mtime pin does not actually
take effect.
step boundaries), and a way to induce a write failure (e.g. an unwritable target).
Required Test Data
Test Scenarios
Scenario 1: Comment posting succeeds on a uutils-style toolchain
Acceptance criterion: "Given a box whose
dateemits more than millisecond fractionalprecision for
%s%3N(uutils-style), when a comment is posted vialocal-fs, then the commentis created with a correct millisecond pin … and the forge flow tests pass at pristine HEAD on
that box."
Lane: integration-covered —
local-fs/test/shim-portability.sh(new suite delivered red-firstby this slice per the PREQ's regression-harness dependency; drives the post→scan roundtrip and the
four existing flow tests under a uutils-emulating
datePATH shim)dateemits full nanosecond digits where millisecond precision wasrequested (uutils emulation).
timestamp of millisecond precision.
smoke,disposition-flow,promotion-flow,release-flow)at pristine HEAD in this environment.
Expected outcome: the quirky-but-capable toolchain is tolerated — posting works, timestamps
are correct, nothing downstream fails.
Scenario 2: Incapable box fails readiness loudly, naming the tool
Acceptance criterion: "Given a box whose tools cannot perform millisecond mtime pinning at
all, when the readiness guard runs with
forge.adapter: local-fsdeclared, then the guardreports the box not ready, naming the failing tool with its observed vs. expected behaviour…"
Lane: integration-covered —
local-fs/test/shim-portability.sh(incapable-toolchain shim;asserts
probe_pin.shexits nonzero with a message naming the tool, observed vs expected. Theguard step is model-executed markdown that relays the probe's exit/message verbatim — the suite
asserts the decision-bearing helper directly)
support at all).
forge.adapter: local-fs.expected.
Expected outcome: one loud up-front message instead of mid-phase failures.
Scenario 2b (edge): Probe unable to execute also reports not-ready
Acceptance criterion: same criterion, probe-error clause: "If the probe itself cannot
execute (e.g. no writable temp location), the guard likewise reports not-ready — never ready."
Lane: integration-covered —
local-fs/test/shim-portability.sh(unwritable-scratch case;asserts probe nonzero exit + explicit message, never a ready verdict)
location for a scratch file).
forge.adapter: local-fsdeclared.Expected outcome: an inconclusive probe is never treated as a pass.
Scenario 3: Probe verifies by doing — output shape alone cannot fool it
Acceptance criterion: "Given the
local-fsreadiness probe runs, then it verifies capabilityby performing a real scratch-file mtime pin and reading it back — the same operation the adapter
performs…"
Lane: integration-covered —
local-fs/test/shim-portability.sh(liar-touchshim that exits 0without applying the mtime — probe must fail via readback; plus the healthy-box pass case)
whose mtime pin does not actually take effect on the file.
forge.adapter: local-fsdeclared.operation, not by inspecting output shape.
Expected outcome: probe-pass demonstrates the pin operation itself succeeding on that box.
Scenario 4: Interrupted post leaves no visible partial comment
Acceptance criterion: "Given comment posting is interrupted at any step boundary before its
commit point (fault-injected at minimum: after number allocation, after a partial content write,
before the pin), when the issue's comments are subsequently read, then no partial comment is
visible…"
Lane: integration-covered —
local-fs/test/crash.sh(new suite delivered red-first by thisslice;
DEVWORK_LFS_FAULTinjection at each of the three normative boundaries, then assertsscan_commentsshows nothing)Expected outcome: at every injection point, the record shows either a complete pinned comment
or nothing at all.
Scenario 5: The next post after an interruption succeeds cleanly
Acceptance criterion: "Given a prior posting attempt was interrupted, when the next comment
is posted on the same issue, then it succeeds cleanly — no residue-induced numbering anomaly, no
blocked or corrupted post."
Lane: integration-covered —
local-fs/test/crash.sh(continues from each Scenario-4interrupted state)
attributable to the earlier interruption, and the new comment's ordering is correct.
Expected outcome: an earlier crash never poisons subsequent posts.
Scenario 6: An existing comment is never silently overwritten
Acceptance criterion: "Given a comment already exists, when any posting attempt (concurrent
or retried) would land on the same comment identity, then the existing comment is never silently
overwritten — concurrent posts either land as distinct comments or the loser fails loudly."
Lane: integration-covered —
local-fs/test/crash.sh(pre-staged exact-identity collision →loud loser, original byte-identical; concurrent-writer case → distinct comments)
started from the same observed state).
the other attempt reported a loud failure — never a silent replacement.
Expected outcome: no write ever silently destroys a record.
Scenario 7: A failed post reports failure to its caller
Acceptance criterion: "Given a posting attempt fails at or before its commit point (e.g.
permission denied, disk full), when the failure occurs, then the caller receives an explicit
error — it never proceeds believing the comment exists."
Lane: integration-covered —
local-fs/test/crash.sh(unwritable comments dir; asserts nonzeroexit, stderr message, empty scan)
Expected outcome: failure is loud and leaves nothing behind — the caller can react instead of
continuing on a phantom record.
Scenario 8: A never-edited comment reports unedited
Acceptance criterion: "Given a comment that was posted successfully and never edited, when
its edit state is checked, then it reports unedited (no false edit signal)."
Lane: integration-covered —
local-fs/test/smoke.sh(existing; assertscreated_ms == edited_mson a fresh post) and_shared/procedures/test/smoke.sh(existing;immutability-detection step covers the edited-comment cross-check)
distinguishes the two.
Expected outcome: edit detection fires only on real edits.
Scenario 9: No regression on a GNU-coreutils box
Acceptance criterion: "Given a GNU-coreutils box, when the forge flow tests run at pristine
HEAD, then they still pass (no regression from the hardening)."
Lane: integration-covered —
_shared/procedures/test/smoke.sh,disposition-flow.sh,promotion-flow.sh,release-flow.sh(all existing; the GNU-baseline regression harness)Expected outcome: the hardening changes nothing for the already-working environment.
Notes
operations); temp/staging residue at the raw filesystem level is acceptable provided
Scenario 5 holds (per the PREQ's visibility definition).
scope; no scenario covers it.
sufficient (PREQ Dependencies).
Linked: this issue is sibling #27 (recorded by the devwork pipeline).
Retroactive attribution, deliberate. This issue was promoted on 2026-08-24, but its
code went live earlier: the merge commit
b9d3cf7is an ancestor ofdev--v0.1.0, sothe feature has been serving in every release since. Linking it to
dev--v0.1.6(therelease cut by this promote run) would have recorded false provenance, so the link
names the release that actually contains the work. Only the bookkeeping was outstanding.
status: tag-only— release.sh pushes tags to thegithubremote only; the Gitea forgehas no tag to hang a Release object on, so there is no release URL to record.
nonebranch unreachable #55