Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .abcd/development/brief/06-delivery/03-out-of-scope.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ gate. That is what keeps "not hand-counted" true after the day it was written.
- `itd-77` — Relocatable user-level home
- `itd-126` — Team bibliography share/ingest: citation data travels the repo, corpora never do
- `itd-127` — Paper reconstruction from the provenance ledger
- `itd-128` — One canonical YAML scalar resolver: every decoder delegates to one exported frontmatter helper
- `itd-78` — Intent-dependency graph: what to build first, even when it is something small
- `itd-82` — Ledger drain: one verb triages the open ledger into work that ships itself and work a human must think about
- `itd-83` — The review bar fires by itself, in every repo abcd manages
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
---
id: itd-128
slug: one-canonical-yaml-scalar-resolver-frontmatter-exports-the-s
spec_id: null
kind: null
suggested_kind: null
reclassification_history: []
builds_on: []
severity: minor
---

# One canonical YAML scalar resolver: frontmatter exports the single null/bool/quote resolution helper and the capture, lint, lifeboat, and memory decoders all delegate to it, so a cross-package verdict split on any scalar axis becomes unrepresentable

## Press Release

Alice writes `impact: NULL` in an issue record and every abcd surface reads
it the same way: capture accepts what lint accepts, the release cut
diagnoses what lint diagnoses, and the lifeboat graveyard sees the same null
the memory dumper writes. There is exactly one place in the codebase that
knows what a YAML scalar means — an exported resolution helper in
`internal/core/frontmatter` covering nulls, booleans, and quoting — and the
capture, lint, lifeboat, and memory decoders all delegate to it. When Bob
extends the scalar rules (a new spelling, whitespace trimming), he edits one
function and every consumer moves together; the class of bug where two
hand-synced copies drift apart is no longer expressible.

## Why This Matters

The uppercase-null bug (iss-287) happened because a widening landed in some
predicate copies and not others; its fix (PR #294) widened two of four
independent scalar decoders and left the quoting axis split (iss-285) and
the boolean axis untouched (`memory/parseScalar` accepts true/True/TRUE
while `memory/vintage.go` tests `== "true"` exactly). Each remaining
asymmetry is a future issue of the same shape. This is the
one-canonical-primitive principle applied to scalar resolution: fix the
class, not the instance. Evidence:
`.abcd/work/reviews/2026-08-19-pr-294-null-predicate/` (F6).

## Acceptance Criteria

- Given a YAML scalar value, When any of the capture, lint, lifeboat, or
memory decoders resolves its null/bool/quoting meaning, Then the answer
comes from one exported frontmatter helper and no package-local literal
set or hand-synced copy remains (grep proves zero duplicate null/bool
tables outside the helper and its tests).
- Given the frontmatter package, When its scalar rules are widened in one
edit, Then an equivalence/consumer test suite proves every delegating
package observes the change without further edits.
- Given a record carrying a quoted null (`impact: "NULL"`), When capture
validation and record-lint both judge it, Then their verdicts agree
(closes iss-285), and the two currently-contradictory tests
(`frontmatter_test.go` vs `graveyard_abandoned_test.go`) are reconciled to
the same stance.

## Open Questions

- Where does quoting normalisation live — inside the shared helper (callers
pass raw bytes) or as a documented pre-step every caller must apply? The
helper-owns-it answer makes the drift unrepresentable; the pre-step answer
preserves callers that need the raw form (lint's "not a record handle"
message wants the original bytes).
- Does the write path (`memory/yaml.go` dumper's own null-literal list) fold
into the same helper, or is emit a separate concern?

## Audit Notes

_Empty. Populated by intent-fidelity-reviewer when intent moves to shipped/._
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
# Issue ledger ↔ forge sync — SOTA survey (2026-08-19)

Commissioned host-delegated research pass on reconciling an in-repo, file-based
issue ledger with forge-hosted issues (GitHub Issues), given two hard
constraints: (a) autonomous CI/cloud agents produce findings, (b) some users
have no forge access and must read/write the record with only a checkout.
Recency assessed against 2026-08-19. This note grounds the 2026-08-19 decision
in `../../../work/DECISIONS.md` (ledger-canonical, one-way mirror).

## Ranked findings

1. **In-repo file ledger canonical; forge issues a derived view.** [CONSENSUS]
The one team with a post-mortem-grade public rationale, CoreOS's
GitHub↔JIRA syncer, deliberately went one-way: "rather than require people
to keep up with both sources, we decided to make *one* the single source of
truth" (coreos/issue-sync, archived 2023). Fossil (the longest-lived
in-repo tracker) avoids reconciliation by having exactly one store. The
no-forge-users constraint forces the choice regardless: anything that
exists only on the forge is invisible to them. Plain committed markdown is
where the 2025-26 agent-tooling wave landed (Backlog.md: the same .md files
humans read are the agent API, "no translation layer or sync issues").

2. **One-way sync (canonical → mirror), external id written back into the
canonical record.** [EVIDENCE] Bidirectional sync has three documented
failure modes — echo/"vampire record" loops, origin ambiguity,
timestamp-only conflict detection under clock skew — and every mature
mitigation (origin tagging, idempotent upserts keyed on a stable external
id, per-field ownership) exists only to contain them. Mechanisms proven in
the survivors: git-bug bridges stamp exported operations with
`github-id`/`github-url` so re-import recognises its own exports; git-issue
(Spinellis) keeps an imports mapping directory plus a checkpoint SHA. The
file-ledger equivalent is one frontmatter field per record: a record
carrying a forge id is updated in place on the mirror, never re-created,
and a mirror event matching a known id with unchanged content is dropped.

3. **Status truth lives in the canonical store; the mirror auto-closes on
canonical resolution.** [EVIDENCE] ClusterFuzz/OSS-Fuzz is the production
precedent: fully automatic filing, triage, and closing where the tracker
issue is a projection closed when the canonical evidence resolves. Forge
triage is never synced automatically — it generates an import proposal a
human accepts by an explicit import that writes the canonical file with
provenance (forge id, actor, timestamp). A forge-side close never imported
is reopened by the next mirror pass — self-healing, and the property that
makes the topology safe.

4. **Agents enter findings as files-via-PR, validated by repo lint/CI; forge
issues are at most a fallback inbox.** [CONSENSUS, strong negative
evidence for the alternative] GitHub Advisory Database: finding records as
committed OSV files entering by reviewed PR at scale. GitHub Security Lab
Taskflow (2026-01) and Google Big Sleep gate every AI-found report on human
review; Taskflow feeds reviewer dismissal reasons back into later runs as
its cross-run suppression mechanism. The negative case is curl's tracker:
ungated agent-generated reports drove signal from ~1-in-6 to ~1-in-20/30
and forced the bounty to halt (2025-07). Practical shape: the finding
file's id is a **stable content-derived fingerprint** (ClusterFuzz crash
signatures are the model), so a repeat run produces a no-op diff and lint
enforces fingerprint uniqueness.

5. **Steal mechanisms from git-bug and git-issue; adopt neither as the
store.** [EVIDENCE on liveness; CONTESTED on viability] git-bug is the
healthiest survivor (releases through 2025-05) but stores issues as git
objects, not files — unreadable from a plain checkout. git-issue has the
best file-based external-id mapping but no maintenance signal since 2020.
Fossil and Radicle solve the problem only by replacing the VCS/forge
wholesale. BugsEverywhere and Simple Defects are dead; the post-mortem
consensus on what kills in-repo trackers: no shared schema, invisibility to
non-developers, and "90% of bug tracking is sending messages to other
people".

## Not worth adopting

- **True bidirectional sync** — enterprise-scale conflict-rule maintenance,
no payoff at this scale. [CONSENSUS]
- **git-bug as storage** — hidden git objects defeat checkout readability;
its bridge code remains the best reference implementation. [CONTESTED]
- **Fossil/Radicle migration** — correct architecture, wrong cost. [CONSENSUS]
- **CRDT/event-log issue formats** — they solve concurrent-edit merging that
one canonical repo with PR-mediated writes mostly doesn't have. [ANECDOTE]
- **Ungated autonomous filing** — curl is the controlled-ish experiment;
even the frontier labs gate on human review. [EVIDENCE]

## Sources

