Skip to content

fix(wiki,knowledge-flush): enforceable prohibition rule, disjoint routing scopes, flush concurrency (#36 #37 #77) - #93

Merged
choiyounggi merged 6 commits into
mainfrom
orch/issues-20260812
Aug 13, 2026
Merged

fix(wiki,knowledge-flush): enforceable prohibition rule, disjoint routing scopes, flush concurrency (#36 #37 #77)#93
choiyounggi merged 6 commits into
mainfrom
orch/issues-20260812

Conversation

@choiyounggi

Copy link
Copy Markdown
Owner

Closes #36
Closes #37
Closes #77

Three independent issues, implemented in parallel worktrees and merged locally
after review. 26 files, +1142 / -60.

#36 — wiki-lint check 2 is now mechanically enforceable

Check 2 flagged 147 prohibition phrases with zero true violations
(plans/wiki-audit/findings/structure.md), so it was permanent manual-review
noise that hid nothing.

The rule was validated against the live corpus over four iterations before any
code was written. Earlier formulations were rejected by measurement, not taste:

formulation violations reported why it was wrong
per line 138 descriptive never ("references never released")
per sentence 11 wrapped list items were split mid-item
per item 6 ## Sources citations quote prohibitions
per item, scoped 0 ships

Final rule: a prohibition clause (≥3 words, starting with
don't/do not/never/avoid/must not) must share its directive item
a table cell or a bullet — with either the replacement action or the
mechanism that makes it true. ## Sources is out of scope.

Corpus practice pairs prohibitions with a reason more often than with an
alternative action (Never retry — the request itself is wrong; the same bytes fail again), which the old AGENTS.md rule 3 would have failed. The new rule
permits it.

  • scripts/wiki-lint-prohibitions.js — exits 0 clean / 1 violations / 2 bad dir
  • Negative-control fixtures + 10 bats tests, so the checker is observed both
    passing and failing rather than only ever returning 0
  • 2-word cells (| Unknown key | Never read | vs | Timeout | Never retry |)
    are undecidable by shape, so they surface as new check 11 at info

#37 — disjoint routing scopes for two ambiguous pairs

Routing probes 6 and 7 (plans/wiki-audit/findings/routing.md) were AMBIGUOUS:
two concerns each claimed verbatim by two domains.

  • Doc gates split on mechanism vs acceptance: testing/quality owns
    authoring and validating the check; qa/document-verification owns whether
    passing it is enough to accept the deliverable.
  • Flaky tests split on what is the unreliable thing: a test in your suite →
    testing/flaky; the system itself → debugging/concurrency.

The flaky predicate was first written as "do you have an on-demand
reproduction?" and the independent auditor rejected it twice — correctly. Probe
7's wording ("an intermittent CI test failure that passes on retry") does not
state whether a reproduction exists, so that predicate is undecidable at the
routing gate, which is exactly where it must be decided. The subject of the
failure is always stated in the symptom, so subject is the decidable axis.

Both probes re-run UNIQUE from index lines alone. Also fixes the INDEX.md
backend LLM phrasing (a weak attractor for ML-training queries) and adds a
databases→backup cross-pointer.

#77 — knowledge-flush no longer races itself

The manual skill and the auto-flush.sh hook drained one queue into competing
PRs and reset each other's checkout. Fixed on both axes the issue identified as
complementary — and the bundled wiki independently prescribes the same pairing
(distributed-locks: "the lock alone cannot provide this";
scheduled-job-overlap: "prevention lowers the probability; idempotency removes
the damage"):

  • scripts/flush-lock.sh — one owner-token lock shared by both entry points.
    mkdir is the primitive because flock(1) is util-linux-only and absent on
    macOS. Release is delete-if-token-matches; reclaim needs TTL expiry and a
    dead pid.
  • hooks/queue-claim.js — per-run claim of queue rows, so even a concurrent pair
    produces disjoint PRs. Atomic tmp+rename; an unparseable line is carried
    through verbatim, never dropped.

This also removes a live bug: auto-flush.sh released its old lock with a bare
rmdir, the exact anti-pattern that deletes the next holder's lock after a TTL
expiry.

Two blockers found in review and fixed before merge, both from the run id being
computed per process:

  1. the hook acquired the lock and the headless session it spawned then hit step 0
    and got exit 3 — silently killing the whole auto-flush path. acquire is now
    re-entrant for the same owner; a foreign run id still gets 3.
  2. the skill's step 0 and step 5 run in separate shells, so an unprefixed
    release computed a new run id, was refused, and leaked the lock for the full
    900s TTL.

Verification

Every verification command was re-run by the coordinator rather than taken from
the worker reports.

Not included

#38 (wiki category seeds) and #75 (orchestrator-skill pattern) were in the
approved split but not implemented; #38 collides with in-flight work on the same
domain indexes. Both stay open.

dch0202 and others added 6 commits August 12, 2026 23:32
Replaces the "don't outside Instead-of" judgment call with a ported,
corpus-validated parser (scripts/wiki-lint-prohibitions.js): a bare
prohibition alone in its directive item (table cell or bullet) is a
violation; one paired with a replacement or mechanism, or living in an
Instead-of row, is compliant; a bare 2-word cell is an undecidable blind
spot surfaced at info (new check 11), never error.

Closes #36. 0 violations / 61 directives on wiki/, matching the reference
probe at .orchestration/evidence/i36-rule-probe.js. AGENTS.md rule 3 and
wiki-lint check 2 restated to match, in the same words, both naming the
script.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_0199ew7RNxbFAiiWaegbXuDP
Routing probes 6 and 7 found two AMBIGUOUS pairs where two domains claimed
near-identical scope, forcing a router to open both pages to decide. Give
each pair a disjoint predicate plus mutual inline cross-pointers so the
index "load when" line alone resolves it.

- Doc-gate cluster (testing/quality <-> qa/document-verification): split on
  mechanism ("does my check discriminate?") vs acceptance ("is passing the
  gate enough to accept the deliverable?"). qa/spec-document-gates no
  longer claims check-authoring or unwritten-target gate patterns.
- Flaky pair (testing/flaky <-> debugging/concurrency): split on what the
  unreliable thing is - a test in your suite vs the system itself - after
  an initial reproduction-state axis proved undecidable from a bare symptom
  statement (probe 7) and was revised mid-implementation.
- Two minor route fixes: INDEX/backend LLM phrasing no longer attracts
  ML-training queries; databases index cross-points to the backup/restore
  page.

Closes #37.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01FU4PzRNVigcSwJCKxDTYrj
…en lock + queue claim

Close #77. The manual /dev-loop:knowledge-flush skill and the
hooks/auto-flush.sh Stop hook drain the same insight queue but had no
mutual exclusion, and the hook released its lock with a bare `rmdir` that
can delete the next holder's lock after a TTL expiry.

- scripts/flush-lock.sh: new owner-token mkdir lock (acquire/release/holder),
  900s TTL with pid-liveness stale reclaim, fail-fast on a held lock, and
  re-entrant acquire for the same run id (refreshes the epoch instead of
  treating a hook-spawned child session as a foreign holder).
- hooks/queue-claim.js: claim/release queue rows by run id (list/claim
  [--max N]/release), 3600s claim TTL, atomic tmp+rename rewrite, dedupes on
  harvest.js's existing "hash" key, never touches .processed.jsonl or drops
  an unparseable line.
- hooks/auto-flush.sh: acquires/releases through flush-lock.sh instead of the
  bare rmdir; PENDING count already excluded claimed rows, now documented.
- skills/knowledge-flush/SKILL.md: new step 0 acquires the lock before the
  checkout's hard-reset step in step 1, and generates one run id explicitly
  threaded through every later flush-lock.sh call (each step runs in a fresh
  shell, so nothing survives via export); step 2 claims rows before
  ingesting; step 5 retires only claimed rows and releases unclaimed ones.
- README.md/README.ko.md: document the two new TTL knobs in the existing
  auto-flush env-var block.

tests/flush-lock.bats (14) and tests/queue-claim.bats (13) cover the
age/pid-liveness reclaim matrix, re-entrant vs. foreign run-id acquire,
non-owner release refusal, malformed/empty state, .processed.jsonl exclusion,
and --max truncation. tests/harvest.bats (12) unaffected.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01RzpAVVUM6BrhzsecWJ4uZ4
@choiyounggi
choiyounggi merged commit 833ff7a into main Aug 13, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment