Development record
Nine findings about abcd's own installer are sitting in a teaching-materials
Nine findings about abcd's own installer are sitting in a teaching-materials repository's ledger. Reported by a session working in that repository, which could see the records but not the cause.
The cause, established
Not a containment bug. The issue ledger is directories under the repository's
own ledger root (internal/core/issueschema/ledgerdirs.go), with no
root-commit-SHA keying — that keying belongs to the user-level stores
(transcripts, worktrees, voyage) and never to the in-repo ledger. So
gitutil.CheckoutRoot resolved correctly and wrote to the repository it was
standing in. The capturing session simply had that directory as its working
directory.
The store worked. The distinction matters because the two hypotheses have opposite remedies, and the reporting session could not tell them apart from where it stood: a wrong store resolution would be a containment defect in the resolver, and this is not that.
What is worth fixing anyway
abcd capture will write a finding about anything into whatever repository it is
standing in, and nothing it records has to be true of that repository.
found_at is the field that could have refused these nine. It is optional and
unvalidated: internal/core/capture/validate.go:103 lists it among
found_at, lapsed_at, details, suggested_fix, wontfix_reason, resolution, promoted_to as optional strings, and nothing checks that the path it names
exists in the tree. All nine carry it empty.
So the failure is silent at both ends. Nothing refuses the write, and afterwards nothing distinguishes a record about this repository from a record that merely landed here: the reporting repository's board now reads "open 85" and leads its recent-open list with an abcd installer defect as that repository's top open issue.
The shape of a guard
A path is checkable, and semantics are not. Do not try to judge whether a finding is "about" this repository — that is not mechanical and a gate that guesses it would be worse than none.
What IS mechanical: when --found-at names a repo-relative path, require it to
resolve in the tree. That alone would have refused all nine of the misfiled
records had they named the installer files they are about, and it costs nothing
in the ordinary case where the path is real. It also makes the empty value the
only way through, which is the right place to put a nudge rather than a refusal —
a capture with no found_at is legitimate (a conceptual finding, a process
observation) and must stay legitimate.
The weaker companion: the conceptual-location escape the field already documents ("optional repo-relative path or conceptual location") means a non-path value must stay accepted, so the check fires only on something that looks like a path and does not resolve. Getting that discrimination right is the work; refusing every non-resolving string would break the documented use.
Related
iss-2609120505141653— the same nine records assertorigin: researcher-authored, which that record read as a claim that a person invoked the verb; ruled to name the route, not who ran the command (M31).iss-2609120452071388—--sourceand--categorydo not name their members in help, and all nine carry the catch-allobservation.
Three findings from one batch of misfiled records, each a different surface failing to hold a caller to something it knows.
Acceptance
- Given a capture whose
--found-atnames a repo-relative path that does not resolve in the tree, when the verb runs, then it is refused and nothing is written. - Given a capture whose
--found-atis absent, or names a conceptual location rather than a path, when the verb runs, then it is written as it is today.
Grounds
- pursued: a capture whose found_at names a path absent from the checkout is refused with nothing written, while conceptual locations still file; TestCaptureFoundAtMustResolveInTheTree holds both sides, and a real path refused or a conceptual phrase refused would show it wrong
Frontmatter
| id | iss-2609120511058115 |
|---|---|
| type | issue |
| lifecycle | resolved |
| severity | major |
.abcd/work/issues/resolved/iss-2609120511058115-capture-writes-a-finding-into-whatever.md
open on GitHub ↗ · commit history ↗
mentions12
- iss-2609120447480903ahoy install writes .githooks/pre-commit but leaves 'git config core.hooksPath .githooks' to the user, so the hook is committed but not running on the clone that just installed it. Detection knows this (hooks_path_armed=false) yet emits no gap for it. Proposal: a user-state gap that arms the clone after checking .git/hooks has no non-sample hooks the redirect would bypass, refusing with a note when it does.
- iss-2609120447482255symlink.foreign (regular file at ~/.local/bin/abcd) is reported as unresolvable with 'resolve manually' but says nothing about what the file is. Reporting size, mtime, and whether it identifies as an abcd binary (and which version) would let the user decide safely instead of inspecting by hand. The removal then needs a second full install run; a re-detect after the user clears it in the same run would save the round-trip.
- iss-2609120447482506PATH entry written as a symlink to the plugin-root binary (no verified artefact in the cache) is fragile by abcd's own note, yet bare 'ahoy' reports zero gaps afterwards. Immediately after a plugin update the symlink still pointed at the superseded root e3696dc while the live root was eda67ccc, and install_mode read 'pinned'. Detection should surface this as a gap (entry points at a non-current plugin root / not an owned copy) rather than only in a one-time install note; better still, provision the cache during install so the owned copy is written first time.
- iss-2609120447483698ahoy install: the scan_deep (true/false) prompt sits in the same stdin stream as the y/N approval prompts, so the documented 'yes | abcd ahoy install' form feeds it 'y', which is silently treated as the default (false). Either accept y/n on boolean config prompts, skip the prompt when --yes or the flag is given, or refuse an unparseable answer loudly. Observed: user chose 'approve everything', scan.deep landed as false with no warning.
- iss-2609120447486547The three required config gaps (docs.target, oracle.backend, repo.visibility) say 'ahoy install prompts for the value', but the flags --docs-target/--oracle-backend/--visibility exist and are the only reliable way to answer in a piped run. The fix_hint should name the flag; and a --yes run that still has to prompt for a free-text config value should say so up front rather than blocking on stdin.
- iss-2609120447487070ahoy --json emits "gaps": null when there are none, not []. jq '.gaps[]' fails on the healthy case, which is the case most scripts will hit. Same for any other list field that can be empty.
- iss-2609120447489045staleness reads 'unknown' on a plain plugin install of a pinned release, every run. For a plugin user that is noise, not signal: the skill text tells the agent to report staleness so a stale binary is never silent, so 'unknown' gets relayed on every healthy render. Either resolve it to 'current' when the pinned version matches the plugin manifest, or suppress it for pinned installs.
- iss-2609120447494818capture --source and --category are closed sets but neither --help nor the refusal names the valid values: 'invalid source "session-observation"' with no list. An agent relaying a capture has to guess or fall back to the default. Every closed-set refusal should print the set, as --production-mode's help text already does.
- iss-2609120450411702Upgrade path self-blocks across a plugin update: v0.7.1 wrote ~/.local/bin/abcd as a symlink into its own plugin root (e3696dc) with the note 're-run install from a session whose hooks have provisioned the cache to upgrade it to an owned copy'. After the plugin updated to v0.8.0 (eda67ccc), that re-run classified the symlink as symlink.foreign ('expected .../eda67ccc02eb/abcd'), unresolvable, 'ahoy refuses to clobber' — so the prescribed remedy is refused by its own predecessor's write, abcd on PATH stays at v0.7.1, and install_mode reads empty. Only a manual rm and a third install produced the owned copy. Fix: a symlink whose target is inside abcd's own plugin-cache directory (any root hash) is abcd's, not foreign — adopt and replace it; or have the first install never write the plugin-root symlink fallback at all, since it is guaranteed to go stale on the next update. Related: iss-2609120447482506.
- iss-2609120452071388Reported from a downstream repository using abcd, where `abcd capture` (bare,
- iss-2609120505141653`origin: researcher-authored` states that a person invoked the verb. It is
- iss-2609231156260287abcd capture writes a record with an empty found_at silently, and that is the shape all nine misfiled installer records of iss-2609120511058115 had: the guard that closed that record refuses a path-shaped found_at absent from the checkout, which none of the nine carried, so the batch that motivated it would still file today. A capture without found_at is legitimate (a conceptual finding, a process observation) and must stay legitimate, so the remedy the record floated is a nudge rather than a refusal: when --found-at is absent, the verb says the record names no location in this checkout and so nothing ties it to the repository it is filed into (on stderr, and as a field in --json), without changing the exit code or what is written. Acceptance: given a capture with no --found-at, when the verb runs, then the record is written as today and the output says no location was named; given one with a found_at, no such line appears.