- <https://github.com/coreos/issue-sync>
- <https://fossil-scm.org/home/doc/trunk/www/bugtheory.wiki>
- <https://truto.one/blog/the-architects-guide-to-bi-directional-api-sync-without-infinite-loops/>
- <https://github.com/git-bug/git-bug> and
<https://github.com/git-bug/git-bug/blob/master/doc/usage/third-party.md>
- <https://github.com/dspinellis/git-issue/blob/master/README.md>
- <https://github.com/MrLesk/Backlog.md>
- <https://google.github.io/clusterfuzz/>
- <https://github.blog/security/ai-supported-vulnerability-triage-with-the-github-security-lab-taskflow-agent/>
- <https://daniel.haxx.se/blog/2025/07/14/death-by-a-thousand-slops/>
- <https://github.com/github/advisory-database/blob/main/README.md>
- <https://news.ycombinator.com/item?id=43971620> (git-bug, 2025-05)
- <https://news.ycombinator.com/item?id=48155570> (Epiq)
- <https://lwn.net/Articles/966869/> and <https://radicle.dev/>
16 changes: 16 additions & 0 deletions .abcd/work/DECISIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -1352,6 +1352,22 @@ parallel-agent merge contention bites.
warn ceiling is an absolute 2 rather than a 1% rate that permitted 9; matchesAny deleted as
dead; and the three surfaces no longer list "launched through a wrapper outside the known set"
as something an allow does not see, which is the case this change converts to a warn.
- 2026-08-19 — The `.abcd/work/issues/` ledger is the single canonical issue store; forge
issues (GitHub) are a derived, opt-in mirror surface — consistent with the host-delegated
boundary (forges are hosts). Per-field ownership: the ledger owns existence, content, and
resolution; the forge owns nothing. Sync is one-way mirror-out, the forge id written back
into the record as the echo suppressor; forge-side activity (triage labels, a close) is
never auto-synced — it generates an import proposal a human accepts via an explicit import
that writes the canonical file with provenance. A forge-side close never imported is
reopened by the next mirror pass: the mirror is self-healing by construction. Autonomous
hunts file findings as fingerprint-keyed iss-N records inside the gated PR they already
open, validated by record-lint (the armed detector) — never as forge issues. Grounded in
commissioned SOTA research (see
`../development/research/notes/2026-08-19-issue-ledger-forge-sync-sota.md`): one-way
canonical sync is the surviving pattern (coreos/issue-sync post-mortem); agent findings as
records-via-reviewed-PR is 2025-26 practice (GitHub advisory-database, ClusterFuzz
fingerprint dedup); ungated agent filing degrades the whole record (curl, 2025).
Architecture-shaping: graduate to an ADR.
- 2026-08-19 — Review agents keep their Bash grant: running the project's tests is
part of what makes their reviews evidence, and the exposure that motivated the
question is closed by the intake rule that external-contribution review runs
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
schema_version: 1
id: "iss-285"
slug: "quoted-yaml-nulls-split-capture-and-record-lint-verdicts-cap"
severity: "major"
category: "bug"
source: "impl-review"
found_during: "pr-294-review"
found_at: "internal/core/capture/parse.go"
---

Quoted YAML nulls split capture and record-lint verdicts: capture unquotes before IsNull, record-lint reads the raw value

An issue record carrying `impact: "NULL"`: capture's `decodeScalar`
(`internal/core/capture/parse.go:188`) strips the double quotes before
`validateStrict` calls `frontmatter.IsNull`, so `abcd capture resolve`
accepts the record — while record-lint's `checkIssueImpact` reads the raw
value with quotes intact, `ParseImpact` fails, and the `issue_impact_valid`
blocker fires on the record capture just accepted. The comment at
`internal/core/lint/lint.go:2397` claims this split is impossible. PR #294
widened the disagreement window from one spelling (`"null"`) to three; its
own tests pin both sides of the contradiction
(`internal/core/frontmatter/frontmatter_test.go:76` vs
`internal/core/lifeboat/graveyard_abandoned_test.go:167`). The fix is to
normalise quoting on one side, not to widen literal sets. Evidence:
`.abcd/work/reviews/2026-08-19-pr-294-null-predicate/` (F1, F2).
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
schema_version: 1
id: "iss-286"
slug: "release-derivation-diagnoses-a-null-impact-as-malformed-whil"
severity: "minor"
category: "inconsistency"
source: "impl-review"
found_during: "pr-294-review"
found_at: "internal/core/changelog/shipped.go"
---

Release derivation diagnoses a null impact as malformed while record-lint calls it missing

`newRecord` (`internal/core/changelog/shipped.go:277`) calls `ParseImpact`
with no `IsNull` gate, so for a resolved issue carrying `impact: NULL`
record-lint reports "impact must be set explicitly" (missing) while the
release cut records "invalid impact" (malformed) — two diagnoses for one
line. Verdicts never diverge (both paths reject), so this is operator-facing
message inconsistency only; an `IsNull` gate at the call site restores
parity. Evidence: `.abcd/work/reviews/2026-08-19-pr-294-null-predicate/`
(F8).
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
---
schema_version: 1
id: "iss-287"
slug: "uppercase-yaml-null-spellings-unrecognised-by-the-null-predi"
severity: "major"
category: "bug"
source: "user-observation"
found_during: "pr-294-review"
found_at: "internal/core/frontmatter/frontmatter.go"
---

Uppercase YAML null spellings unrecognised by the null predicates (reported as GitHub #290)

Retroactive capture of the bug reported as GitHub #290 so the ledger holds
the handle the CHANGELOG entry should cite: `frontmatter.IsNull` and lint's
`isNull` recognised `null` and `~` but not the YAML 1.2 core-schema
spellings `Null`/`NULL`, so records using an uppercase null were misread
(e.g. a superseded ADR's `superseded_by:` treated as a live handle). Fixed
in flight by PR #294, which widens both predicates with regression tests;
resolve this issue when that PR merges, citing its commit. Evidence:
`.abcd/work/reviews/2026-08-19-pr-294-null-predicate/` (F10).
43 changes: 43 additions & 0 deletions .abcd/work/reviews/2026-08-19-pr-294-null-predicate/00-summary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# PR #294 — uppercase YAML nulls: commissioned review summary

- **Subject**: PR #294 ("fix: IsNull resolves the uppercase YAML nulls"), an
outside contribution widening `frontmatter.IsNull` and lint's `isNull` to
the uppercase null spellings after GitHub #290.
- **Provenance**: commissioned host-delegated multi-agent code review,
2026-08-19 — 8 finder angles (7 reported; the efficiency finder stalled on a
two-comparison diff), candidates deduped, recall-biased adversarial
verification with primary-source reads. Conducted outside abcd's own
command machinery, hence recorded here per this folder's charter.

## Verdict

Sound in its stated scope: the widening itself is correct, tests pass,
gofmt/vet clean, attribution trailer correct (the `Signed-off-by` is
defensible as the repo's first outside contribution). The findings cluster
around one theme: the PR widens two **hand-synced copies** of the null
predicate while the real divergence axis — **quoting** — is untouched, and
the two most severe findings show the PR's own "the gates can never disagree"
guarantee failing for `"NULL"` (accepted by capture, blocked by record-lint),
with the PR's own tests pinning both sides of the contradiction.

Two candidate claims were refuted during verification and are **not**
findings: a lifeboat quoted-`"NULL"` "silent data loss" claim (the memory
dumper and the ADR scanner read disjoint trees, and the `status: superseded`
clause fires independently), and a DCO-trailer conventions claim (an outside
contribution is exactly the deferral endpoint the convention names).

## Ranked actions

Findings are detailed in `01-findings.md` (F1–F10, most severe first).

| Action | Findings | Route |
| --- | --- | --- |
| Request changes on PR #294 (in scope): delegate lint's `isNull` to `frontmatter.IsNull` (kills the drift class and F4's mirrored-test gap), fix the doc-comment miscount, `t.Errorf` in the spelling matrix, widen the capture agreement test, cite the ledger handle in the CHANGELOG entry | F3, F4, F7, F9, F5, F10 (cite) | PR review comment |
| Capture: quoted-null verdict split between capture and record-lint (the PR's contradiction pair F2 is the pinned evidence) | F1, F2 | issues ledger |
| Capture: release-derivation vs record-lint diagnosis skew for null impact | F8 | issues ledger |
| Capture retroactively: the GitHub #290 bug itself, so the CHANGELOG cites a ledger handle | F10 | issues ledger |
| Intent: one canonical exported scalar resolver; the four independent decoders delegate | F6 | intent draft |

Decision context recorded the same day in `../../DECISIONS.md`
(ledger-canonical issue store, one-way forge mirror), grounded in
`../../../development/research/notes/2026-08-19-issue-ledger-forge-sync-sota.md`.
Loading