diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 3a965e5..bab7ea2 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,237 +1,75 @@ -# Knowledge flush — 13 insight(s) ingested (21 claimed, 2 dropped, 6 released) +# Knowledge flush — 12 insight(s) -Cross-Check: 1× independent adversarial `claude` CLI headless pass over the 5 new pages — it refuted the changed-files gate page's "prettier exits 0 on an empty match set" claim; re-measured against Prettier 3.7.4, confirmed the reviewer was right (unmatched operand exits **2**), and rewrote the page, report rows 3/5 and `log.md`. Other 5 claim groups verdicted sound. Limits: the reviewer's sandbox blocked repo reads, so source-quote-supports-directive and self-contradiction dimensions went unaudited (details in `## Cross-Check`). - -Queue drained under run id `20260827-125731-38371` (this session is the detached -`hooks/auto-flush.sh` run; its step-0 acquire resolved re-entrantly to -`already-owned`, not to a competing holder). 21 rows were claimed; 13 are -ingested below, 2 are retired as out-of-layer, and 6 are released back to -`pending` for a later flush because each needs its own page rather than a row, -and rushing six more pages in one pass would have lowered the bar on all of them. +Batch: 12 of 109 pending candidates were claimed (`queue-claim.js claim --max 12`) so the PR stays reviewable; the remaining 97 stay pending for later flushes. Every claimed row was handled (none released): 5 new pages, 7 merges into existing pages, 0 dropped. Each claim was researched by a dedicated verifier agent against live-fetched primary sources and, where possible, reproduced locally; two candidate details were found wrong and corrected in the pages (noted below). ## Verified best-practice -Every external claim below was live-fetched this session and quoted in the page's -`Sources` block. Field evidence carries the repo, date, and the measured numbers. - -| # | Claim | Sources checked | How verified | Confidence | -|---|-------|-----------------|--------------|------------| -| 1 | `now()` is `transaction_timestamp()` (fixed at transaction start) while `clock_timestamp()` "changes even within a single SQL statement"; `RETURNING` yields computed defaults "without needing a separate database query" | postgresql.org `functions-datetime`, `dml-returning`, `transaction-iso` | Fetched; both key sentences quoted verbatim into the page | verified | -| 2 | A boundary recomputed in a follow-up step is a *second, later* `now`, widening a `<= boundary` set | Field: `rtb-unified` `packages/orpc/src/routers/batch.ts` — codifies "one `now` per decision" and passes `now` into the boundary helper; its result type omits the boundary, which is the shape that invites recomputation | Read the invariant and the signature in the cited file | field-tested | -| 3 | **[CORRECTED BY CROSS-CHECK]** The vacuous-pass shapes for `prettier --check` are: no operands (rc **0**), all operands ignore-filtered (rc **0**), and unsupported extensions with `--ignore-unknown` (rc **0**). A pattern/operand matching nothing exits **2** — it prints the success sentence *and* an unmatched-pattern error | prettier.io CLI + ignore docs; local measurement, Prettier 3.7.4 | The first draft generalised "empty match set ⇒ exit 0" from a field log where both messages appeared together. The independent reviewer flagged it; I then ran all seven cases against a real binary and rewrote the page around the measured table | verified (re-measured) | -| 4 | zsh does not word-split unquoted parameter expansions by default, so `cmd $FILES` arrives as **one** operand | zsh FAQ ch. 3 (`SH_WORD_SPLIT`) | Fetched; quoted ("By default, zsh does not have that behaviour: the variable remains intact") | verified | -| 5 | The zsh word-split operand exits **2**, but its log still carries the success sentence — so the log misleads even though the exit code does not | Field 2026-08-24 (`rtb-unified`, zsh) + local measurement 2026-08-27 | Field log showed both messages together; the local run reproduced it as `rc=2`. The page now says explicitly that this row fails loudly *unless* `--no-error-on-unmatched-pattern` is set. Probe placement re-confirmed: `.claude/tmp/` is `.gitignore`d, so a probe there passes at rc 0 | verified (re-measured) | -| 6 | TypeScript applies excess-property/contextual typing to fresh object literals, so a value of a type can be constructed with the type's name absent from the text | typescriptlang.org handbook, *Object Types* | Fetched; confirmed the check follows from the contextual type, not from a written annotation | verified | -| 7 | `tsc`'s program is `files` ∪ `include` ∪ transitive imports; `exclude` "only changes which files are included as a result of the `include` setting" and does not stop an imported file entering the program | typescriptlang.org TSConfig `#include`, `#exclude` | Fetched; the `exclude` sentence quoted (it sharpens the rule to "in the program", not "in `include`") | verified | -| 8 | Consequence of 6+7 measured | Field 2026-08-24/25 (`rtb-unified`): `grep "DealViewer"` reported 3 construction sites, actual 8 — the missed set included production wiring `routers/deal.ts:38`; `ContractScopeActor` 7→~22. Separately, `packages/orpc/tsconfig.json` `include: ["src/**/*"]` produced 3 production + 13 api-test errors and **zero** for `__tests__/routers/deal.test.ts`, whose 6 sites appeared only as 6 failing tests | Counts recorded from the cited runs | verified | -| 9 | cgroup v2: `memory.peak` is max usage since creation/reset; at `memory.max` "the OOM killer is invoked in the cgroup"; in `memory.events`, `max` counts times usage "was about to go over the max boundary" — **distinct** from `oom_kill` | docs.kernel.org cgroup-v2 admin guide | Fetched; all four quoted. This corrected the candidate, which had read a non-zero `max` as a kill; the page now states the distinction explicitly | verified | -| 10 | An `exec`'d process joins the container's cgroup and is invisible to the application's own semaphore | kubernetes.io `manage-resources-containers`, `assign-memory-resource`, `kubectl exec` reference + field 2026-08-26 (review-bot pod, `limits.memory: 3Gi`): `memory.current` 2.54 GiB, `memory.peak` 3.0 GiB (at the limit), `memory.events: max 5`, while `maxConcurrentAgents: 20` reported free slots | Docs fetched; pod numbers from the cited measurement | verified | -| 11 | Basename-keyed mutation backups collide across directories and restore cross-writes; an untracked file's `git diff` is empty whether restored or destroyed | Field 2026-08-21 (`rtb-unified`, NEWRTB-2936): restore wrote `schemas/deal.ts` into `routers/deal.ts` → `Cannot find module './common.js'`, `grep -c dealRouter` = 0; **both files were 154 lines**, so a line-count check passed; after re-keying, M9/M10 flipped SURVIVED→KILLED. Plus stryker mutant-states / pitest for the verdict vocabulary | Reproduced end to end in the cited run | field-tested | -| 12 | A negative assertion is vacuous when the fixture never supplies the triggering input | Field 2026-08-25 (`rtb-unified`): with `staleQueuedJobIds: []` the code early-returned; the widening the assertion claimed to catch survived 116/116 green | Mutation applied and observed | field-tested | -| 13 | A body-level (non-inline) review finding cites no file, so rebutting from an assumed file rejects real defects | Field 2026-08-19 (PR #327 r16): quote matched `report.py:393/416/425`, not the assumed `fill_plan.py:307` — sibling modules, one already fixed | Grep resolved the quote to the real site | field-tested | -| 14 | Unifying two duplicate allowlists defaults to the union and silently widens each side | Field 2026-08-25 (`rtb-unified` PR #965): folding `DISPLAYABLE_ERROR_CODES` into `USER_FACING_ERROR_CODES` would have added `UNAUTHORIZED` + `VALIDATION_ERROR`, exposing raw server messages as inline UI errors; caught only by computing the difference first | Difference computed before the merge | field-tested | - -Not upgraded: nothing was marked `verified` on field evidence alone. Two pages -carry `confidence: field-tested` (`mutation-harness-file-custody`, -plus the pre-existing `evaluating-review-feedback`), and no candidate was -recorded as `verified` without a fetched primary source. +| # | Candidate (hash) | Claim | Sources checked (fetched/read) | How verified | Confidence | +|---|------------------|-------|--------------------------------|--------------|------------| +| 1 | `2002a817b2e74f84` | A quantifier after a non-ASCII literal binds to the last UTF-8 byte under `LC_ALL=C`; group it `(─){3,}` and test under C | POSIX grep spec (already cited on the page); bug-grep list thread on unibyte vs multibyte code paths | Reproduced on macOS BSD grep 2.6.0-FreeBSD and BSD sed: `─{3,}` → 0 under C, 1 under UTF-8; `(─){3,}` → 1 in both; a `─` + two bare `0x80` bytes matches `─{3,}` under C (last-byte binding). GNU grep not installed → GNU result stated as untested | verified (BSD); GNU untested, said so on the page | +| 2 | `6b8811c6c3b0f983` | `permissions.deny` is enforced in `bypassPermissions`; deny wins at every level; compound commands split per subcommand; wrappers stripped | https://code.claude.com/docs/en/permission-modes ("Deny rules block in every mode, including bypassPermissions … Allow rules have no effect in bypassPermissions"); https://code.claude.com/docs/en/permissions (deny-at-any-level, separator list, stripped-wrapper list) | Direct quotes. **Correction:** the candidate listed `env` among stripped wrappers; the docs' list is `timeout, time, nice, nohup, stdbuf, command, builtin, noglob` plus a leading `VAR=value` — `env` is not stripped. Page says so | verified | +| 3 | `a9ebd8fda72aade6` | Slow Kotlin build + `Exception during IR lowering` → suspect heap; raise Gradle + Kotlin daemon heap at user level | https://docs.gradle.org/current/userguide/build_environment.html (user-level precedence quote); https://kotlinlang.org/docs/gradle-compilation-and-caches.html (`kotlin.daemon.jvmargs` vs `-Dkotlin.daemon.jvm.options`, inheritance); https://kotlinlang.org/docs/kotlin-daemon.html (inherits `-Xmx`) | Docs fetched and the property keys re-grepped from the raw HTML by me. **Correction:** the candidate's `kotlin.daemon.jvm.options=-Xmx4g` line is a system property valid only inside `org.gradle.jvmargs`; the Gradle key is `kotlin.daemon.jvmargs`. The symptom→heap link has no external source (documented causes of that message are compiler bugs), only the session's one measured build (19 min fail at 1g → 4m13s pass at 4g) | field-tested; page keeps the compiler-defect hypothesis explicit | +| 4 | `44cd212b53946b62` | Bash-hook guard misses Write/Edit escapes; on escalation check main `git status`, transfer by patch (`diff` → `apply --check`/`apply` → `checkout --`) | https://code.claude.com/docs/en/hooks (matcher `Edit\|Write`, exit-2 block); https://git-scm.com/docs/git-worktree; https://git-scm.com/docs/git-apply (`--check` quote) | Mechanics doc-confirmed; the recovery recipe is the session's field reproduction (lo-2 files transferred, main restored, `MAIN_CLEAN`). Merged into a page already `verified` | verified page, field-tested addition | +| 5 | `2e3d108012a7e164` | Unbound method extraction loses `this`; bind at extraction; a `vi.fn()` test cannot catch it | https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this; https://vitest.dev/api/mock.html | `node -e` repro: unbound → `TypeError: Cannot read properties of undefined`; bound → 42. **Correction:** Vitest's `mock.contexts` records `this` per call, so a `vi.fn()` test *can* catch it when it asserts `mock.contexts[0] === obj`; page offers that as the third test shape | verified | +| 6 | `28897d4a24a46ae9` | Parallel branches green alone can fail on merge (E0004); build+test the merged tree before dispatching dependents | https://martinfowler.com/bliki/SemanticConflict.html; https://git-scm.com/docs/git-merge; https://doc.rust-lang.org/error_codes/E0004.html | Quotes: "safely merged on a textual level but cause the program to behave differently"; git merge documented as textual 3-way only; E0004 non-exhaustive patterns. Field run slk1 as the reproduction | verified | +| 7 | `f978fd126f72956f` | Write the interpreter by path in worker briefs; measure and record the baseline `Ran N / failures=M` first | https://docs.python.org/3/library/venv.html ("You don't specifically need to activate…"); https://docs.python.org/3/library/unittest.html | Docs quoted; `Ran 3 tests … FAILED (failures=1, errors=1)` reproduced locally; field measurement 3532/15+18 vs 3549/1 | verified (baseline-recording half is process advice grounded in completion-claims) | +| 8 | `076309143096ed63` | A substring doc-coverage gate is vacuous; scope to heading lines with word boundaries; falsify against a known-bad revision | https://testing.googleblog.com/2020/08/code-coverage-best-practices.html ("Mutation testing can help detect such false coverage"); the page's existing mutation-testing and RuleTester sources | Source fetched; field reproduction (commit 7bdbb1c: pre-fix doc → `missing: ['grammar']`) | verified sources, merged into a field-tested page | +| 9 | `5d4a63ae21e06d9e` | After an auditor reports mutation testing on your uncommitted tree, diff against a pre-audit patch and re-run the suite yourself | https://git-scm.com/docs/git-checkout (`checkout -- ` discards unstaged changes); https://pitest.org/quickstart/basic_concepts/; repo's own `agents/test-quality-auditor.md` (restore mechanism) | Docs quoted; repo file read; wiki's own caveat (untracked file diffs clean either way) supports the independent check. The "session must verify independently" step is the session's practice, not an external norm | field-tested | +| 10 | `d3d8fdcc202b8760` | `tmux ls` sees one server only; before resuming as coordinator, prove no live coordinator via `ps` + artifact mtimes | `man tmux` (list-sessions scoped to a server; `-L`/`-S`); `man ps` (`-A`, `-o lstart,command`); repo `skills/orchestrate/SKILL.md` re-entry section | Man pages read; **nuance added:** a missing watcher process alone proves nothing (coordinator between polls), only presence proves liveness — page states both-absences rule. Field run: PID 54780 alive on ssh pty, duplicate commits on t3 | field-tested (man-page facts verified) | +| 11 | `87d2a59d6b82953c` | argparse `required=True` accepts `""`; `realpath("")` is the CWD; reject empty before resolving | https://docs.python.org/3/library/argparse.html#required; https://docs.python.org/3/library/os.path.html#os.path.abspath | Quotes; local repro `parse_args(["--out",""])` → `Namespace(out='')`, `realpath('')` → cwd | verified | +| 12 | `f301809d865f8865` | Guard bats suite inside a worker floods the run's escalation dir and reads the worktree's sandbox config via upward traversal; override the env + run from a clean cwd | https://bats-core.readthedocs.io/en/stable/writing-tests.html (`bats-run-*` naming); repo `skills/orchestrate/scripts/{escalation-dir,worker-guardrails,watch-status}.sh`; guard source `groundwork/plugins/guardrails/hooks/bash-guard.sh` + its bats tests (the two named tests do not `cd` into their tmpdir) | Mechanism confirmed by reading the real scripts; the 175-record count is the session's observation | field-tested | ## Existing-layer check -Method: routed via `INDEX.md` → domain `index.md`; then built a full id+title -index of all 265 pre-existing pages and probed it with concept greps -(`clock_timestamp|clock skew`, `changed[- ]files|--ignore-unknown`, `tsconfig`, -`contextual typ|excess property`, `set difference|allowlist`, `cgroup`, -`basename|backup.*restore`, `2>&1`, `delta|baseline`) before deciding new vs merge. - -Pages read: testing-quality-source-text-wiring-assertions, testing-quality-tests-that-cannot-fail, backend-common-change-impact-call-site-enumeration, backend-common-change-impact-widening-a-closed-value-table, qa-process-evaluating-review-feedback, infrastructure-containers-host-cgroup-visibility, testing-quality-behavior-not-implementation - -Findings: - -- **Zero coverage** (→ new pages): changed-files-only gates, tsconfig/contextual - typing, allowlist set-difference, app-clock-vs-DB-timestamp, exec-into-a-running-container. - The concept greps returned no hits for these; the clock hits were incidental - (offline sync, token handling) and none compared an app clock to a DB column. -- **Already covered — one candidate all but retired.** The comment-stripping - insight is `source-text-wiring-assertions` step 2 verbatim ("Make the - assertion's subject the file with comments removed"), and its false-RED and - negative/count false-GREEN shapes are already edge rows. Only the *empty-slice* - consequence was new, so that alone was merged. -- **Line-cap conflict handled without breaking the invariant.** - `source-text-wiring-assertions` sits at exactly **120** body lines (the - documented cap). Rather than add a row and violate maintenance invariant 5, the - new nuance and the new field evidence were merged **in place** into an existing - edge row and an existing source bullet. Body count re-measured after editing: - still 120. -- **No conflicts found.** Nothing ingested contradicts an existing directive. - The one correction made was to a *candidate*, not to the wiki (item 9: the - `memory.events` `max` counter is approaches-to-limit, not kills). -- **Related links added both ways**: `tests-that-cannot-fail` ↔ - `mutation-harness-file-custody`; `widening-a-closed-value-table` ↔ - `compiler-as-call-site-inventory` (+ `errors-diagnostics-from-a-shared-code-path`); - `host-cgroup-visibility` → `exec-added-processes-and-the-memory-budget` - (from its existing self-monitoring row). -- **Indexes/log updated**: 4 domain indexes (+5 "load when" rows), `log.md` - appended. Root `INDEX.md` unchanged — no new domain. - -Gates run (the exact CI commands from `.github/workflows/test.yml`): -`node scripts/wiki-structure-checks.js wiki` → **pages: 270, indexes: 13, -findings: 0**; `node scripts/wiki-lint-prohibitions.js wiki` → **directives 72, -compliant 72, violations 0** (the 1 `info` is pre-existing in -`config/keys-ahead-of-their-consumer.md`, untouched); `bash scripts/check-versions.sh` -→ `ok: dev-loop 1.11.2`. The `bats tests/` job was **not** run — bats is not -installed on this machine, and this change touches only wiki markdown (no -scripts or hooks), so that suite's subject is unchanged. +Routing went through `INDEX.md`, then the domain indexes for infrastructure, backend (+ java, python, node subtrees), frontend, testing, platforms, qa, debugging, and the security agent-exposure section; every page whose "load when" overlapped a candidate was opened in full. + +Pages read: platforms-environment-unicode-text-matching, platforms-shells-escapes-in-shell-string-literals, platforms-environment-timezone-and-locale, platforms-tools-agent-permission-classifier-denials, infrastructure-agent-orchestration-worktree-isolated-workers, qa-process-scope-purity-checks, backend-java-runtime-threads-and-memory, testing-mocking-what-to-mock, testing-quality-tests-that-cannot-fail, infrastructure-agent-orchestration-shared-run-state, backend-common-change-impact-cross-module-consumer-census, infrastructure-agent-orchestration-autonomous-decision-rulings, platforms-environment-path-resolution, platforms-toolchains-version-management, qa-process-completion-claims, testing-quality-spec-artifact-checks, qa-document-verification-spec-document-gates, testing-quality-checks-that-cannot-pass, testing-quality-harness-reverse-controls, testing-quality-mutation-harness-file-custody, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, infrastructure-config-path-valued-config, backend-python-boundaries-runtime-validation, testing-data-test-data-and-isolation, testing-data-artifact-leakage-from-a-suite + +Per candidate: + +| # | Overlap found | Decision | +|---|---------------|----------| +| 1 | `unicode-text-matching` already has a generic "quantifier over non-ASCII → test on both userlands" row | **Merged** into it: +1 edge row (last-byte binding under C, grouping fix), +1 Instead-of row, +2 sources; `related` +bsd-vs-gnu-cli both ways | +| 2 | `agent-permission-classifier-denials` has the deny tier in its precedence table but is scoped to auto-mode classifier denials; `worktree-isolated-workers` covers path isolation, not command classes | **New page** `platforms/tools/deny-rules-under-bypassed-permissions`; links both ways with classifier-denials, worktree-isolated-workers, control-signals, binding-instructions, checks-that-cannot-pass | +| 3 | `threads-and-memory` covers runtime heap diagnosis, not the build daemons; no Gradle/Kotlin-compiler page exists | **New page** `backend/java/kotlin/compiler-daemon-heap-pressure`; links both ways with threads-and-memory, reading-error-messages, hypothesis-testing | +| 4 | `worktree-isolated-workers` already carries the Bash-hook-vs-Edit/Write edge row and Instead-of row | **Merged**: +1 edge row (escalation-time main-tree check + patch transfer, complete hook matcher), +1 Instead-of row, +git-apply source, +field reproduction. No conflict with the existing directive — it extends it | +| 5 | `what-to-mock` and `tests-that-cannot-fail` cover mock-hides-bug generally; nothing on `this` binding | **New page** `testing/mocking/extracted-method-this-binding`; links both ways with what-to-mock, tests-that-cannot-fail, captured-call-arguments, test-level-choice | +| 6 | `shared-run-state` (default branch moved), `cross-module-consumer-census` (integration-time census), `widening-a-closed-value-table` (enum/table widening) are adjacent; none covers the post-merge build gate | **New page** `infrastructure/agent-orchestration/semantic-conflicts-after-parallel-merge`; links both ways with those three, autonomous-decision-rulings, worktree-isolated-workers, completion-claims | +| 7 | `path-resolution` (pin binaries in automation), `version-management` (shims absent non-interactively), `completion-claims` (dropped total = red) each hold one third | **New page** `infrastructure/agent-orchestration/verify-command-in-a-worker-brief` composing them for the brief-authoring trigger; links both ways with all three, compiler-sysroot-on-macos, tests-that-cannot-fail, worktree-isolated-workers | +| 8 | `spec-document-gates` axis table ("token survives in a nearby paragraph"), `spec-artifact-checks` (negative control per check), `checks-that-cannot-pass` (known-good/known-bad) already own the principle | **Merged** into `spec-document-gates`: +1 edge row (heading-scoped word-boundary match + pre-fix revision as negative control), +1 Instead-of row, +2 sources. Near-duplicate; the heading/word-boundary scoping and revision-as-mutant detail were the only additions | +| 9 | `tests-that-cannot-fail` (restore mechanism by commit state; multi-agent shared tree), `mutation-harness-file-custody` (untracked file diffs clean either way), `completion-claims` (relaying subagent reports) | **Merged** into `completion-claims` claim/evidence table (+1 row, +git-checkout source, +field reproduction); `related` +mutation-harness-file-custody both ways. Not added to tests-that-cannot-fail (115 body lines, near the cap) | +| 10 | `shared-run-state` step 3 confirms a foreign run from the repo; `autonomous-decision-rulings` step 3 re-reads the ledger on resume | **Merged** into `shared-run-state`: +1 edge row (coordinator liveness on resume: tmux-server scope, `ps`, artifact mtimes, both-absences rule), +1 Instead-of row, +man ps citation, +field reproduction | +| 11 | `path-valued-config` already rejects non-absolute paths (an empty string is non-absolute) and lists rejection test inputs | **Merged**: When-this-applies widened to required CLI flags, `""` added to the rejection test set, +1 edge row (argparse presence-only, realpath("") = CWD), +1 Instead-of row, +3 sources | +| 12 | `test-data-and-isolation` already has two rows on harness-injected env vars and "the code under test is the harness that spawned the session" (issue #100, same shape) | **Merged** (delta only): +1 edge row (cwd-walking config discovery + override the exported escalation dir), +bats source, +field reproduction; coordinator-side cleanup row added to `shared-run-state` | + +Conflicts flagged: none — every merge extends an existing directive; no existing directive was contradicted or overwritten. ## Open-PR check -`gh pr list --repo choiyounggi/dev-loop --state open --search "head:knowledge/"` -returned **no open PRs**, and a second unfiltered `gh pr list --state open` -returned none either — the repository has zero open PRs at flush time. There -were therefore no in-flight sibling branches to diff against, and no -`git fetch origin ` / `git diff origin/main origin/ -- wiki/` -comparisons to run. +``` +gh pr list --repo choiyounggi/dev-loop --state open --json number,headRefName,title --search "head:knowledge/" +``` +returned no rows, and `gh pr list --state open --limit 50` (any head) also returned no rows at flush time (2026-09-03 17:28 KST, as `choiyounggi`). No sibling knowledge branch exists, so no candidate could fold into or duplicate an in-flight PR. -Per-candidate verdict: **all 21 = `new`.** No `fold`, no `drop-as-pending-duplicate`. -(The 2 drops recorded below are out-of-layer drops, not pending-duplicate drops.) +Per-candidate verdict: all 12 → **new** (no overlapping open head). ## Routing decision -**New pages (5)** - -| Page | Domain/category | From | Why not an existing page | -|------|-----------------|------|--------------------------| -| `application-clock-vs-database-timestamps` | databases / transactions | `2b27d15d` + `bea92fdd` | No page compares an app clock to a DB column. `transactions` chosen over `schema-design` because the decisive content is transaction-time semantics (`now()` = transaction start ⇒ stamp order ≠ commit order) and the fix is a lock/isolation choice | -| `changed-files-only-gates` | infrastructure / ci-cd | `ff041061` + `4b9af3a0` | Zero grep hits. Both candidates are the same defect (a gate green with an empty subject) from two directions, so they became one page rather than two | -| `compiler-as-call-site-inventory` | backend / common / change-impact | `702dcf4e` + `94d55f2f` | `call-site-enumeration` is the sibling case (callers of a changed signature, Python positional-vs-keyword) and is at 80 body lines; the TS mechanism is *constructors of a type* with its own workflow, so per "one case per page" it is a separate page, cross-linked | -| `mutation-harness-file-custody` | testing / quality | `6a9de235` + `41fa1c87` | `harness-reverse-controls` covers scoring a harness; nothing covers the harness's custody of the tree. Both candidates are that one case (keying, and the read window) | -| `exec-added-processes-and-the-memory-budget` | infrastructure / containers | `7b9e8788` | `host-cgroup-visibility` is cross-pod read mechanics and explicitly routes self-monitoring elsewhere; `resource-limits-and-probes` is manifest authoring. This is a runtime preflight before adding load | - -No new category was created — all five landed in existing categories. - -**Merged into existing pages (5 candidates)** - -| Candidate | Merged into | Shape | -|-----------|-------------|-------| -| `91ef5d53` | `testing-quality-tests-that-cannot-fail` | +1 never-fails row, +1 Instead-of row, +1 source | -| `f189f423` | `testing-quality-source-text-wiring-assertions` | In-place extension of 1 edge row + 1 source bullet (page at the 120-line cap) | -| `bb6d8539` | `backend-common-change-impact-widening-a-closed-value-table` | +Do-this 6 & 7 (incl. a set-difference ruling table), +1 Instead-of row, +1 source | -| `60a817ee` | `qa-process-evaluating-review-feedback` | +2 edge rows, +1 Instead-of row, +1 source | -| `7b9e8788` | `infrastructure-containers-host-cgroup-visibility` | Cross-link from its self-monitoring row to the new page | - -**Dropped — out of layer (2, retired)** - -- `094dedf3` — a Figma MCP `inspect_node` → `get_dev_ready` children-fetch - workaround. The server is a private, org-internal MCP plugin; the behavior is - not publicly verifiable and the directive does not transfer to any other reader. -- `e165a365` — an `/rtb:review` remote-fallback runbook naming - `~/.claude/tools/rtb-remote-review.sh` and an internal pod. The transferable - kernel ("a two-provider review gate degraded to one provider is not a passed - gate") is already the subject of `qa-process-llm-review-pipelines`; what remains - is machine-specific paths. - -**Released back to `pending` (6)** — each needs its own page, not a row, and is -better served by a dedicated pass than by being appended here: -`81dc1f98` (naming the carrier field/type when a plan says "wire A to B"), -`b9ae304a` (`VAR="$(cmd 2>&1)"` mixing stderr into a value used as a path), -`fdd0b3c6` (monitor markers anchored at line start; delta rather than absolute -state; first cycle records a baseline), -`c2adb2be` (positional-order assertions on rendered SQL predicates), -`815e8cb9` (grep only *active* `DATABASE_URL` assignments, and confirm which -dotenv file the tool loads, before a destructive DB command), -`f1146adb` (CI ticket-key extraction scoped by changed-file intersection rather -than by mention). - -## Decision Log - -**Intent.** Drain the harvested `★ Insight` queue into reviewable wiki knowledge -without lowering the wiki's evidence bar. The queue held 21 rows accumulated over -several days; the goal was correct routing and real verification, not a high -ingest count. - -**Alternatives considered and rejected.** - -- *Ingest all 21 in this pass.* Rejected: six of them each need their own page, - and writing six more pages in one pass would have produced thin, weakly-sourced - entries. They are released to `pending`, not dropped, so the next flush takes - them with a full budget. -- *Append the two TypeScript candidates to `call-site-enumeration`.* Rejected: - that page is the sibling case (callers of a changed signature, Python - positional-vs-keyword). AGENTS.md requires one case per page, so the - constructor-enumeration case became its own page, cross-linked both ways. -- *Add a row to `source-text-wiring-assertions` for the empty-slice nuance.* - Rejected: that page is at exactly the documented 120-line body cap, so adding a - line would violate maintenance invariant 5. The nuance was merged **in place** - into an existing edge row instead; body re-measured at 120. -- *Drop the comment-stripping candidate entirely as a duplicate.* Rejected: its - directive is already the page's step 2, but the empty-slice consequence - (vacuous **green**, not the documented noisy red) was genuinely absent. -- *Claim a cross-check exemption because this PR cannot merge itself.* Rejected — - see below; the check found a real error, which is the argument against exempting. -- *Push to `origin`* as the skill's snippet does. Not available: this contributor - has no write access to `choiyounggi/dev-loop` (403). Used the pre-existing - `fork` remote, which is how every prior knowledge branch here was published. -- *Branch name from `git config user.name`.* The skill's ASCII sanitisation of a - Korean name yields an empty string → `anon`, defeating the attribution the - branch name exists for. Used the gh login, matching existing branch names. - -**Where reviewers should look hardest.** - -1. `infrastructure/ci-cd/changed-files-only-gates.md` — rewritten after the - cross-check. The measured table is the load-bearing part; please sanity-check - it against your own Prettier version, since the exit codes are version-visible - behaviour rather than a documented contract. -2. `databases/transactions/application-clock-vs-database-timestamps.md` step 5–6 — - the claim that timestamp order is not commit order, and that the remedy is a - lock/isolation level rather than finer clock resolution. `[추정]` on the MySQL - `NOW()`/`SYSDATE()` row: taken from general MySQL semantics, not fetched this - session like the PostgreSQL pages were. -3. `widening-a-closed-value-table.md` Do-this 6–7 — this inserts a security-shaped - concern (allowlist widening) into a page whose original subject was value - tables. If that reads as two cases, it should be split. -4. The 2 dropped candidates — if you consider private-tooling runbooks in scope - for this wiki, they should be restored rather than retired. - -## Cross-Check - -Independent adversarial pass via `claude` CLI headless (separate process, no -shared context), prompted to refute rather than confirm, over the five new pages' -technical claims. - -**It found a real error, and the page was rewritten because of it.** The reviewer -challenged the claim that `prettier --check` exits 0 on an empty match set, -arguing an unmatched pattern errors by default and that exit-0 belongs to the -ignore-filtered case. I resolved it by measurement rather than by argument — -running all seven cases against Prettier 3.7.4 — and the reviewer was right: -an unmatched operand exits **2** (while still printing the success sentence), -whereas the genuine silent vacuous passes are no-operands, all-ignore-filtered, -and `--ignore-unknown`-with-unsupported-extensions. The page, this report's -rows 3 and 5, and the `log.md` entry were all corrected. - -Verdicts on the other five claim groups: **sound** (PostgreSQL clock semantics — -noted as if anything *understated*; zsh word-splitting; TS contextual typing; -`tsc` program membership incl. `exclude`-does-not-stop-imports; cgroup v2 -`max` vs `oom_kill` and `kubectl exec` cgroup placement). - -Stated limits of the check: the reviewer's sandbox denied it read access to -`~/.dev-loop/repo/wiki`, so it adjudicated the six claims as quoted in its prompt -and could **not** audit (b) whether each `Sources` quote supports the directive it -is cited for, or (c) whether any page contradicts its own edge-case rows. Those -two dimensions remain unreviewed by an independent party and are the residual -risk in this PR. A first attempt also returned only the session's Stop-hook -output rather than a verdict; that run was discarded rather than read as -"no findings". - -## Review notes - -- PR-only, as required: no merge, no push to `main`. -- Commit is under the contributor's own ambient git identity - (`최영기 `, gh `dch0202-rsquare`); no assistant identity - and no `Co-Authored-By` trailer. The branch uses the gh login because - sanitizing the Korean `user.name` to ASCII yields an empty string, which the - skill's snippet would have turned into `anon` — that would have defeated the - attribution the branch name exists for. -- Scope purity: only `wiki/**`, four domain indexes, `log.md`, and this report. - Two untracked leftovers from earlier flushes - (`.dev-loop/CROSSCHECK_FINDINGS.md`, `.dev-loop/fold-note-73.md`) were left - untouched and unstaged. +| # | Target | Category fit | +|---|--------|--------------| +| 1 | platforms/environment/unicode-text-matching (merge) | existing | +| 2 | platforms/tools/deny-rules-under-bypassed-permissions (new page) | `tools` already holds Claude Code harness pages (classifier denials, plugin MCP registration, version-keyed cache); no new category | +| 3 | backend/java/kotlin/compiler-daemon-heap-pressure (new page) | `kotlin` is the Kotlin-only subtree category; the Kotlin compile daemon is Kotlin-only; no new category | +| 4 | infrastructure/agent-orchestration/worktree-isolated-workers (merge) | existing | +| 5 | testing/mocking/extracted-method-this-binding (new page) | the changed artifact is the test's mock choice → `mocking`; frontend has no language-mechanics category and the lesson is not React-specific | +| 6 | infrastructure/agent-orchestration/semantic-conflicts-after-parallel-merge (new page) | the coordinator's integration step owns the merged tree → `agent-orchestration` | +| 7 | infrastructure/agent-orchestration/verify-command-in-a-worker-brief (new page) | brief authoring is `agent-orchestration` (same category as worktree-isolated-workers' brief rules) | +| 8 | qa/document-verification/spec-document-gates (merge) | existing | +| 9 | qa/process/completion-claims (merge) | existing | +| 10 | infrastructure/agent-orchestration/shared-run-state (merge) | existing | +| 11 | infrastructure/config/path-valued-config (merge) | existing | +| 12 | testing/data/test-data-and-isolation (merge) + shared-run-state (coordinator side) | existing | + +No new category was needed. Indexes updated: `wiki/platforms/index.md`, `wiki/infrastructure/index.md`, `wiki/backend/java/index.md`, `wiki/testing/index.md`, `wiki/qa/index.md`, root `INDEX.md` (infrastructure and platforms route lines); `log.md` appended. + +Checks run before the PR: `node scripts/wiki-lint-prohibitions.js wiki`, `node scripts/wiki-structure-checks.js wiki`, body-line counts of every touched page (≤120), and a resolver over every `related:` id, inline `[id]` reference, and page-to-index listing (results in the PR conversation). diff --git a/INDEX.md b/INDEX.md index d644e55..9596ba4 100644 --- a/INDEX.md +++ b/INDEX.md @@ -14,12 +14,12 @@ follow the cross-pointers in their index or take the next matching seeded domain | [databases](wiki/databases/index.md) | **seeded** | Choosing a datastore/database type for a workload (relational vs document vs vector vs graph), designing schemas/tables/keys, choosing or evaluating indexes, writing or optimizing queries, choosing transaction/isolation behavior, surveying live data to derive a rule, verifying additive migrations | | [backend](wiki/backend/index.md) | **seeded** | Server-side application code — language-agnostic (`common/`: API contracts, call-site enumeration before a contract change, idempotency, JWT, timeouts/retries, caching, jobs, transactions in app code, shared state/pools, errors, consuming LLM APIs (completion validation, context budgeting), authoring agent-facing artifacts (binding instruction text, agent tool-surface granularity/parity), MAPE-aligned point-prediction calibration, consuming external-API responses, externally-owned defaults, object-storage references, sync-vs-async integration choice, WebSocket/SSE connection lifecycle) plus stack subtrees: `java/` (JPA, Spring proxies, JVM threads/memory), `node/` (event loop, promises, runtime validation, shutdown), `python/` (GIL/asyncio, pydantic, WSGI/ASGI workers, language traps, packaging data files with `importlib.resources`) | | [frontend](wiki/frontend/index.md) | **seeded** | Web UI code: state placement, rendering performance, in-UI data fetching (races, infinite scroll), auth token handling, forms, XSS-safe output, accessibility, agent-facing tool surfaces (WebMCP) | -| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting), per-environment/path-valued config, multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, completion gates, worktree-isolated workers, autonomous ask-vs-rule decisions, session context/token budgeting) | +| [infrastructure](wiki/infrastructure/index.md) | **seeded** | CI/CD pipelines, secrets in build/deploy, container image builds, rollout/rollback strategy, observability (logs/metrics/alerting), per-environment/path-valued config, multi-agent orchestration (worker liveness signals, shared run state, tmux pane delivery, completion gates, worktree-isolated workers, autonomous ask-vs-rule decisions, session context/token budgeting, the merged-tree gate for parallel branches, the verify command written into a worker brief) | | [testing](wiki/testing/index.md) | **seeded** | Writing or structuring automated tests: level choice, test-before-code ordering, cases/assertions, cross-layer effect scoping, test data, mock decisions, flaky tests (release-process quality → qa) | | [qa](wiki/qa/index.md) | **seeded** | Release-quality process: release gates, regression scoping, bug reports, severity/priority triage, evidence for completion claims, acting on code-review feedback, adversarial review of high-risk diffs, exploratory testing (guarded-path coverage, override matrices), scope-purity gates, sourcing deliverable documents from generated artifacts, verifying the quantitative claims in a document before publishing it, automated verification of document deliverables (spec/RFC gates) (writing automated test code → testing) | | [debugging](wiki/debugging/index.md) | **seeded** | Diagnosing a failure — finding what is wrong and why: reproducing, bisection, hypothesis testing, traces/logs, intermittent failures (fixing the diagnosed fault → its owning domain) | | [security](wiki/security/index.md) | **seeded** | Trust-boundary decisions: input validation, session-vs-token auth choice, per-resource authorization (IDOR), secrets hygiene, dependency trust, PII handling, in-session agent tool exposure (prompt-injection blast radius), the author identity a commit publishes to a public repository, host-compromise triage / incident response (verifying assumed security agents, identifying masquerading processes) (XSS rendering → frontend; CI secrets → infrastructure; JWT implementation → backend/frontend auth) | -| [platforms](wiki/platforms/index.md) | **seeded** | OS-level differences breaking code across macOS/Linux/Windows: shell portability, BSD-vs-GNU CLI, filesystem case/line endings, Unicode normalization in text/file-name matching, commands inspected before execution, background services/cron, invoking prompt-capable CLIs non-interactively, toolchain version pinning | +| [platforms](wiki/platforms/index.md) | **seeded** | OS-level differences breaking code across macOS/Linux/Windows: shell portability, BSD-vs-GNU CLI, filesystem case/line endings, Unicode normalization in text/file-name matching, commands inspected before execution, permission deny rules for bypass-mode agent workers, background services/cron, invoking prompt-capable CLIs non-interactively, toolchain version pinning | | [mobile](wiki/mobile/index.md) | **seeded** | App-side iOS/Android/cross-platform: process death/state survival, offline-first sync, mobile-network calls, store rollout/hotfix strategy, startup time | All ten domains are seeded. New categories grow via `skills/wiki-ingest/SKILL.md`. diff --git a/log.md b/log.md index 3f8265b..ca17a50 100644 --- a/log.md +++ b/log.md @@ -103,3 +103,6 @@ Append-only. Format: `## [YYYY-MM-DD] → Ran 3549 tests … FAILED (failures=1); the failure is tests/x_test.py::test_y (pre-existing) +Done: Ran ≥ 3549, failures ≤ 1, and the only failure is that same test id +``` + +4. **Make the total part of the criterion.** A worker that breaks an import + sees fewer tests and a green-looking run; the `Ran N` against the baseline N + is what catches it ([testing-quality-tests-that-cannot-fail], + [qa-process-completion-claims]). + +## Edge cases + +| Case | Then | +|------|------| +| The repo ships an environment doctor (`scripts/dev_doctor.sh`, `make check-env`) | Put it first on the verify line; when import failures are environmental it names the missing pieces, and the worker has nothing left to call pre-existing | +| The baseline has many failures | List them by test id; a bare `failures=15` lets a new failure hide inside the count | +| The worker reports "environment problem, failures are pre-existing" | Compare its `Ran N` and its failure ids with the baseline line; a mismatch in either is the worker's change, not the environment | +| The tests spawn subprocesses with `sys.executable` | The path form propagates the venv interpreter; a bare name resolves again in the child | +| No venv exists and the system interpreter is the intended one | Still write the path (`/usr/bin/python3`), so a later venv or a different `PATH` cannot change what the brief means | +| The task's deliverable is a new numbered/spec document (RFC, ADR) in a repo with doc-currency gates (a README-count test, a generated-reference `--check`) | Name those gate tests on the task's own verify line and include the files they read (README rows, the routes/registry constant) in the task's scope, not only at integration | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Write `python3 -m unittest … must pass` | Write the venv path plus the baseline `Ran N … failures=M` line | Bare names resolve per session; "pass" has no meaning on a non-green baseline | +| Let the worker measure the baseline itself | Measure it on the base branch with the same command and put it in the brief | A baseline measured on the worker's changed tree is the thing in question | +| Accept "the failures are environmental" from the worker's report | Diff its totals and ids against the brief's baseline | Import errors and regressions both read as "environment" in a self-report | +| Scope a new-document task's brief to the document directory only, and check currency gates only at integration | Include the repo's README-count and generated-reference tests on that task's own verify line from the start | The gates fail deterministically at integration once every task lands; one in-task test run catches a missing registry entry before it costs a full rework round-trip | + +## Sources + +- https://docs.python.org/3/library/venv.html — activation "will prepend that directory to your PATH"; "You don't specifically need to activate a virtual environment, as you can just specify the full path to that environment's Python interpreter when invoking Python" +- https://docs.python.org/3/library/unittest.html — the text runner's `Ran N tests in …` summary followed by `OK` or `FAILED (failures=M, errors=E)`; reproduced 2026-09-03 with a 3-test case: `Ran 3 tests … FAILED (failures=1, errors=1)` +- Field measurement 2026-08-31 (linkly, one worktree): `python3 -m unittest` → Ran 3532, failures=15, errors=18; `.venv/bin/python -m unittest` → Ran 3549, failures=1. All 33 extra failures were `ModuleNotFoundError: jsonschema` and a missing MLIR toolchain, both named by the repo's `scripts/dev_doctor.sh`; the repo's own baseline was one failure, so "must pass" could never have been met +- Field evidence 2026-08-29 (linkly, run `enf0829`): a task briefed to add RFC-0043 was scoped to its document directory only, with the currency gates left for integration; the integration suite failed 10 cases, 9 on `test_readme_currency` plus 1 on `test_plugin_references`, from one missing `RFC_ROUTES["0043"]` entry and its README rows. Fixed and re-run at 3374 tests OK; a verify line naming those two tests inside the doc task would have caught it before integration diff --git a/wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md b/wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md index 99d3c7c..bc23d32 100644 --- a/wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md +++ b/wiki/infrastructure/agent-orchestration/worktree-isolated-workers.md @@ -7,8 +7,8 @@ confidence: verified sources: - https://git-scm.com/docs/git-worktree - https://code.claude.com/docs/en/hooks -last_verified: 2026-08-17 -related: [infrastructure-agent-orchestration-session-completion-gates, infrastructure-agent-orchestration-pane-delivery-confirmation, infrastructure-agent-orchestration-shared-run-state, platforms-shells-command-text-inspected-before-execution, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts] +last_verified: 2026-09-03 +related: [infrastructure-agent-orchestration-session-completion-gates, infrastructure-agent-orchestration-pane-delivery-confirmation, infrastructure-agent-orchestration-shared-run-state, platforms-shells-command-text-inspected-before-execution, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, platforms-tools-deny-rules-under-bypassed-permissions, infrastructure-agent-orchestration-semantic-conflicts-after-parallel-merge, infrastructure-agent-orchestration-verify-command-in-a-worker-brief] --- # Writing the Brief for a Worker Confined to Its Own Worktree @@ -47,6 +47,13 @@ wait loop keeps escalating with no error from the task itself. write commands from the brief inside a worktree and require them to complete without an escalation — one probe costs a minute and a bad brief costs every worker's first phase. +7. **Name every tool-owned gate/state directory (`.dev-loop/`, `.orchestration/`) + as worktree-relative in the brief, before the worker's first write.** A tool + that derives its repository root from `git rev-parse --git-common-dir` (the + main checkout's `.git`, shared by every linked worktree) instead of + `--show-toplevel` (the current worktree's own root) computes its state path + under the main checkout even while running inside a worktree — the relative + rule in the brief is what stops the worker from acting on that path. ## Edge cases @@ -63,6 +70,9 @@ wait loop keeps escalating with no error from the task itself. | A read of a main-root path is redirected to an absolute path (`grep … /x > /tmp/out`) | It fires — the redirect-to-absolute branch matches regardless of what is being read. Redirect to a worktree-relative path | | The brief points the worker at a coordinator-state directory that is gitignored (`.orchestration/`, `.state/`) via a repo-relative path | The path resolves only in the main checkout: `git worktree add` checks out tracked files, so an ignored directory never materializes in a worktree. Substitute the absolute main-checkout path into the brief and state that the directory is gitignored and absent from the worktree — a capable worker otherwise hides the miss by searching for the file instead of failing | | The guardrail is a Bash-command hook and the worker edits files through its native Edit/Write tool | The hook never runs — tool hooks match on the tool name, so a `Bash` matcher does not fire for Edit/Write calls, and an absolute-path edit into the main checkout lands with no block and no log. State in the brief that **all** file operations, whatever the tool, use worktree-relative paths, and `git status` the protected tree before merging any worker's branch — discovery otherwise depends on luck | +| A `worktree_escape` escalation arrives, or the main checkout's `git status --porcelain -uall` shows modifications while workers run | Read the Bash escalation as the visible part and check the main tree in the same step. When it holds files a worker's task owns, transfer them before touching either branch: in the main checkout `git diff -- > `, in that worker's worktree `git apply --check ` then `git apply ` (untracked files: copy them across), then in the main checkout `git checkout -- ` and delete the copied untracked files, and re-run `git status` there to confirm it is clean. Stop the escaping worker before lifting the patch — a live worker keeps writing while you transfer — and when several workers are in flight, attribute the dirty files by mtime: `ls -lT` (or `stat`) against each worker's active window names the one whose own worktree is clean while its files sit dirty in main. A hook that also guards the file tools registers a PreToolUse matcher of `Bash\|Write\|Edit\|MultiEdit` | +| A test unrelated to the task just merged fails, or the integration branch checked out in main shows modifications nobody merged | Treat it as an escape that already landed: `git status --porcelain` in main and in every worktree; a worker whose worktree is clean while main is dirty wrote by absolute path. With the integration branch checked out in main those edits ride into the next merge commit as another task's work, so transfer and clean main (row above) before the next merge, then re-run the failing tests | +| A `worktree_escape` escalation arrives for a write that has **not yet executed** (`rm -rf /.dev-loop`, `mkdir /.dev-loop/gates`) | Deny it — nothing has landed, so there is nothing to transfer — and hand the worker the same path rewritten worktree-relative (`.dev-loop/gates`) to retry with. A write that already landed through a non-Bash tool is the recovery case above, not this one | ## Instead of @@ -73,14 +83,23 @@ wait loop keeps escalating with no error from the task itself. | Designate a shared scratch directory inside the repo for worker output | Place it outside the repo and pass its path as one named variable | A shared in-repo directory is both a guardrail trip and a write race between workers | | Reuse the coordinator's repo-relative path to a gitignored state directory in a worker's prompt template | Expand it to the absolute path at substitution time and note the directory is absent from the worktree | Ignored files exist only where they were created; the relative form silently resolves to a nonexistent path in every worker, and reads of absolute main-root paths pass the guardrail | | Trust a Bash-hook guardrail as the only isolation for workers with native file tools | Pair it with a relative-paths-only instruction in the brief and a pre-merge `git status` of the main checkout | The hook inspects only the tool its matcher names; an Edit-tool write to an absolute main-checkout path passes silently — the worker need not be routing around anything for the escape to happen | +| Answer a worktree-escape escalation and move on | Check the main checkout's `git status` in the same step and transfer any worker-owned changes by patch | The Bash hook sees one channel; file-tool edits into the main checkout raise no escalation and are found only by looking | +| Approve a pre-write `worktree_escape` for a state directory because "the tool needs it" | Deny, and reply with the worktree-relative equivalent of the same path | The tool resolved its root against the shared `.git` common dir; the same directory inside the worktree serves it, and approving lets N workers write one shared state tree in main | +| Rely on workers complying with a relative-paths-only instruction for edits made through the native Edit/Write tools, when the hook matcher cannot be widened to cover those tools | Route the worker's edits through Bash (heredoc, `sed`, worktree-relative paths) instead of the Edit/Write tools, so the existing Bash-only guard covers every write, and run `git status --porcelain` in main after each task | Edit/Write bypass the guard regardless of instruction compliance, as the field evidence below shows; moving the edit itself onto the channel the guard inspects removes the failure path instead of relying on the worker following a rule | ## Sources - https://git-scm.com/docs/git-worktree — linked worktrees are separate checkouts sharing one repository; each has its own working directory -- https://code.claude.com/docs/en/hooks — tool-event hook matchers filter on the tool name ("`Bash` matches only the Bash tool"); a hook registered for Bash does not run on Edit/Write calls +- https://code.claude.com/docs/en/hooks — tool-event hook matchers filter on the tool name ("`Bash` matches only the Bash tool"); a hook registered for Bash does not run on Edit/Write calls; the documented matcher form `Edit|Write` fires on both file tools +- https://git-scm.com/docs/git-apply — `--check`: "Instead of applying the patch, see if the patch is applicable to the current working tree and/or the index file and detects errors" +- https://git-scm.com/docs/git-rev-parse — `--show-toplevel` shows the top-level directory of the working tree; `--git-common-dir` shows `$GIT_COMMON_DIR` (the repository shared by all linked worktrees). Reproduction 2026-09-03 (git 2.x, macOS): from a linked worktree the first returned the worktree's own root and the second the main checkout's `.git` +- Field evidence 2026-08-24 (linkly, second orchestration run): escalations lo-19 (`rm -rf /.dev-loop`) and lo-22 (`mkdir /.dev-loop/gates`) arrived back to back from workers whose tool resolved its state directory against the main checkout; both were denied with the worktree-relative path in the reply and the workers proceeded without touching main +- Field reproduction 2026-08-21 (dev-loop orchestrate, task hide-color-nudge): on a `worktree_escape` escalation, the main checkout held three files belonging to worker lo-2 whose own worktree was clean; `git diff > patch` in main, `git apply --check` (rc 0) and `git apply` in lo-2's worktree, then `git checkout --` in main left the main tree clean (`MAIN_CLEAN` confirmed) with the work preserved on the worker branch +- Field evidence 2026-08-25 (linkly t112): a worker's cwd was its worktree, yet `diagnostics.py` and `lower.py` in the main checkout carried mtimes 14:23–14:27 when checked at 14:29, naming the worker active in that window; after stopping it and cleaning main by patch transfer, all 11 diagnostic test failures attributed to the just-merged task disappeared - Field observation 2026-08-17 (linkly run, worker under a `worktree_escape` Bash-hook guard): the worker modified two `examples/*.lnpl` files in the **main checkout** via its native Edit tool with absolute paths — no block, no log; discovered only when the coordinator's `git pull` failed on local changes (contents happened to match the merged branch, so no damage). The same paths written via Bash redirection would have escalated - Field reproduction 2026-08-05 (groundwork guardrails 1.0.0 `hooks/bash-guard.sh`, `worktree_escape` rule, macOS): from a linked worktree, `cp ./a /b` and `echo z > /f` were both stopped; `cat /f`, `ls /.orchestration`, and `grep -n x /f` all passed. The rule matches an absolute main-root mention together with a write verb (`rm|mv|cp|tee|mkdir|touch|install|dd`) or a redirect to an absolute path - Field evidence 2026-08-06 (dev-loop orchestrate, Wave 2 worker consuming an upstream worktree's FINDINGS file): a read-only `awk`/`grep` verification and a `git status` check each raised `worktree_escape` as `ask` and stopped the coordinator's watch with exit 5; both were confirmed read-only and approved. This rule version fired on reads, unlike the 1.0.0 reproduction above where bare `cat`/`ls`/`grep` passed — the read/write asymmetry in the Do-this table is version-dependent, so probe before fanning out +- Field evidence 2026-08-26 (linkly): task t119's read-only Bash diff tripped the guardrail as `ask`, while task t113's three native Edit-tool writes all passed silently and landed in the main checkout; adding a `git status --porcelain` post-check in main after each task caught the third write immediately - Local reproduction 2026-08-06 (groundwork guardrails 1.2.0 `hooks/bash-guard.sh`, `worktree_escape`, macOS), run from a linked worktree against a sibling worktree's path: `grep -n foo //FINDINGS.md`, `awk 'NR<5' //FINDINGS.md`, `cat /README.md` and `git -C / status --short` all passed; `mkdir -p .claude/tmp && grep -n foo //FINDINGS.md`, `cp /README.md ./x` and `grep -n foo //FINDINGS.md > /tmp/out` each returned `ask`. Reading the rule confirms why: it fires when a main-root mention survives the strip **and** `(rm|mv|cp|tee|mkdir|touch|install|dd)` or a redirect to an absolute path matches anywhere in the command — the two tests are independent - Field context: a parallel run stalled at the same phase for two workers whose brief's `` named a main-checkout absolute path; the coordinator's wait loop returned its escalation status repeatedly. Rewriting the contract to worktree-relative paths let the remaining workers record their plans locally - Local reproduction 2026-08-13 (git 2.x, macOS): in a repo with `.gitignore` containing `.orchestration/` and a populated `.orchestration/status/`, `git worktree add ../wt1 -b wt1` produced a worktree where `ls ../wt1/.orchestration` → No such file or directory and `cat .orchestration/status/run.json` from the worktree cwd failed; `git ls-files .orchestration` → 0 tracked files. Field context: dev-loop's own `templates/session-prompt.md` handed workers `.orchestration/…` relative paths, and workers located the files by searching the main checkout rather than failing diff --git a/wiki/infrastructure/config/path-valued-config.md b/wiki/infrastructure/config/path-valued-config.md index 8f322fb..688834e 100644 --- a/wiki/infrastructure/config/path-valued-config.md +++ b/wiki/infrastructure/config/path-valued-config.md @@ -8,7 +8,7 @@ sources: - https://man7.org/linux/man-pages/man5/systemd.exec.5.html - https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html - https://12factor.net/config -last_verified: 2026-08-04 +last_verified: 2026-09-03 related: [infrastructure-config-environment-config, platforms-processes-background-services, platforms-environment-path-resolution, backend-python-boundaries-runtime-validation, backend-node-boundaries-runtime-validation] --- @@ -19,7 +19,8 @@ related: [infrastructure-config-environment-config, platforms-processes-backgrou A service reads a filesystem path from an env var or config key (an input/spool directory, an output dir, a data file, a socket), and the process is started by something that owns the working directory: launchd, systemd, cron, a container -entrypoint, a supervisor, or a CI runner. +entrypoint, a supervisor, or a CI runner. Also a CLI whose required flag (`--out`, +`--spool`) names the directory it writes into. General per-environment config shape → [infrastructure-config-environment-config]. Locating *binaries* rather than data → [platforms-environment-path-resolution]. @@ -60,7 +61,8 @@ if not p.is_absolute(): | The unit sets no `WorkingDirectory` | The CWD is the manager's default, not the install directory: `/` for launchd and for systemd **system** units, and the user's home directory for systemd **user** units. The same relative path resolves to three different places across those managers | | A container image sets `WORKDIR` | The CWD is defined but owned by the image, so it changes with a base-image or Dockerfile edit that no config review covers — keep the absolute-only rule | | The relative path happens to work in staging | The launcher there sets a `WorkingDirectory` that production's does not; treat a passing relative path as an accident, not as validation | -| Tests exercise the loader with relative paths | Assert the rejection: pass `"./data/x"`, `"x"`, and `"relative/path"` and require the exception type and the received value in the message ([testing-quality-minimum-case-set]) | +| Tests exercise the loader with relative paths | Assert the rejection: pass `""`, `"./data/x"`, `"x"`, and `"relative/path"` and require the exception type and the received value in the message ([testing-quality-minimum-case-set]) | +| The path arrives through a CLI flag declared required (argparse `required=True`, the equivalent in any parser) | `required` checks presence, not content: `--out ""` satisfies it, and `os.path.realpath("")`/`abspath("")` resolve to the process CWD, so output lands wherever the command happened to run. Reject the empty string before resolving (`if not args.out: parser.error("--out must be a non-empty absolute path")`) and keep `""` in the rejection tests | | The value arrives already absolute but with a symlink or `..` | Resolve it once at startup and store the resolved form, so every later component agrees on one path | ## Instead of @@ -71,11 +73,15 @@ if not p.is_absolute(): | `os.chdir()` at startup so relative paths work | Reject the relative value and let the operator supply an absolute one | A chdir makes every path in the process depend on startup ordering, and it hides the misconfiguration instead of reporting it | | Log a warning and continue when the configured directory is absent | Crash at startup with the received value | A scan of a missing directory yields an empty result, so the service reports a healthy idle state forever while processing nothing | | Default the key to `./data/` for developer convenience | No default; validate presence and absoluteness at startup | The dev-friendly relative default becomes the production value the moment the real one is missing | +| Rely on a parser's `required=True` to guarantee a usable output directory | Reject empty and non-absolute values yourself before `realpath` | Presence is all the parser checks; an empty value resolves to the CWD with no error | ## Sources - https://man7.org/linux/man-pages/man5/systemd.exec.5.html — `WorkingDirectory=`: "If not set, defaults to the root directory when systemd is running as a system instance and the respective user's home directory if run as user" - https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html — LaunchAgent plist keys; `WorkingDirectory` is documented in `launchd.plist(5)` as "This optional key is used to specify a directory to chdir(2) to before running the job" — optional, with no directory inherited from the installer - https://12factor.net/config — config lives in the environment and is what varies between deploys +- https://docs.python.org/3/library/argparse.html#required — "if an option is marked as required, parse_args() will report an error if that option is not present at the command line" — presence only +- https://docs.python.org/3/library/os.path.html#os.path.abspath — "Return a normalized absolutized version of the pathname path. On most platforms, this is equivalent to calling normpath(join(os.getcwd(), path))" — the empty path is the CWD +- Local reproduction 2026-09-03 (CPython 3): `parse_args(["--out", ""])` → `Namespace(out='')` and `os.path.realpath('')` → the current directory. Field reproduction 2026-08-28 (a code-generation CLI): `generate openapi SRC --out ""` returned rc 0 and wrote `openapi.json` into the repository root; an independent test-quality audit found it and confirmed the new rejection tests fail with the fix reverted - Local reproduction 2026-08-04 (macOS 25.1, launchd): a LaunchAgent with `ProgramArguments` and `RunAtLoad` and **no** `WorkingDirectory` key recorded `cwd=/` and `PWD=/`; a relative `./data/signals` lookup from that job reported "No such file or directory". An independently launchd-spawned process (`loginwindow`) also reports cwd `/` under `lsof` - Local reproduction 2026-08-04 (CPython 3.14.6): `glob.glob("/nonexistent-xyz/*.json")` returns `[]` and `Path("/nonexistent-xyz").glob("*.json")` yields nothing, both without raising, while `os.listdir` on the same path raises `FileNotFoundError` — the empty-scan result is what makes a bad path silent. `Path("~/data").expanduser().is_absolute()` is `True` while `Path("./data").expanduser().is_absolute()` is `False` diff --git a/wiki/infrastructure/index.md b/wiki/infrastructure/index.md index f781a71..ac6c774 100644 --- a/wiki/infrastructure/index.md +++ b/wiki/infrastructure/index.md @@ -13,15 +13,17 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| | [control-signals-vs-primary-artifacts](agent-orchestration/control-signals-vs-primary-artifacts.md) | An orchestrator is about to restart, discard, merge, or keep waiting on a worker based on a status file, a watcher's exit code, or a heartbeat; a monitor reports a worker dead while it is committing; a worker's status write produced no output and you must decide whether it landed; distinguishing alive-and-progressing from stalled from dead; several workers went quiet at once while every liveness check passes (usage-limit pause); a dispatch issued right after a worker's done signal fails runtime-unavailable | -| [shared-run-state](agent-orchestration/shared-run-state.md) | Several agent/worker sessions coordinate through files in one repository (status directory, briefs, escalations, claim files); choosing the path layout for that state; starting an orchestration in a repo that may already have one running; a watcher woke on a task id it did not create; the default branch moved during a run; a coordinator is about to reset a task's status file while re-delivering a prompt | +| [shared-run-state](agent-orchestration/shared-run-state.md) | Several agent/worker sessions coordinate through files in one repository (status directory, briefs, escalations, claim files); choosing the path layout for that state; starting an orchestration in a repo that may already have one running; a watcher woke on a task id it did not create; the default branch moved during a run; a coordinator is about to reset a task's status file while re-delivering a prompt; resuming a run whose coordinator "died" while `tmux ls` shows only workers; escalation records arriving in bulk with test-runner temp paths (`bats-run-*`) | | [pane-delivery-confirmation](agent-orchestration/pane-delivery-confirmation.md) | An orchestrator drives another program through a terminal multiplexer (`tmux send-keys` + `capture-pane`) and must decide whether the input was consumed, retry, or escalate; a pane diff is being used as delivery evidence; the target echoes but never runs the input; deciding *where* in a pane capture to search for a collapsed paste marker whose position depends on payload size, or what to report when the pane's input-box chrome cannot be located at all | | [session-completion-gates](agent-orchestration/session-completion-gates.md) | Writing a Stop/completion hook that blocks a worker session from ending while its phase is non-terminal; the gate fires on a worker that followed its own prompt; deciding the terminal phase set, the unknown-phase default, and how the gate bounds its own repetition; you are the worker the gate repeats on at an instructed pause and are deciding whether to advance your phase to silence it | | [dispatching-after-a-completion-report](agent-orchestration/dispatching-after-a-completion-report.md) | A worker reported completion and the orchestrator wants to hand that same terminal or runtime slot its next task; a start/dispatch call fails with a runtime-unavailable-class error moments after a completion report; a task reached a terminal `failed` status with no worker having worked on it; deciding a settled dispatch's next owner (transfer, release, or retain) and how to retry a failed start without spending the task's attempt budget | | [unattended-worker-questions](agent-orchestration/unattended-worker-questions.md) | A worker agent raises a question through its own interactive UI (a numbered chooser, a confirmation/trust/re-auth screen) with no human at that terminal; a worker is flagged stalled with a live terminal and no task-level error; a worker reports a decision it assumed rather than asked; designing the channel a worker uses to ask its coordinator for a decision | | [usage-limit-paused-workers](agent-orchestration/usage-limit-paused-workers.md) | Several workers billed to one account go quiet within minutes of each other while every liveness check passes; a worker's terminal shows a `You've hit your session/weekly/Opus limit · resets …` notice; deciding whether to restart, replace, or wait on a worker with no task-level error; writing the prompt that resumes a worker after a usage window resets | -| [worktree-isolated-workers](agent-orchestration/worktree-isolated-workers.md) | Authoring the brief/output contract for parallel workers each confined to its own git worktree; workers stall at the same phase with no task-level error; deciding where shared or produced artifacts live and which direction (read vs write) a worktree guardrail stops; a guardrail escalates on read-only access to another worktree; the isolation guard is a Bash-command hook while workers also edit files with native Edit/Write tools | +| [worktree-isolated-workers](agent-orchestration/worktree-isolated-workers.md) | Authoring the brief/output contract for parallel workers each confined to its own git worktree; workers stall at the same phase with no task-level error; deciding where shared or produced artifacts live and which direction (read vs write) a worktree guardrail stops; a guardrail escalates on read-only access to another worktree; the isolation guard is a Bash-command hook while workers also edit files with native Edit/Write tools; a worktree-escape escalation arrived or the main checkout shows worker-made edits that must be transferred by patch; a worker's tool resolves its `.dev-loop`/state directory against the main checkout from inside a worktree, or a pre-write escape escalation names a state directory | | [autonomous-decision-rulings](agent-orchestration/autonomous-decision-rulings.md) | An unattended agent hits a decision its plan does not answer and must choose between stopping to ask and proceeding; a run stalls on questions no human needed to see; deciding which decision categories require a human; recording autonomous decisions for audit; resuming after interruption/compaction without re-dispatching completed work | | [session-context-token-budget](agent-orchestration/session-context-token-budget.md) | Planning or running long-lived coordinator/worker agent sessions and deciding when to compact or clear context; a run's cost is dominated by cache reads; screenshots or large file reads are entering a long-lived session; choosing slot counts / per-phase token budgets for an orchestrated run | +| [semantic-conflicts-after-parallel-merge](agent-orchestration/semantic-conflicts-after-parallel-merge.md) | Integrating two or more parallel branches (worktree workers, one wave's tasks) that are each green and merge with no textual conflict, before dispatching dependent work; one branch adds a `match`/`switch` arm or mapping row while another adds the enum variant/case it must cover; the merged tree fails to compile (Rust `E0004`, an exhaustiveness error) or a test neither branch touched fails; deciding who commits the resolution | +| [verify-command-in-a-worker-brief](agent-orchestration/verify-command-in-a-worker-brief.md) | Writing the test/verify command and done criterion in a brief or prompt for a subagent or worker; about to write a bare interpreter name (`python3`, `pytest`, `node`) plus "must pass"; the repo has a venv or version-managed toolchain; the base branch is not fully green; a worker reports mass import failures as pre-existing environment problems; the task's deliverable is a new numbered/spec document (RFC, ADR) in a repo with README-count or generated-reference currency gates and the brief scopes the task to the document directory only | ## ci-cd diff --git a/wiki/platforms/environment/path-resolution.md b/wiki/platforms/environment/path-resolution.md index 2bb8bc2..ac85621 100644 --- a/wiki/platforms/environment/path-resolution.md +++ b/wiki/platforms/environment/path-resolution.md @@ -12,7 +12,7 @@ sources: - https://www.sudo.ws/docs/man/sudoers.man/ - https://docs.brew.sh/FAQ last_verified: 2026-08-04 -related: [platforms-toolchains-version-management, platforms-processes-background-services, platforms-shells-env-var-off-switches, platforms-toolchains-compiler-sysroot-on-macos] +related: [platforms-toolchains-version-management, platforms-processes-background-services, platforms-shells-env-var-off-switches, platforms-toolchains-compiler-sysroot-on-macos, infrastructure-agent-orchestration-verify-command-in-a-worker-brief] --- # The Wrong Binary (or None) Resolving From PATH diff --git a/wiki/platforms/environment/unicode-text-matching.md b/wiki/platforms/environment/unicode-text-matching.md index cccb83b..f0543e0 100644 --- a/wiki/platforms/environment/unicode-text-matching.md +++ b/wiki/platforms/environment/unicode-text-matching.md @@ -9,8 +9,8 @@ sources: - https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/ - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html -last_verified: 2026-07-30 -related: [platforms-environment-timezone-and-locale, platforms-filesystems-paths-case-and-line-endings, qa-document-verification-spec-document-gates] +last_verified: 2026-09-03 +related: [platforms-environment-timezone-and-locale, platforms-filesystems-paths-case-and-line-endings, qa-document-verification-spec-document-gates, platforms-tools-bsd-vs-gnu-cli] --- # Matching Non-ASCII Text with grep and Regex @@ -48,6 +48,7 @@ len('아닌') NFC = 2 code points, NFD = 5 # jamo L+V+T decomposition |------|------| | The pattern must survive both normalization forms | Match on a substring that contains no combining sequence (an ASCII token, an id, a number), or normalize the input through a filter before grep | | A pattern with a character class or quantifier over non-ASCII text | Test the exact pattern against a known-matching line first: BSD and GNU regex engines differ in multi-byte class handling, so a class that works on one userland can misfire on the other ([platforms-tools-bsd-vs-gnu-cli]) | +| A quantifier follows a bare multibyte literal (`─{3,}`, `가+`) and the pattern may run under `LC_ALL=C`/POSIX (a minimal CI image, cron, `env -i`, a hook that pins the C locale) | Group the literal — `(─){3,}` — and run the pattern once under `LC_ALL=C` before accepting it: a byte-oriented locale binds the quantifier to the **last byte** of the UTF-8 sequence, so `─{3,}` matches one `─` followed by two stray `0x80` bytes and misses three `─`, while the grouped form matches in both locales | | Zero hits and the cause is unclear | Print the code points of both the pattern and the target line (`python3 -c "print([hex(ord(c)) for c in open(f).read()])"`) before concluding the text is missing — it separates "word absent" from "different code points" | | The text is user-supplied and used as a key or a dedup identifier | Normalize to NFC at the trust boundary on write, so later equality and search compare one form | | The search happens inside a database rather than a file | Normalization is applied by the writer, not the engine — same rule: normalize on write, search the stored form | @@ -59,6 +60,7 @@ len('아닌') NFC = 2 code points, NFD = 5 # jamo L+V+T decomposition | Write a stem prefix (`아니`) expecting it to match its inflections | Copy the literal form present in the text, or enumerate the alternatives | Precomposed syllables are distinct code points, so the stem is not a substring of the inflected word | | Read a 0-hit grep as "the requirement is absent from the document" | Compare the code points of the pattern and the line before acting | 0 hits also means "different normalization form" or "different syllable" — an absence conclusion from that is a false negative | | Compare two file-name lists byte-for-byte across machines | Normalize both lists to NFC in code, then diff | Producers store different forms of the same name; APFS lookup hides this locally but a byte diff does not | +| Accept a `X{n,}` pattern over a non-ASCII literal because it matches in your UTF-8 terminal | Group it as `(X){n,}` and test it under `LC_ALL=C` | The runner's locale decides what the quantifier binds to; a C-locale runner repeats the last byte instead of the character and reports no error | ## Sources @@ -66,3 +68,5 @@ len('아닌') NFC = 2 code points, NFD = 5 # jamo L+V+T decomposition - https://www.unicode.org/versions/Unicode16.0.0/core-spec/chapter-3/ — §3.12 Conjoining Jamo Behavior: 11,172 precomposed Hangul syllables from `SBase = U+AC00` decompose algorithmically into L/V/T jamo - https://developer.apple.com/library/archive/documentation/FileManagement/Conceptual/APFS_Guide/FAQ/FAQ.html — APFS preserves the file name's normalization and is normalization-insensitive via hashes of the normalized form; HFS+ stores the normalized form - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/grep.html — grep matches patterns against input lines by the specified regular-expression rules; no canonical-equivalence folding is specified +- Local reproduction 2026-09-03 (macOS, BSD grep 2.6.0-FreeBSD and BSD sed): `printf '───\n' | LC_ALL=C grep -cE '─{3,}'` → 0, the same under `LC_ALL=en_US.UTF-8` → 1, and the grouped `(─){3,}` → 1 in both locales; `printf '\xe2\x94\x80\x80\x80\n' | LC_ALL=C grep -cE '─{3,}'` → 1 (one `─` plus two bare `0x80` bytes), which is the quantifier binding to the last byte. `sed -E 's/─{3,}/X/'` under C left `───` unchanged while `s/(─){3,}/X/` replaced it. GNU grep was not installed on the machine, so the GNU result is untested here — probe both userlands per [platforms-tools-bsd-vs-gnu-cli] +- Field context 2026-08-25 (dev-loop, review t1-detect-r1 finding F1): a `─{3,}` rule-line detector passed its first review because it was correct in the author's UTF-8 shell; run under a C locale it matched nothing and the script silently took its old code path; grouping to `(─){3,}` in both scripts fixed it diff --git a/wiki/platforms/index.md b/wiki/platforms/index.md index 8200fae..7f7d4c8 100644 --- a/wiki/platforms/index.md +++ b/wiki/platforms/index.md @@ -32,6 +32,7 @@ Match your situation to a "load when" line; load only matching pages. | [plugin-mcp-server-registration](tools/plugin-mcp-server-registration.md) | A plugin-bundled MCP server is missing from `/mcp` or its tools are uncallable while the plugin's skills work; deciding whether the fault is registration, config shape, or the server itself; about to change a plugin `.mcp.json` (bare map vs `mcpServers` wrapper, `command`/`args`/`env`) to make a server appear; relaying a shell variable into a server with `${VAR}` and the server reads the wrong value; a plugin updated mid-session and its server still runs the old path | | [version-keyed-artifact-cache](tools/version-keyed-artifact-cache.md) | Shipping a code update to a distribution system that caches artifacts by a version string (a Claude Code marketplace plugin, or any tag-pinned cache) and the update runs but the old behavior persists; deciding why `/plugin update` reports "at latest" yet new code never runs; locating and clearing a stale `~/.claude/plugins/cache////` | | [unpacked-extension-source-reload](tools/unpacked-extension-source-reload.md) | You edited a file of an unpacked Chromium extension (content script, service worker, manifest) and are about to judge the change in the browser, or the edit appears to have no effect; deciding which of extension-reload and host-page-reload a given file requires; adding a running-build version marker; automating the `chrome://extensions` reload control from an agent or CDP script | +| [deny-rules-under-bypassed-permissions](tools/deny-rules-under-bypassed-permissions.md) | Launching Claude Code workers with permissions bypassed (`--dangerously-skip-permissions`, `bypassPermissions`) and one command class must be blocked mechanically rather than by prose (`git stash`, `git push`, `rm -rf`); a `permissions.allow` entry for a bypass-mode session does nothing; choosing which settings file carries a deny; writing a deny that holds across compound commands and stripped wrapper prefixes (`timeout`, `nice`, `VAR=x`) and knowing which wrappers (`env`, `sh -c`) are not stripped | ## environment @@ -39,7 +40,7 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| | [timezone-and-locale](environment/timezone-and-locale.md) | Date/time or text-processing code behaves differently across machines (passes locally, fails in CI or vice versa); a cron/scheduled job fires at the wrong hour or double-fires/skips around DST; reviewing code that formats, parses, or compares dates or strings; writing tests that touch time; building case-insensitive keys, sorted output, or number parsing that must agree across machines | -| [unicode-text-matching](environment/unicode-text-matching.md) | A grep/regex pattern over non-ASCII text (Korean/Japanese/accented Latin/emoji) returns zero hits on text you can see; writing a pattern that must match an inflected or precomposed word; a search or name comparison works on one machine and misses after the file crossed an OS/archive/editor boundary; deciding where to normalize (NFC/NFD) user-supplied text used as a key | +| [unicode-text-matching](environment/unicode-text-matching.md) | A grep/regex pattern over non-ASCII text (Korean/Japanese/accented Latin/emoji) returns zero hits on text you can see; writing a pattern that must match an inflected or precomposed word; a search or name comparison works on one machine and misses after the file crossed an OS/archive/editor boundary; deciding where to normalize (NFC/NFD) user-supplied text used as a key; a quantifier applied to a non-ASCII literal (`─{3,}`) must also hold under `LC_ALL=C`/POSIX | | [path-resolution](environment/path-resolution.md) | "command not found" though the tool is installed; a different version runs than the one installed; sudo/CI/cron/GUI apps/ssh can't find a command the interactive shell finds; two installations of the same tool conflict; a package manager reports a tool installed yet no PATH lookup finds it (Homebrew keg-only/unlinked); deciding how a script should locate its correctness-critical tools | ## filesystems diff --git a/wiki/platforms/toolchains/compiler-sysroot-on-macos.md b/wiki/platforms/toolchains/compiler-sysroot-on-macos.md index 595d0ed..b48c682 100644 --- a/wiki/platforms/toolchains/compiler-sysroot-on-macos.md +++ b/wiki/platforms/toolchains/compiler-sysroot-on-macos.md @@ -11,7 +11,7 @@ sources: - https://clang.llvm.org/docs/DiagnosticsReference.html - https://discourse.llvm.org/t/stdio-h-not-found-on-mac-how-to-add-system-headers-includes-into-clang/77604 last_verified: 2026-08-29 -related: [platforms-toolchains-version-management, platforms-environment-path-resolution, debugging-signals-reading-error-messages] +related: [platforms-toolchains-version-management, platforms-environment-path-resolution, debugging-signals-reading-error-messages, infrastructure-agent-orchestration-verify-command-in-a-worker-brief] --- # A Non-Apple Compiler Resolving the macOS SDK diff --git a/wiki/platforms/toolchains/version-management.md b/wiki/platforms/toolchains/version-management.md index b925512..2a436be 100644 --- a/wiki/platforms/toolchains/version-management.md +++ b/wiki/platforms/toolchains/version-management.md @@ -10,7 +10,7 @@ sources: - https://mise.jdx.dev/configuration.html - https://docs.npmjs.com/cli/v11/configuring-npm/package-json last_verified: 2026-07-10 -related: [platforms-processes-background-services, platforms-shells-portable-shell-scripts, platforms-toolchains-compiler-sysroot-on-macos, platforms-toolchains-environment-resync-removes-undeclared-packages] +related: [platforms-processes-background-services, platforms-shells-portable-shell-scripts, platforms-toolchains-compiler-sysroot-on-macos, platforms-toolchains-environment-resync-removes-undeclared-packages, infrastructure-agent-orchestration-verify-command-in-a-worker-brief] --- # Pinning Tool Versions So Every Machine Runs the Same Toolchain diff --git a/wiki/platforms/tools/agent-permission-classifier-denials.md b/wiki/platforms/tools/agent-permission-classifier-denials.md index 9d75bb3..f9bf3ad 100644 --- a/wiki/platforms/tools/agent-permission-classifier-denials.md +++ b/wiki/platforms/tools/agent-permission-classifier-denials.md @@ -9,7 +9,7 @@ sources: - https://github.com/anthropics/claude-code/issues/58222 - https://github.com/anthropics/claude-code/issues/64128 last_verified: 2026-08-12 -related: [platforms-tools-harness-mediated-tool-results, platforms-shells-command-text-inspected-before-execution, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, infrastructure-agent-orchestration-worktree-isolated-workers] +related: [platforms-tools-harness-mediated-tool-results, platforms-shells-command-text-inspected-before-execution, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, infrastructure-agent-orchestration-worktree-isolated-workers, platforms-tools-deny-rules-under-bypassed-permissions] --- # A Classifier Denies a Tool Call the Agent Was Told to Make diff --git a/wiki/platforms/tools/bsd-vs-gnu-cli.md b/wiki/platforms/tools/bsd-vs-gnu-cli.md index 21181ef..d410848 100644 --- a/wiki/platforms/tools/bsd-vs-gnu-cli.md +++ b/wiki/platforms/tools/bsd-vs-gnu-cli.md @@ -10,7 +10,7 @@ sources: - https://man.freebsd.org/cgi/man.cgi?sed(1) - https://man.freebsd.org/cgi/man.cgi?seq(1) last_verified: 2026-07-10 -related: [platforms-shells-portable-shell-scripts] +related: [platforms-shells-portable-shell-scripts, platforms-environment-unicode-text-matching] --- # Same Command Name, Different Userland: BSD (macOS) vs GNU (Linux) Flags diff --git a/wiki/platforms/tools/deny-rules-under-bypassed-permissions.md b/wiki/platforms/tools/deny-rules-under-bypassed-permissions.md new file mode 100644 index 0000000..186ca84 --- /dev/null +++ b/wiki/platforms/tools/deny-rules-under-bypassed-permissions.md @@ -0,0 +1,88 @@ +--- +id: platforms-tools-deny-rules-under-bypassed-permissions +domain: platforms +category: tools +applies_to: [claude-code, agent-harness] +confidence: verified +sources: + - https://code.claude.com/docs/en/permission-modes + - https://code.claude.com/docs/en/permissions +last_verified: 2026-09-03 +related: [platforms-tools-agent-permission-classifier-denials, infrastructure-agent-orchestration-worktree-isolated-workers, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, backend-common-llm-binding-instructions-for-agents, testing-quality-checks-that-cannot-pass] +--- + +# Blocking One Command Class for a Worker That Runs With Permissions Bypassed + +## When this applies + +You are launching Claude Code worker sessions in `bypassPermissions` mode +(`--dangerously-skip-permissions`, `--permission-mode bypassPermissions`) and one +command class must stay off-limits mechanically — `git stash` against a stash +stack shared across worktrees, `git push`, `rm -rf` — because a sentence in the +prompt or CLAUDE.md is the only thing currently forbidding it. Also when a +`permissions.allow` rule you added for a bypass-mode worker appears to do nothing. + +## Do this + +1. **Write the boundary as a `permissions.deny` rule in the worktree's + `.claude/settings.local.json`**, not as prose: + +```json +{ "permissions": { "deny": ["Bash(git stash:*)", "Bash(git push:*)"] } } +``` + + Deny rules block in every mode, including `bypassPermissions`; allow rules + have no effect in that mode. A deny at any settings level cannot be undone by + another level or by `--allowedTools`. + +2. **Choose the file by who must be unable to relax the rule:** + +| Boundary | Put the deny in | +|----------|-----------------| +| One run's workers on a machine you control | the worktree's `.claude/settings.local.json` (per checkout, gitignored) | +| Every clone of the repo | `.claude/settings.json` (committed) | +| Every session of this user, any repo | `~/.claude/settings.json` | +| Must hold even if the agent edits project or user settings | managed settings — the docs' example is a managed deny that `--allowedTools` cannot override | + +3. **Write the pattern per subcommand — compound commands are split before + matching.** The harness splits on `&&`, `||`, `;`, `|`, `|&`, `&`, and + newlines and matches each piece on its own, so `Bash(git stash:*)` also + catches `cd x && git stash`; conversely an allow must match every piece. + +4. **Count on the documented wrapper set being stripped, and on nothing else.** + Before matching, a leading `timeout`, `time`, `nice`, `nohup`, `stdbuf`, the + builtins `command`/`builtin`, zsh `noglob`, and a leading `VAR=value` + assignment of known-safe variables are removed. `env cmd`, `sh -c 'cmd'`, + `xargs cmd`, and `bash script.sh` are not — add a deny entry for each wrapper + shape the boundary must hold against (`Bash(sh -c:*)`), or record that those + shapes stay covered only by the prose rule. + +5. **Probe the rule from inside one worker before fanning out.** Launch a + session in the intended mode and have it run the forbidden command in its + plainest form and in a compound form; require the denial for both. A rule + read from the docs has not been proven against the version installed + ([testing-quality-checks-that-cannot-pass] — a control in each direction). + +## Edge cases + +| Case | Then | +|------|------| +| The worker must still run the read-only members of the family (`git stash list`) | Deny the mutating subcommands by prefix — `Bash(git stash push:*)`, `Bash(git stash pop:*)`, `Bash(git stash drop:*)`, `Bash(git stash clear:*)` — rather than the whole family | +| The effect is reachable through a non-Bash tool (an MCP tool that runs git, a file write into `.git/`) | A `Bash(...)` rule covers only the Bash tool; deny that tool's name too, or add a PreToolUse hook that matches it ([infrastructure-agent-orchestration-worktree-isolated-workers]) | +| The worker adds its own `allow` entry to get past the boundary | It changes nothing — allow is inert in bypass mode and deny wins at every level; the worker's correct move is to report the denial ([infrastructure-agent-orchestration-control-signals-vs-primary-artifacts]) | +| The task genuinely needs the denied command | Edit the settings file from outside the worker and re-prompt it; a denial is not a cue to reach the same effect by another command | +| The rule must survive the session but no settings file is acceptable in the repo | `~/.claude/settings.json` at user scope; a boundary stated only in conversation is lost at compaction ([platforms-tools-agent-permission-classifier-denials]) | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Forbid `git stash` in the worker prompt and rely on compliance | Add `Bash(git stash:*)` to `permissions.deny` in the worktree settings | Prose is advisory under pressure; a deny rule is enforced by the harness in every mode | +| Constrain a bypass-mode worker with a `permissions.allow` list | Use `deny` for the boundary | Allow rules have no effect in `bypassPermissions` | +| Assume `env FOO=1 git stash` is caught because `timeout git stash` is | Add the wrapper shape to the deny list | Only the documented wrappers and `VAR=value` prefixes are stripped; `env` is not on the list | + +## Sources + +- https://code.claude.com/docs/en/permission-modes — "Deny rules block in every mode, including `bypassPermissions`. … Allow rules have no effect in `bypassPermissions`" +- https://code.claude.com/docs/en/permissions — "If a tool is denied at any level, no other level can allow it. For example, a managed settings deny can't be overridden by `--allowedTools`"; deny rules from any scope are evaluated before allow rules; "The recognized command separators are `&&`, `||`, `;`, `|`, `|&`, `&`, and newlines. A rule must match each subcommand independently"; "The stripped wrappers are `timeout`, `time`, `nice`, `nohup`, and `stdbuf`, plus the shell builtins `command` and `builtin`, and zsh's `noglob`"; "also strips a leading assignment of certain known-safe environment variables" +- Field context 2026-09-02 (dev-loop orchestration, workers launched with permissions bypassed): the stash stack shared across worktrees was protected by a prose rule alone; the deny form above was adopted after the docs confirmed deny applies under bypass. The session's first draft listed `env` among the stripped wrappers — the docs' list does not include it, which is why step 4 names it diff --git a/wiki/qa/document-verification/spec-document-gates.md b/wiki/qa/document-verification/spec-document-gates.md index 0cf00fb..6d9d98c 100644 --- a/wiki/qa/document-verification/spec-document-gates.md +++ b/wiki/qa/document-verification/spec-document-gates.md @@ -14,7 +14,7 @@ sources: - https://github.com/DavidAnson/markdownlint/blob/main/doc/md056.md - https://github.com/DavidAnson/markdownlint/issues/1206 - https://google.github.io/styleguide/docguide/best_practices.html -last_verified: 2026-08-09 +last_verified: 2026-09-03 related: [ qa-process-acceptance-criteria, @@ -88,6 +88,7 @@ what this gate must assert about the document and stays here. | The check restates the expected values as its own literals | Import or read the owning symbol and iterate it. A literal list is a third copy that drifts with the other two, and it makes the check pass when the constant changes but the document does not | | The check must run where the owning code cannot be imported (docs-only CI job) | Have the code-side job emit the constant as a data file and diff the document against that file; keep the emit step in the same pipeline as the code | | A row exists in the document but not in the constant (or vice versa) | FAIL naming the direction — a removed constant leaves a documented row nothing checks, which reads as covered | +| The gate proves a name (CLI subcommand, config key, rule id) is documented by grepping it as a bare substring anywhere in the file | Scope the match to heading lines with a word boundary (`^#+ .*\bgrammar\b`) or to the parsed section list, so a passing mention in prose cannot satisfy it; then take the step-2 negative control from a known-bad revision — the pre-fix commit of the document — and require FAIL on it before trusting PASS on the current one | ## Instead of @@ -99,6 +100,7 @@ what this gate must assert about the document and stays here. | Skip a check whose anchor sentence was not found | Report FAIL and name the missing anchor | A skipped check is indistinguishable from a passed one in the summary line | | Verify a cross-section value by matching the number as written | Recompute it from its inputs and compare | Matching the written number passes when both sections were edited to the same wrong value | | Assert a documented table satisfies the property the table itself claims | Resolve the owning constant and compare the table's cells to it, row by row | The document is both subject and oracle, so the check is a tautology; when the constant moves, the gate built to catch drift is what pins the stale claim green | +| Prove coverage of N names with `grep -q doc.md` per name | Match each name on heading lines with word boundaries, and run the gate against the pre-fix revision | A substring gate is green exactly when the section is missing and the name survives in a sentence elsewhere | ## Sources @@ -111,6 +113,8 @@ what this gate must assert about the document and stays here. - https://github.com/DavidAnson/markdownlint/blob/main/doc/md056.md — MD056 flags tables whose rows disagree with the header's column count (structural table checking) - https://github.com/DavidAnson/markdownlint/issues/1206 — MD056 counts pipes inside backticks as separators: a delimiter count is not a parse - https://google.github.io/styleguide/docguide/best_practices.html — "Change your documentation in the same CL as the code change"; and where a fact lives elsewhere, "Link to it instead" of restating it — the external-agreement axis is what enforces that when a table restates the fact anyway +- https://testing.googleblog.com/2020/08/code-coverage-best-practices.html — a line counted as covered is not a line whose behavior was checked; "Mutation testing can help detect such false coverage" — the same presence-versus-verification gap a substring gate has +- Field reproduction 2026-09-02 (linkly, review t162-r1, commit 7bdbb1c): a doc-coverage test grepped subcommand names as bare substrings and passed while the `grammar` section was absent (the word appeared in prose elsewhere); re-scoped to heading lines with word-boundary matches, the same test run against the pre-fix document reported `missing: ['grammar']` and passed on the fixed one - Local reproduction 2026-08-08 (`linkly`, `docs/ENFORCEMENT-MATRIX.md` §C vs `lnpl.diagnostics.SEVERITY_OF`): the document's severity column reads `warning` in all five rows and its summary sentence says so, while the constant grades three of them `info`. A gate asserting "every documented code is a warning" passes on this pair; resolving each row against `SEVERITY_OF` returns three mismatches (`declared-not-enforced`, `declared-measured-only`, `authorization-not-verified`: documented `warning`, code `info`). The repo's `references/declarations.md`, whose same column is generated from the constant, agreed ## Field context diff --git a/wiki/qa/index.md b/wiki/qa/index.md index 18a4e9a..fe48141 100644 --- a/wiki/qa/index.md +++ b/wiki/qa/index.md @@ -20,7 +20,7 @@ Match your situation to a "load when" line; load only matching pages. | [post-release-verification](process/post-release-verification.md) | A release just deployed to production; defining what "released safely" means; an incident revealed a release was broken for hours before anyone noticed | | [scope-purity-checks](process/scope-purity-checks.md) | Proving a change/session/agent run touched nothing outside an allowed path set by filtering `git status --porcelain` output; a purity gate flags `?? dir/` for a directory that is wholly in scope; writing such a gate for an orchestration/CI workflow; a purity check in a permanent test suite fails on unrelated uncommitted files | | [defect-class-resweep-after-review](process/defect-class-resweep-after-review.md) | Addressing review findings when the remediation itself adds code (a new function, branch, call site, formatter); about to hand that diff to a verifier, an audit, or CI as "review comments addressed"; deciding whether a bot's acknowledged/resolved thread means the class is closed; reporting fix completeness as a class-with-method count rather than a per-finding list | -| [completion-claims](process/completion-claims.md) | About to report work as done, fixed, or passing — to a human, a coordinator, a PR, or a commit message; about to write "should work" or "tests pass" without a fresh run; deciding what evidence a completion claim requires; relaying a subagent's success report | +| [completion-claims](process/completion-claims.md) | About to report work as done, fixed, or passing — to a human, a coordinator, a PR, or a commit message; about to write "should work" or "tests pass" without a fresh run; deciding what evidence a completion claim requires; relaying a subagent's success report; relaying a delegated auditor's claim that it mutated and restored your uncommitted files | | [evaluating-review-feedback](process/evaluating-review-feedback.md) | Review findings arrived (human, bot, or reviewer agent) and you are deciding what to implement; a finding is unclear; a reviewer proposes robustness or features nothing uses; you disagree with a finding and are deciding how to respond | | [adversarial-change-review](process/adversarial-change-review.md) | Reviewing a diff ≥50 changed lines or one touching auth/payments/migrations/external APIs; a checklist review found nothing on a high-blast-radius change; deciding review depth for a risky diff; constructing failure scenarios (assumption violation, composition, cascade, abuse) | | [llm-review-pipelines](process/llm-review-pipelines.md) | Building or configuring an automated LLM code-review pipeline (CI review bot, review skill, PR-reviewer agent); review token cost grows with changeset size; deciding which stages run as deterministic code vs model judgment; keeping review rules as matchable data not prompt prose; model-written comments land on wrong lines; measuring a precision-vs-recall threshold change | @@ -38,7 +38,7 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| -| [spec-document-gates](document-verification/spec-document-gates.md) | Deciding whether passing a doc gate is enough to accept the deliverable: a document passed its checklist but the requirement is still unmet; choosing what the gate must assert beyond keyword presence (table structure, MUST-vs-SHOULD demotion, closed-set completeness, cross-section consistency, agreement with a code constant the document copies); a gate over a table asserts the property the table itself claims; setting the release policy a gate verdict feeds (authoring or validating the check code itself → wiki/testing/quality/spec-artifact-checks.md, wiki/testing/quality/checks-that-cannot-pass.md) | +| [spec-document-gates](document-verification/spec-document-gates.md) | Deciding whether passing a doc gate is enough to accept the deliverable: a document passed its checklist but the requirement is still unmet; choosing what the gate must assert beyond keyword presence (table structure, MUST-vs-SHOULD demotion, closed-set completeness, cross-section consistency, agreement with a code constant the document copies); a gate over a table asserts the property the table itself claims; setting the release policy a gate verdict feeds; a coverage gate that greps names as bare substrings anywhere in the document (authoring or validating the check code itself → wiki/testing/quality/spec-artifact-checks.md, wiki/testing/quality/checks-that-cannot-pass.md) | | [generated-reference-drift-gates](document-verification/generated-reference-drift-gates.md) | Writing or reviewing the reference material that enumerates a closed vocabulary an agent will emit tokens from (DSL verbs, config keys, diagnostic codes, enum members) in a plugin/skill/SDK; deciding whether to hand-write that list or generate it from the owning constant; choosing what gates a generated document beyond a `--check` diff; a documented token compiles to a silent no-op instead of an error | | [retiring-a-provisional-marker](document-verification/retiring-a-provisional-marker.md) | Turning provisional markers (`[추정]`, TBD, "assumed", DRAFT) into settled statements in an ADR/RFC/spec that has been through review rounds, so the document also carries a review checklist and a round history referring to those markers; a checklist row stayed `[x]` on evidence you just deleted; a coordinator's marker count and yours disagree while you read the same file | | [editing-a-gated-document](document-verification/editing-a-gated-document.md) | Editing or rewording a document that grep/regex gates or a lint config check; a gate fails on wording whose meaning did not change; reflowing prose a test asserts as a verbatim phrase (CI red on one platform only); describing what an upstream spec says without tripping a "do not redefine it" gate; a check matches the pattern your own document quotes; recording an audit verdict inside the document that was audited; deciding which checks to re-run after editing a gated document | diff --git a/wiki/qa/process/completion-claims.md b/wiki/qa/process/completion-claims.md index 6ddcfc6..5760356 100644 --- a/wiki/qa/process/completion-claims.md +++ b/wiki/qa/process/completion-claims.md @@ -6,8 +6,8 @@ applies_to: [general] confidence: field-tested sources: - https://github.com/obra/superpowers -last_verified: 2026-08-22 -related: [debugging-methodology-verify-the-fix, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, testing-quality-tests-that-cannot-fail, qa-process-release-gates] +last_verified: 2026-09-03 +related: [debugging-methodology-verify-the-fix, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, testing-quality-tests-that-cannot-fail, qa-process-release-gates, testing-quality-mutation-harness-file-custody, infrastructure-agent-orchestration-semantic-conflicts-after-parallel-merge, infrastructure-agent-orchestration-verify-command-in-a-worker-brief] --- # Claiming Work Is Done, Fixed, or Passing @@ -33,6 +33,7 @@ work now", "probably fixed", or "tests pass" without a run in front of you. | "Feature works" | The feature executed end-to-end with its observable output | A clean build; unit tests of the parts | | "Worker/subagent finished its task" | Its diff and artifacts inspected per [infrastructure-agent-orchestration-control-signals-vs-primary-artifacts] | The worker's own completion report | | "Regression test added" | The red-green flip: test fails with the fix reverted, passes with it ([testing-quality-tests-that-cannot-fail]) | A test written once and seen green once | +| "The auditor mutated files and restored them" (a test-quality auditor or any delegated agent that ran mutation testing on your uncommitted tree) | Your own byte comparison of the tree against a patch saved before delegating — `git diff > pre-audit.patch` before, `diff <(git diff) pre-audit.patch` after — plus a fresh suite run with the pre-audit total | The agent's "restored" sentence; a clean `git status --porcelain`, which an untracked file shows whether restored or destroyed ([testing-quality-mutation-harness-file-custody]) | 3. Treat hedge words in a completion sentence — "should", "probably", "seems to" — as markers of an unverified claim: run the proving command, or @@ -59,3 +60,5 @@ work now", "probably fixed", or "tests pass" without a run in front of you. ## Sources - https://github.com/obra/superpowers — verification-before-completion skill: fresh-evidence gate, claim/evidence table, hedge-word red flags, distrust of delegated self-reports; field-tested across agentic coding sessions +- https://git-scm.com/docs/git-checkout — `git checkout -- ` replaces the file with the index version and discards unstaged changes; an auditor that runs it on your uncommitted work has discarded that work until its restore lands +- Field reproduction 2026-08-18 (dev-loop task lo-t1-teardown): the test-quality auditor reported running `git checkout --` on `safe-cleanup.sh` during mutation testing and restoring it with `git apply`; the implementing session's own `diff <(git diff) ` was byte-identical and `bats tests/safe-cleanup.bats` re-ran 44/44 — that, not the auditor's sentence, established the restore diff --git a/wiki/testing/data/test-data-and-isolation.md b/wiki/testing/data/test-data-and-isolation.md index 4df1135..2d55674 100644 --- a/wiki/testing/data/test-data-and-isolation.md +++ b/wiki/testing/data/test-data-and-isolation.md @@ -10,7 +10,7 @@ sources: - https://testing.googleblog.com/2017/01/testing-on-toilet-keep-cause-and-effect.html - https://nodejs.org/api/fs.html - https://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html -last_verified: 2026-08-29 +last_verified: 2026-09-03 related: [testing-flaky-diagnosing-flaky-tests, testing-strategy-test-level-choice, testing-strategy-import-time-side-effects, testing-data-artifact-leakage-from-a-suite, testing-quality-behavior-not-implementation, platforms-filesystems-permissions-and-exec-bits, backend-common-change-impact-call-site-enumeration, testing-data-harness-vs-run-path-fixtures, infrastructure-agent-orchestration-shared-run-state] --- @@ -66,6 +66,7 @@ state-leak symptom. | The machine runs endpoint security (EDR) that flags an executable created under the system temp directory | Keep the fixture inside the repo's gitignored build-output tree and set the bit at creation; that path is what a "+x file dropped in a temp dir" heuristic looks for, and the fixture only needs the bit, not the location ([platforms-filesystems-permissions-and-exec-bits]) | | Leftover test artifacts (temp dirs/files) accumulate in the repo and the producers look diffuse | Count leftovers by name prefix (`ls \| sed 's/-[a-z0-9]*$//' \| sort \| uniq -c`) and match the distribution against the sites that create such files — a match closes the attribution; fix those sites, then enforce the cleanup convention with a static check proven red against the unfixed code first | | The code under test is the harness that spawned the session now running its suite (an orchestration worker runs the orchestrator's own tests in its worktree) | Treat the leak as two failures: reproduce the test failures with `env VAR=… bats ` on a clean checkout to confirm the mechanism, then check the live run's state files for writes stamped with test-fixture values — a leaked state path corrupts the running orchestration ([infrastructure-agent-orchestration-shared-run-state]), which surfaces later as a watcher monitoring the wrong session | +| The code under test also discovers its config by walking up from the cwd (a `.groundwork/`, `.editorconfig`, `.npmrc`-style file), and the suite runs from a worktree that carries a sandbox copy | Tests that never `cd` into their own temp directory inherit the runner's invocation cwd and read the sandbox config, so they fail from the worktree and pass from a clean checkout. Make every such test `cd "$BATS_TEST_TMPDIR"` (or pin the discovery root explicitly), and override the exported state directory (`GROUNDWORK_ESCALATION_DIR`) to a scratch path in `setup()` — a run that inherits it writes one real record per fixture command into the live run's directory | ## Instead of @@ -86,3 +87,5 @@ state-leak symptom. - https://testing.googleblog.com/2017/01/testing-on-toilet-keep-cause-and-effect.html — keep the inputs a test's result depends on visible in the test method instead of in shared setup, so the cause-and-effect relationship is readable without jumping elsewhere - Field incident 2026-08-04 (`linkly-t1-repo-policy`, Python): `rows_for(doc)` seeded its rows from the module constant `PAYLOAD` while its tests ran payload `{}`; a shape-only migration of the helper fixed 1 of 11 failures, and moving the payload into the helper's signature fixed 11 of 11 - Field incident 2026-08-14 (dev-loop issue #100): `launch-session.sh` exports `LO_RUN_ID`/`LO_STATUS_DIR`/`LO_TASK_ID` into every worker session; a worker running `bats tests/launch-session.bats` inherited them — 6 deterministic failures absent on a clean shell, reproduced with `env LO_RUN_ID=… bats`, and the live run's `t90.json` status file was found rewritten with bats tempdir paths and a foreign session name +- https://bats-core.readthedocs.io/en/stable/writing-tests.html — `BATS_RUN_TMPDIR` defaults to `$BATS_TMPDIR/bats-run-$BATS_ROOT_PID-XXXXXX` and `BATS_TEST_TMPDIR` is per test; a record carrying that prefix was produced by the suite +- Field reproduction 2026-09-02 (guardrails bats suite run from a dev-loop worker worktree): 65 of 102 tests failed from the worktree cwd in one run while 166/166 passed from a clean checkout; the two named failures (`non-interactive turns ask into deny`, `self-test exits 0`) are the tests that do not `cd` into their tmpdir, so they read the worktree's sandbox `.groundwork/guardrails.json` through the guard's upward config walk; the same run left 175 escalation records with `bats-run-*` paths in the live run's exported escalation directory diff --git a/wiki/testing/index.md b/wiki/testing/index.md index 4e65ac9..9929337 100644 --- a/wiki/testing/index.md +++ b/wiki/testing/index.md @@ -61,7 +61,7 @@ Match your situation to a "load when" line; load only matching pages. |------|-----------| | [artifact-leakage-from-a-suite](data/artifact-leakage-from-a-suite.md) | Temp directories, build outputs, or scratch files pile up in the repo or system temp after a suite runs; a clone grows with no obvious owner; you suspect the leak comes from everywhere and need a way to locate it; deciding between per-site cleanup, the runner's owned-temp API, and a static rule that enforces the convention | | [harness-vs-run-path-fixtures](data/harness-vs-run-path-fixtures.md) | A test/spec harness and the production entry point each synthesize the program's input and the harness reads a narrower declaration set; a harness run reports a guarded step skipped with exit 0 and you are about to record that skip as program behavior; deciding whether a false guard means a false operand or an absent one | -| [test-data-and-isolation](data/test-data-and-isolation.md) | Tests need fixture data and you are choosing how to create it; tests pass alone but fail together (or vice versa); DB cleanup, shared fixtures, time-dependent logic, or unique-value collisions; a suite behaves differently inside a harness-spawned session that injects env vars | +| [test-data-and-isolation](data/test-data-and-isolation.md) | Tests need fixture data and you are choosing how to create it; tests pass alone but fail together (or vice versa); DB cleanup, shared fixtures, time-dependent logic, or unique-value collisions; a suite behaves differently inside a harness-spawned session that injects env vars; a suite fails from a worktree cwd and passes from a clean checkout because the code under test walks up from cwd to find its config | ## mocking @@ -70,6 +70,7 @@ Match your situation to a "load when" line; load only matching pages. | [destructive-operations-on-shared-daemons](mocking/destructive-operations-on-shared-daemons.md) | The code under test enumerates and deletes a machine-wide daemon's resources by name/pattern (tmux sessions, docker containers, systemd units, namespaces) and that daemon runs on the test machine; proving a sweep deletes the targets and spares bystanders; keeping a scope bug from destroying the dev environment instead of failing the test; giving a shell script a substitution seam for the tool it shells out to | | [what-to-mock](mocking/what-to-mock.md) | Deciding whether to mock/stub/fake a dependency or use the real one; mocks breaking on refactors; testing handling of a third-party's failure modes; the same mock setup is copy-pasted across tests | | [captured-call-arguments](mocking/captured-call-arguments.md) | Writing the spy/stub test that holds a fix to one argument of one wiring call (constructor, factory, server startup); such a test is green while a mutation of a *different* argument of the same call survives; the fix extracted the value into a resolver and you are choosing what to assert; deciding between asserting a constant's value and asserting that the call site passes it on; choosing how to record an argument you deliberately leave unpinned | +| [extracted-method-this-binding](mocking/extracted-method-this-binding.md) | Code extracts a method off a class instance into a variable (`const fn = obj.method`) or passes `obj.method` as a callback, and the unit tests inject it as `vi.fn()`/`jest.fn()`; a feature throws `TypeError` in the browser or an E2E run while its unit tests are green; choosing a regression test a `this`-indifferent mock cannot satisfy (a `this`-reading callable, `mock.contexts`) | ## flaky diff --git a/wiki/testing/mocking/captured-call-arguments.md b/wiki/testing/mocking/captured-call-arguments.md index d1d2fb1..18bca08 100644 --- a/wiki/testing/mocking/captured-call-arguments.md +++ b/wiki/testing/mocking/captured-call-arguments.md @@ -17,6 +17,7 @@ related: testing-quality-tests-that-cannot-fail, testing-quality-behavior-not-implementation, backend-common-change-impact-call-site-enumeration, + testing-mocking-extracted-method-this-binding, ] --- diff --git a/wiki/testing/mocking/extracted-method-this-binding.md b/wiki/testing/mocking/extracted-method-this-binding.md new file mode 100644 index 0000000..5ca6bf3 --- /dev/null +++ b/wiki/testing/mocking/extracted-method-this-binding.md @@ -0,0 +1,73 @@ +--- +id: testing-mocking-extracted-method-this-binding +domain: testing +category: mocking +applies_to: [javascript, typescript, vitest, jest] +confidence: verified +sources: + - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this + - https://vitest.dev/api/mock.html +last_verified: 2026-09-03 +related: [testing-mocking-what-to-mock, testing-quality-tests-that-cannot-fail, testing-mocking-captured-call-arguments, testing-strategy-test-level-choice] +--- + +# A Method Extracted From Its Object and Tested Through a Mock + +## When this applies + +Code reads an optional method off an object into a variable and calls it later +(`const resolve = source.resolveGate; if (resolve) resolve(x)`), or passes +`obj.method` as a callback; the object is a class instance (a client, a store, +an IPC bridge) whose methods read `this`; the unit tests inject the method as +`vi.fn()`/`jest.fn()`. Also when such a feature crashes in the browser or an E2E +run while its unit tests are green. + +## Do this + +1. **Bind at the extraction site, in the code:** + `const resolve = source.resolveGate?.bind(source)`. A method extracted from + its object and called standalone runs with `this` of `undefined` (strict mode + and modules), so its first `this.x` read throws `TypeError`; a bound + function's `this` no longer depends on the caller. Arrow-function class + fields and `obj.method(...)` call syntax are the other two safe shapes. + +2. **Write the regression test with a `this`-dependent callable, or assert the + recorded context.** A bare `vi.fn()` returns its canned value whatever `this` + is, so a test that only checks it was called passes on the unbound code too: + +| Test shape | Assertion | +|------------|-----------| +| Inject a real instance whose method reads `this` (the production class, or a two-line class with a counter) | The call succeeds and the instance's state changed | +| Inject `function () { return this === obj; }` | The returned value is `true` | +| Keep `vi.fn()` | `expect(fn.mock.contexts[0]).toBe(obj)` — Vitest records the `this` of every call in `mock.contexts` | + +3. **Prove the test reddens on the unbound form before keeping it:** remove + the `.bind(...)`, require red, restore ([testing-quality-tests-that-cannot-fail]). + +4. **When a report reads "unit tests green, crashes in the real app" and the + crashing call is a method used as a value, check the binding first** — the + gap between a `this`-indifferent mock and a `this`-reading instance is the + usual mechanism. + +## Edge cases + +| Case | Then | +|------|------| +| The method is optional on the type (`resolveGate?: () => …`) | `source.resolveGate?.bind(source)` is `undefined` when the method is absent; keep the existence check on the bound variable | +| The object is a plain literal with arrow-function members | No `this` dependency today; keep the `this` assertion in the test anyway so a later class-based implementation cannot regress silently | +| The callback is handed to a framework that calls it with its own `this` (event emitters, some ORMs) | Bind explicitly to the object you mean; the framework's `this` is not your instance | +| Two real implementations back the interface (a dev mock service, a production IPC bridge) | Run the test against each real one: the field crash was on both, and only a `this`-reading callable exposed it | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Test the extracted-method path with `vi.fn()` and assert it was called | Inject a `this`-dependent callable, or assert `mock.contexts[0]` | The bare mock is indifferent to `this`, so it passes on code that throws in production | +| Fix the crash by wrapping the call in `try/catch` | Bind at extraction | The catch hides the lost context; the feature still does nothing | + +## Sources + +- https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/this — extracting a method (`const carSayHi = car.sayHi`) and calling it standalone loses `this`; "For bound methods, `this` doesn't depend on the caller" +- https://vitest.dev/api/mock.html — `mock.contexts` is "an array of `this` values used during each call to the mock function"; `vi.fn()` places no requirement on `this` +- Local reproduction 2026-09-03 (Node): `class Foo { constructor(){ this.value = 42 } method(){ return this.value } }`; `const fn = new Foo().method; fn()` → `TypeError: Cannot read properties of undefined (reading 'value')`; `obj.method.bind(obj)()` → `42` +- Field reproduction 2026-09-02 (linkly-crew approvals inbox, React/TS + Tauri): `defaultSource.resolveGate` extracted unbound crashed on click against both the dev mock service (`gateEnvCounter` on `this`) and the Tauri bridge (`this.invoke`), while three unit tests built on `vi.fn()` stayed green; `.bind` fixed both, verified in the browser diff --git a/wiki/testing/mocking/what-to-mock.md b/wiki/testing/mocking/what-to-mock.md index 17f7fa2..2b9996a 100644 --- a/wiki/testing/mocking/what-to-mock.md +++ b/wiki/testing/mocking/what-to-mock.md @@ -11,7 +11,7 @@ sources: - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import - https://nodejs.org/api/test.html last_verified: 2026-08-06 -related: [testing-strategy-test-level-choice, testing-quality-behavior-not-implementation, testing-quality-tests-that-cannot-fail, testing-mocking-captured-call-arguments] +related: [testing-strategy-test-level-choice, testing-quality-behavior-not-implementation, testing-quality-tests-that-cannot-fail, testing-mocking-captured-call-arguments, testing-mocking-extracted-method-this-binding] --- # Deciding Whether to Mock, Fake, or Use the Real Dependency diff --git a/wiki/testing/quality/checks-that-cannot-pass.md b/wiki/testing/quality/checks-that-cannot-pass.md index 2aae3e8..297fe6c 100644 --- a/wiki/testing/quality/checks-that-cannot-pass.md +++ b/wiki/testing/quality/checks-that-cannot-pass.md @@ -10,7 +10,7 @@ sources: - https://docs.semgrep.dev/writing-rules/testing-rules - https://docs.pytest.org/en/stable/reference/exit-codes.html last_verified: 2026-08-06 -related: [testing-quality-tests-that-cannot-fail, testing-quality-minimum-case-set, backend-common-change-impact-call-site-enumeration, platforms-processes-tool-diagnostics-without-a-failing-exit-code, qa-process-scope-purity-checks, testing-quality-history-dependent-checks-on-shallow-clones] +related: [testing-quality-tests-that-cannot-fail, testing-quality-minimum-case-set, backend-common-change-impact-call-site-enumeration, platforms-processes-tool-diagnostics-without-a-failing-exit-code, qa-process-scope-purity-checks, testing-quality-history-dependent-checks-on-shallow-clones, platforms-tools-deny-rules-under-bypassed-permissions] --- # Validating a Check Whose Target Does Not Exist Yet diff --git a/wiki/testing/quality/mutation-harness-file-custody.md b/wiki/testing/quality/mutation-harness-file-custody.md index 8b00dd3..fdd4625 100644 --- a/wiki/testing/quality/mutation-harness-file-custody.md +++ b/wiki/testing/quality/mutation-harness-file-custody.md @@ -18,6 +18,7 @@ related: testing-quality-completion-predicates, testing-data-artifact-leakage-from-a-suite, qa-process-scope-purity-checks, + qa-process-completion-claims, ] --- diff --git a/wiki/testing/quality/tests-that-cannot-fail.md b/wiki/testing/quality/tests-that-cannot-fail.md index fe33077..cf74131 100644 --- a/wiki/testing/quality/tests-that-cannot-fail.md +++ b/wiki/testing/quality/tests-that-cannot-fail.md @@ -16,7 +16,7 @@ sources: - https://git-scm.com/docs/git-checkout - https://git-scm.com/docs/git-restore last_verified: 2026-08-27 -related: [testing-quality-minimum-case-set, testing-quality-behavior-not-implementation, testing-mocking-what-to-mock, testing-async-async-testing, testing-quality-checks-that-cannot-pass, testing-quality-spec-artifact-checks, testing-quality-harness-reverse-controls, testing-quality-schema-additions-under-a-golden-gate, testing-quality-differential-run-agreement, testing-quality-completion-predicates, testing-quality-guard-shape-vs-consequence, testing-quality-injected-clock-duration-assertions, testing-quality-write-path-assertions, testing-quality-value-preserving-refactor-assertions, testing-quality-unasserted-return-fields, testing-quality-stale-artifact-baselines, backend-common-change-impact-call-site-enumeration, platforms-shells-portable-shell-scripts, qa-document-verification-spec-document-gates, testing-quality-surviving-mutant-equivalence-triage, testing-quality-source-text-wiring-assertions, testing-quality-default-values-under-test, testing-mocking-captured-call-arguments, testing-quality-captured-log-message-assertions, testing-strategy-failing-test-first, testing-quality-mutation-harness-file-custody] +related: [testing-quality-minimum-case-set, testing-quality-behavior-not-implementation, testing-mocking-what-to-mock, testing-async-async-testing, testing-quality-checks-that-cannot-pass, testing-quality-spec-artifact-checks, testing-quality-harness-reverse-controls, testing-quality-schema-additions-under-a-golden-gate, testing-quality-differential-run-agreement, testing-quality-completion-predicates, testing-quality-guard-shape-vs-consequence, testing-quality-injected-clock-duration-assertions, testing-quality-write-path-assertions, testing-quality-value-preserving-refactor-assertions, testing-quality-unasserted-return-fields, testing-quality-stale-artifact-baselines, backend-common-change-impact-call-site-enumeration, platforms-shells-portable-shell-scripts, qa-document-verification-spec-document-gates, testing-quality-surviving-mutant-equivalence-triage, testing-quality-source-text-wiring-assertions, testing-quality-default-values-under-test, testing-mocking-captured-call-arguments, testing-quality-captured-log-message-assertions, testing-strategy-failing-test-first, testing-quality-mutation-harness-file-custody, infrastructure-agent-orchestration-verify-command-in-a-worker-brief, testing-mocking-extracted-method-this-binding] --- # Proving a Test Can Fail diff --git a/wiki/testing/strategy/test-level-choice.md b/wiki/testing/strategy/test-level-choice.md index 504305d..3fe2364 100644 --- a/wiki/testing/strategy/test-level-choice.md +++ b/wiki/testing/strategy/test-level-choice.md @@ -9,7 +9,7 @@ sources: - https://testing.googleblog.com/2017/04/where-do-our-flaky-tests-come-from.html - https://docs.pytest.org/en/stable/how-to/skipping.html last_verified: 2026-08-04 -related: [testing-quality-minimum-case-set, testing-mocking-what-to-mock, testing-data-test-data-and-isolation, testing-strategy-import-time-side-effects, testing-strategy-cross-layer-effect-tests] +related: [testing-quality-minimum-case-set, testing-mocking-what-to-mock, testing-data-test-data-and-isolation, testing-strategy-import-time-side-effects, testing-strategy-cross-layer-effect-tests, testing-mocking-extracted-method-this-binding] --- # Choosing the Test Level for a Behavior