diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 3a965e5..dfa7623 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,237 +1,78 @@ -# Knowledge flush — 13 insight(s) ingested (21 claimed, 2 dropped, 6 released) +# Knowledge flush — 12 insight(s) claimed: 8 ingested on this branch, 3 folded onto open PRs, 1 dropped as already merged -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. +Run id `20260906-003715-45485` (headless auto-flush), branch `knowledge/choiyounggi-20260906-003745`, base `origin/main` at `6dcda6f` (1.21.0). Claimed with `queue-claim.js claim --max 12`; 40 rows remain pending for later flushes. ## 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. +| # | Queue hash | Claim | Sources checked | How verified | Confidence | +|---|-----------|-------|-----------------|--------------|------------| +| 1 | `fbb4ea0373ad14b6` | OKLCH lightness steps below L 30% collapse to ~1.1–1.3:1 contrast in sRGB; gate state cues on the measured ratio, carry identification on outline/chroma/shape | https://www.w3.org/TR/WCAG21/ (ratio formula), https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html (3:1 for UI components), https://www.w3.org/TR/css-color-4/ (OKLCH lightness is visual lightness; sRGB transfer function) | Recomputed WCAG ratios from the candidate's measured rgb bytes in Python: 13% vs 19% = 1.091, 13% vs 27% = 1.339 (exact match to the candidate). Re-derived OKLCH→sRGB with the CSS Color 4 matrices: all five rungs within ±2 bytes of the engine measurement. Agents confirmed all three URLs 200 with quotes | verified | +| 2 | `2232cfdc9e06a8b7` | Grade a per-entity signal on excess return over the benchmark for the same window; a population-wide burst is a shared driver | https://corporatefinanceinstitute.com/resources/equities/abnormal-return/ ("Excess Return = Actual Return − Expected Return", market index as expected), https://www.wallstreetprep.com/knowledge/information-ratio/ ("excess return over a benchmark") | Both URLs 200 with verbatim quotes; arithmetic in the field data checked (1,531,000→1,599,000 = +4.44%). Investopedia returned 402 and was excluded | verified | +| 3 | `5a6ea1bb69d16261` | Multi-row rewrite on a shared resource: parent-row lock in one transaction, deferrable unique position, explicit final-state rule, two-writer test | https://www.postgresql.org/docs/current/explicit-locking.html (FOR UPDATE), https://www.postgresql.org/docs/current/sql-createtable.html (DEFERRABLE), https://www.postgresql.org/docs/current/sql-set-constraints.html (DEFERRED checked at commit), https://www.postgresql.org/docs/current/sql-insert.html (ON CONFLICT DO UPDATE = UPSERT) | All four URLs 200 with quotes; field fix (pessimistic lock + single transaction + upsert) from the linkly-calendar review findings | verified | +| 4 | `1c1a26a8abae7532` | Check a DDL rule's premise (large live table) and the runner's ability to execute it (Prisma wraps multi-statement migrations in an implicit transaction, so `CONCURRENTLY` fails) | https://github.com/prisma/orm/issues/22922 (title and body read via `gh api`; the reporter's reproduction: two-statement file fails, one-statement passes; a later comment shows a single statement split across lines also wrapped), https://github.com/prisma/orm/discussions/10601 (title "Disable transactions for a single migration", error 25001 quoted), https://github.com/prisma/orm/issues/14456, https://github.com/prisma/prisma-engines/blob/main/schema-engine/ARCHITECTURE.md ("Why does Migrate not run migrations in a transaction by default?"), https://www.postgresql.org/docs/current/sql-createindex.html ("CREATE INDEX CONCURRENTLY cannot" run in a transaction block) | The candidate's `prisma/prisma` URLs redirect to `prisma/orm`; recorded the canonical URLs. Issue is still open (no fix), which the row reflects | verified | +| 5 | `c20328d15c5d637f` | `graphify update` on >5,000 nodes fails only at the HTML export; graph.json and the report are already written; no `--no-viz` on `update` | Installed source, graphifyy 0.4.23 (`~/.local/pipx/venvs/graphifyy`): `watch.py` `_rebuild_code` writes `GRAPH_REPORT.md`, `to_json`, then `to_html`; `export.py` `MAX_NODES_FOR_VIZ = 5_000` raises `ValueError("... Use --no-viz ...")`; `__main__.py` `update` exits 1 on `False`; `grep no-viz` over the CLI finds only the error string. PyPI JSON API: latest is 0.9.54 | Read the code paths directly; the row is version-scoped to 0.4.23 with a re-check instruction. The candidate's monkeypatch directive is kept as the "hook must exit 0" branch only | verified (0.4.23) | +| 6 | `aae0d53b3d306e5e` | Measure a live session's context from the newest transcript file, not the project directory | https://code.claude.com/docs/en/sessions (`/.jsonl`, project = cwd with non-alphanumerics replaced by `-`); dev-loop's own `skills/orchestrate/scripts/token-report.sh` header and SKILL.md lines 108–122 already encode the same rule | URL 200 with quote; field measurement (49 files, 10 spurious `warn:` lines, live session under threshold) from the candidate | verified | +| 7 | `3bb4937f1288fef0` | With fake timers, a "lock explicitly cleared" test whose wait ≥ TTL cannot fail; rewind the clock to before expiry and mutation-check the clear | https://vitest.dev/api/vi.html (`vi.setSystemTime` "simulates a user changing the system clock") | URL 200 with quote; field mutation check (commenting out `lockouts.clear()` reddened exactly the target test) from the candidate | verified | +| 8 | `ef088fd050a74454` | Alert on the primary source's success count reaching zero when a fallback silently takes over | https://sre.google/sre-book/monitoring-distributed-systems/ (symptoms vs causes; already the page's source). The agent found no primary text stating "fallback success must emit a signal" — the row is field-derived | 7-day incident (pykrx dead, Naver fallback 29/30, 0 signals, runs logged green) from korea-data-suite / stock-signal-bot | field-tested (row); page stays `verified` for its existing content | +| 9 | `00297bb2452bb72c` | Worker done with a clean worktree, or "unexplained resets": the edits escaped to the main checkout; recover with `git diff` → `git apply --3way` | https://git-scm.com/docs/git-apply (`--3way`: "Attempt 3-way merge if the patch records the identity of blobs") | URL 200 with quote (agent + my own curl); field run i168 t2 (bats 21/21 after transplant) | verified | +| 10 | `18392e61e52d0b0f` | After merging worker branches into the integration worktree, re-sync installed deps and codegen before diagnosing "module not found" / undefined generated model as a regression | https://pnpm.io/cli/install (frozen lockfile "fails to install if the lockfile is out of sync"), https://www.prisma.io/docs/orm/prisma-client/setup-and-configuration/generating-prisma-client ("whenever you add models, change fields…"), https://git-scm.com/docs/git-worktree (already on the page) | URLs 200 with quotes; field run trip3 (7 suites `Cannot find module 'undici'`, 2 e2e `prisma.trip` undefined, fixed by install + generate with no code change) | verified | +| 11 | `13ce23861664767e` | Before writing "reuse component X" in a design doc, grep the implementation and consumer count and record the command | No external source needed (the mechanism is the grep itself); field: `LinklyModal.swift` was a bottom sheet with one consumer, `LinklyCalendarRangeLozenge` had zero | Field evidence from the candidate; verifiable by re-running the recorded grep | field-tested | +| 12 | `f1ba9bf617fbd101` | graphify CLI exits 0 on missing node / missing file / JSON error; validate the artifact and branch on stdout | — | Already on `main` verbatim: `code-graph-as-orientation-layer.md` edge row "The CLI exits 0 on a missing node, a missing graph file, or a JSON decode error" plus the 0.4.23 source line, merged via #184 on 2026-09-04 from the same session | dropped — already merged | + +Nothing was upgraded to `verified` without a cited document or a reproduction I ran; #8 and #11 are labelled field-tested in the pages. ## 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. +Pages read: infrastructure-agent-orchestration-code-graph-as-orientation-layer, infrastructure-agent-orchestration-worktree-isolated-workers, infrastructure-agent-orchestration-session-context-token-budget, infrastructure-observability-alerting, testing-quality-tests-that-cannot-fail, testing-quality-injected-clock-duration-assertions, testing-async-async-testing, frontend-design-anti-slop-visual-design, databases-schema-design-online-schema-changes, databases-transactions-optimistic-vs-pessimistic-locking, databases-transactions-isolation-level-selection, backend-common-integrations-estimate-derived-thresholds + +Also read `INDEX.md`, the domain indexes for infrastructure, testing, frontend, databases, backend, qa and debugging, `AGENTS.md`, `templates/page.md`, and, on the open PR branches only (not on this checkout), `semantic-conflicts-after-parallel-merge` (#179) and `checkable-claims-in-an-adopted-plan` (#181). + +Overlaps and outcomes: + +- **#1 OKLCH** — `anti-slop-visual-design` has the "+3% lightness per elevation level" rule; not a conflict (elevation is decorative), but a reader could over-apply it to state cues. Created a new page and appended a pointer to that row plus a `related:` link both ways. +- **#2 benchmark-relative** — no existing page; `estimate-derived-thresholds` (same bot, integrations) and `mape-aligned-point-prediction` (ml) are the neighbours. New page; back-link added on `estimate-derived-thresholds`. +- **#3 multi-row reorder** — `isolation-level-selection` has the multi-row-invariant row (SERIALIZABLE or parent lock) and `optimistic-vs-pessimistic-locking` the version-column path, but neither covers the unique-position constraint or the final-state rule. New page; both existing pages gained a `related:` back-link. +- **#4 Prisma CONCURRENTLY** — `online-schema-changes` already says "Never inside a transaction block" for `CONCURRENTLY`; merged two edge rows (implicit runner transaction; premise absent on an empty table), five sources, `applies_to` widened to prisma. +- **#5 graphify viz failure** — merged as one edge row into `code-graph-as-orientation-layer` next to the exit-0 row it extends. +- **#6 newest transcript** — merged as one edge row into `session-context-token-budget`. Body-only edit: PR #183 rewrites that page's `related:` line, so the frontmatter was left untouched to avoid a conflict; the source is recorded in the body's Sources list. +- **#7 fake-timer TTL** — `injected-clock-duration-assertions` is about float tolerance, not this; `tests-that-cannot-fail` is at 114 body lines and its `related:` line is rewritten by #179 and #183. Merged into `async-testing` (fake-timer mechanics page) as one edge row, body-only for the same reason (#183 rewrites its `related:`), with an inline link to `tests-that-cannot-fail` for the mutation proof. +- **#8 fallback alert** — merged into `alerting` as one edge row + one Instead-of row, body-only (#181 rewrites its `related:` line). +- **#9, #10, #11** — folded onto open PRs (next section), no main-branch page touched. +- **#12** — duplicate of a merged row; nothing written. + +No contradictions found. Lint: `wiki-structure-checks.js wiki` → 0 findings (281 pages); `wiki-lint-prohibitions.js wiki` → 0 violations; new pages 83/78/88 total lines (all bodies ≤ 120). ## 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. +Open `knowledge/*` heads listed with `gh pr list --search "head:knowledge/"`: #183 (`knowledge/choiyounggi-20260904-133717`), #182 (`…-20260903-214027`), #181 (`…-20260903-203836`), #180 (`…-20260903-184706`), #179 (`…-20260903-172728`). Each was fetched and diffed against `origin/main -- wiki/`. + +| Candidate | Overlapping open head | Verdict | Action | +|-----------|----------------------|---------|--------| +| #9 `00297bb2452bb72c` worker done / clean worktree / "resets" | #179 rewrites `worktree-isolated-workers` with the patch-transfer recovery rows (`git diff` → `git apply --check` → `git checkout --`) | **fold** | Added one edge-case row (the clean-worktree-at-done and "external resets" symptoms are the same incident; `git apply --3way`), extended the git-apply source bullet with `--3way`, added the i168 t2 field line. Pushed to #179 and commented | +| #10 `18392e61e52d0b0f` stale install/codegen after merge | #179 adds `semantic-conflicts-after-parallel-merge` (merged-tree result table) | **fold** | Added a row to the merged-tree result table, one Instead-of row, two doc sources (pnpm, Prisma generate) and the trip3 field line. Pushed to #179 and commented | +| #11 `13ce23861664767e` grep before writing "reuse X" | #181 adds `checkable-claims-in-an-adopted-plan` (adopter-side recomputation of a plan's claims) | **fold** | Added one edge-case row for the plan's author, the field line, and extended the page's load-when line in `infrastructure/index.md` on that branch. Pushed to #181 and commented | +| #6 newest transcript | #183 touches `session-context-token-budget` `related:` only | **new** (body-only edit here) | — | +| #7 fake-timer TTL | #183 touches `async-testing` `related:` only; #179/#183 touch `tests-that-cannot-fail` `related:` only | **new** (body-only edit here) | — | +| #8 fallback alert | #181 touches `alerting` `related:` only | **new** (body-only edit here) | — | +| #1, #2, #3, #4, #5 | none of the five heads touch `anti-slop-visual-design`, `online-schema-changes`, `code-graph-as-orientation-layer`, the transactions pages, or the ml category | **new** | — | +| #12 | not an open-PR overlap — already on `main` via #184 | **drop** (pending-duplicate of merged content) | Retired | -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.) +Expected merge friction: every open head appends to `log.md`, and #179/#180 touch `INDEX.md` (2 lines each); this branch appends nine `log.md` lines and edits the databases and backend rows of `INDEX.md`. `frontend/index.md` and `infrastructure/index.md` are touched by #181/#179/#183 in other rows. These are line-adjacent, not semantic, conflicts. ## 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. +| Candidate | Target | New page? | Why here | +|-----------|--------|-----------|----------| +| #1 OKLCH steps | `frontend/design/lightness-steps-on-dark-surfaces.md` | yes | frontend/design owns color/token decisions; trigger (dark ladder below L 30%, state cue) is new | +| #2 benchmark-relative | `backend/common/ml/benchmark-relative-signal-classification.md` | yes | No category for analytics exists; `ml` already holds the evaluation/calibration page (MAPE) and this is a grading-input rule of the same kind. Creating an `analytics` category for one page was rejected in favour of the closest fit; INDEX.md backend row now names it | +| #3 multi-row reorder | `databases/transactions/multi-row-reorder-on-a-shared-resource.md` | yes | Transaction boundary + constraint deferral + locking are DB-side mechanics; the app-side wrapper is linked (`backend-common-orm-transaction-boundaries`) | +| #4 Prisma CONCURRENTLY | `databases/schema-design/online-schema-changes.md` | merge (2 edge rows) | The page already owns `CONCURRENTLY`; the runner constraint and the empty-table premise are its edge cases | +| #5 graphify viz | `infrastructure/agent-orchestration/code-graph-as-orientation-layer.md` | merge (1 edge row) | Same tool, same page, adjacent to the exit-0 row | +| #6 newest transcript | `infrastructure/agent-orchestration/session-context-token-budget.md` | merge (1 edge row) | Measuring a session's context is that page's subject | +| #7 fake-timer TTL | `testing/async/async-testing.md` | merge (1 edge row) | Fake-timer mechanics live there; the cannot-fail proof is linked | +| #8 fallback alert | `infrastructure/observability/alerting.md` | merge (1 edge + 1 Instead-of row) | Alert design; sits beside the "absence of success" batch-job row | +| #9, #10 | PR #179 pages | fold | see Open-PR check | +| #11 | PR #181 page + its index line | fold | see Open-PR check | +| #12 | — | drop | already merged (#184) | + +No new category was created. Indexes updated: `frontend/index.md`, `backend/index.md`, `databases/index.md`, `INDEX.md` (databases and backend route lines); `log.md` gained nine entries. diff --git a/INDEX.md b/INDEX.md index 80fee3f..79eed7e 100644 --- a/INDEX.md +++ b/INDEX.md @@ -11,8 +11,8 @@ follow the cross-pointers in their index or take the next matching seeded domain | Domain | Status | Route here when | |--------|--------|-----------------| -| [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`) | +| [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, multi-row rewrites (reorder, bulk status) on a shared resource, 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, benchmark-relative signal rating, 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, a pre-built code knowledge graph as a freshness-gated orientation layer for planning) | | [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) | diff --git a/log.md b/log.md index e2ee9ca..1634732 100644 --- a/log.md +++ b/log.md @@ -104,3 +104,12 @@ Append-only. Format: `## [YYYY-MM-DD] 5,000 nodes fails only at HTML export; graph.json is already current +## [2026-09-06] ingest | infrastructure-agent-orchestration-session-context-token-budget — measure a live session from the newest transcript file, not the project directory +## [2026-09-06] ingest | testing-async-async-testing — rewind the fake clock to before the lock's TTL to isolate an explicit clear; mutation-check the clear +## [2026-09-06] ingest | infrastructure-observability-alerting — alert on primary-source success count reaching zero when a fallback silently takes over +## [2026-09-06] ingest | folds onto open PRs: #179 worktree-isolated-workers (clean-worktree-at-done recovery via git apply --3way) + semantic-conflicts-after-parallel-merge (stale install/codegen in the integration worktree); #181 checkable-claims-in-an-adopted-plan (author-side reuse-claim grep) diff --git a/wiki/backend/common/integrations/estimate-derived-thresholds.md b/wiki/backend/common/integrations/estimate-derived-thresholds.md index 8ecc281..fc3740f 100644 --- a/wiki/backend/common/integrations/estimate-derived-thresholds.md +++ b/wiki/backend/common/integrations/estimate-derived-thresholds.md @@ -9,7 +9,7 @@ sources: - https://en.wikipedia.org/wiki/Slippage_(finance) - "Live incident 2026-08-13/14 (KIS auto-trading bot): 6 positions exited TAKE_PROFIT at +0.06%–+0.42% against a +2% design; re-anchor in mark_filled() restored the band, regression tests red on pre-fix code" last_verified: 2026-08-14 -related: [] +related: [backend-common-ml-benchmark-relative-signal-classification] --- # Absolute Thresholds Derived from a Pre-Execution Estimate diff --git a/wiki/backend/common/ml/benchmark-relative-signal-classification.md b/wiki/backend/common/ml/benchmark-relative-signal-classification.md new file mode 100644 index 0000000..ca219ed --- /dev/null +++ b/wiki/backend/common/ml/benchmark-relative-signal-classification.md @@ -0,0 +1,78 @@ +--- +id: backend-common-ml-benchmark-relative-signal-classification +domain: backend +category: ml +applies_to: [general] +confidence: verified +sources: + - https://corporatefinanceinstitute.com/resources/equities/abnormal-return/ + - https://www.wallstreetprep.com/knowledge/information-ratio/ + - "Field measurement 2026-08-26 (stock-signal-bot, KRX): a 5-day +4.44% move graded as entity-specific strength sat under the KOSPI's +5.21% over the same window" +last_verified: 2026-09-06 +related: [backend-common-integrations-estimate-derived-thresholds, backend-common-ml-mape-aligned-point-prediction, infrastructure-observability-alerting] +--- + +# Rating a Per-Entity Signal While a Shared Driver Moves Every Entity + +## When this applies + +Code classifies a per-entity observation as entity-specific strength or weakness +— a stock's price move or buy-flow (institutional + foreign net buying), a +store's sales jump, a service's latency change, a cohort's conversion lift — and +a driver shared by every entity in the population (the market index, a holiday, +a platform-wide release, seasonality) moves all of them at once; deciding the +inputs a signal grader needs before it labels anything "strong". + +## Do this + +1. **Compute the benchmark's return over the same window as the entity's, and + grade the difference, not the raw move.** Excess return = entity return − + benchmark return over identical start and end points. A signal earns + "entity-specific" only when the excess is positive (or beyond a floor you + set); a raw +0.88% day on a +0.97% index day is a −0.09% excess, a lag. + +| Observation | Read it as | +|-------------|------------| +| Entity up, excess > 0 | Entity-specific strength — grade the signal | +| Entity up, excess ≤ 0 | Benchmark-driven; the entity lagged — grade as neutral or weak | +| Entity flat or down, excess > 0 | Relative strength (held up while the population fell) — grade it | +| Benchmark unavailable for the window | Withhold the grade and log the missing benchmark; a grade computed without it is the raw move relabelled | + +2. **Use one window for both sides and carry it in the record.** Store + `window_start`, `window_end`, `entity_return`, `benchmark_return`, `excess` + next to the grade so a reviewer can recompute; a same-day flow signal graded + against a 5-day price window is two different claims. + +3. **Treat a population-wide signal burst as a driver, not a discovery.** When + the same "strong" label fires on most large entities on one day, the shared + driver is the cause; gate the grade on excess return and on the share of the + population also flagged (a flag on more than a set share of the universe is a + market event, not that many signals). + +4. **Choose the benchmark by what the entity is compared against in the + consumer's decision** — a broad index for market-wide flow signals, a sector + index for sector rotation, a control cohort for product experiments — and + name it in the output. + +## Edge cases + +| Case | Then | +|------|------| +| The entity's window includes a day the benchmark did not trade (holiday, halt) | Align to the entity's trading days: take the benchmark's values on those exact dates; a calendar-day window mixes different day counts | +| The benchmark is a composite the entity dominates (a mega-cap in a cap-weighted index) | Use an equal-weighted or ex-entity benchmark; otherwise the entity is measured against itself | +| Only daily flow counts (buyer categories) exist, with no benchmark flow series | Grade the flow on the price excess return over the same window; flow direction alone is the population-wide signal this page warns about | +| The consumer wants the raw move too | Emit both fields; the grade is computed from the excess, the raw move is displayed | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Label "institutions and foreigners both net-bought and the stock rose" as strength | Compute the stock's excess return over the index for the window first | In an index rally nearly every large entity carries that pattern; without the benchmark the label's discrimination is near zero | +| Compare today's move to the index but grade a multi-day trend | Align both sides to the same window before grading | A one-day beat inside a five-day lag reverses the conclusion (measured: +0.88% vs +0.97% on the day, +4.44% vs +5.21% over five days) | +| Skip the benchmark when its feed is down | Withhold the grade and alert on the missing input | A grade emitted without its denominator is silently degraded output ([infrastructure-observability-alerting], fallback row) | + +## Sources + +- https://corporatefinanceinstitute.com/resources/equities/abnormal-return/ — "Excess Return = Actual Return − Expected Return", with the market index as the expected return in the worked example (12% − 14% = −2%) +- https://www.wallstreetprep.com/knowledge/information-ratio/ — active performance is "the excess return over a benchmark", the standard denominator for judging entity-specific performance +- Field measurement 2026-08-26 (stock-signal-bot, KRX): close 1,531,000 (08-18) → 1,599,000 (08-26) = +4.44% while the KOSPI 5-day return was +5.21%; the same day's +0.88% sat under the index's +0.97%; the buy-flow signal graded as strength was a lag once benchmarked, and the grader was changed to require positive excess return diff --git a/wiki/backend/index.md b/wiki/backend/index.md index 7f86f69..0f3c68e 100644 --- a/wiki/backend/index.md +++ b/wiki/backend/index.md @@ -5,7 +5,7 @@ three stack subtrees — route by concern first, stack second: | Subtree | Route there when | |---------|------------------| -| [common](#common-language-agnostic) (below) | The concern is language-agnostic: API contracts, enumerating call sites before a contract change, idempotency, JWT issuance, outbound calls, caching, jobs, transactions in app code, shared state/pools, exception structure, consuming LLM APIs (completion validation, context budgeting), MAPE-aligned point-prediction calibration, consuming external-API responses, externally-owned defaults, object-storage references, sync-vs-async integration choice, WebSocket/SSE connection lifecycle | +| [common](#common-language-agnostic) (below) | The concern is language-agnostic: API contracts, enumerating call sites before a contract change, idempotency, JWT issuance, outbound calls, caching, jobs, transactions in app code, shared state/pools, exception structure, consuming LLM APIs (completion validation, context budgeting), MAPE-aligned point-prediction calibration, benchmark-relative signal rating, consuming external-API responses, externally-owned defaults, object-storage references, sync-vs-async integration choice, WebSocket/SSE connection lifecycle | | [java](java/index.md) | You are writing/reviewing JVM backend code (Java/Kotlin, Spring, JPA/Hibernate) and the concern is stack-specific: entity mapping, persistence context, proxy pitfalls, JVM threads/memory | | [node](node/index.md) | You are writing/reviewing Node.js/TypeScript backend code: event-loop blocking, promise error handling, runtime validation at boundaries, graceful shutdown | | [python](python/index.md) | You are writing/reviewing Python backend code: GIL/concurrency model, pydantic validation, WSGI/ASGI workers, language traps, packaging data files and resolving them after install | @@ -103,6 +103,7 @@ Match your situation to a "load when" line; load only matching pages. | Page | Load when | |------|-----------| | [mape-aligned-point-prediction](common/ml/mape-aligned-point-prediction.md) | A regression model evaluated by MAPE was trained as a median predictor (log target + L1 loss, or quantile q50) — deciding what point value to emit, or the model systematically overpredicts on MAPE despite fitting well; choosing between a global scale factor and per-row variance-based correction | +| [benchmark-relative-signal-classification](common/ml/benchmark-relative-signal-classification.md) | Code grades a per-entity observation (a stock's move or buy-flow, a store's sales jump, a cohort's lift, a service's latency change) as entity-specific strength while a driver shared by the whole population (market index, holiday, platform-wide release, seasonality) moves every entity; the same "strong" label fires across most of the population on one day; choosing the benchmark and the window a signal grader needs; the benchmark feed is missing | ### integrations diff --git a/wiki/databases/index.md b/wiki/databases/index.md index c90f588..9f76b43 100644 --- a/wiki/databases/index.md +++ b/wiki/databases/index.md @@ -83,3 +83,4 @@ Match your situation to a "load when" line; load only matching pages. | [isolation-level-selection](transactions/isolation-level-selection.md) | Check-then-act writes, lost updates, duplicate bookings, choosing isolation/locking; deadlock-detected errors; oversell despite @Transactional | | [optimistic-vs-pessimistic-locking](transactions/optimistic-vs-pessimistic-locking.md) | Multi-step read-modify-write that cannot fold into one UPDATE — choosing version-column optimistic vs FOR UPDATE by conflict frequency; stale form submits; retry storms on hot rows | | [application-clock-vs-database-timestamps](transactions/application-clock-vs-database-timestamps.md) | A predicate compares an application-produced timestamp against a DB-filled column (`created_on > $boundary`, "older than N minutes"); one step classifies rows by a time boundary and a later step cancels/deletes what it chose; choosing between `RETURNING` the DB clock and adding a skew margin; deciding whether timestamp order can stand in for commit order | +| [multi-row-reorder-on-a-shared-resource](transactions/multi-row-reorder-on-a-shared-resource.md) | Designing or reviewing an action that rewrites many rows of one parent at once (drag-to-reorder positions, bulk status change, re-ranking) on a resource several clients edit; a `unique (parent_id, position)` constraint exists or is proposed and the rewrite fails mid-way; deciding the transaction boundary, the constraint's deferral, and the final-state rule when two reorders race; writing the two-writer test | diff --git a/wiki/databases/schema-design/online-schema-changes.md b/wiki/databases/schema-design/online-schema-changes.md index 2c564c9..27b37d6 100644 --- a/wiki/databases/schema-design/online-schema-changes.md +++ b/wiki/databases/schema-design/online-schema-changes.md @@ -2,11 +2,16 @@ id: databases-schema-design-online-schema-changes domain: databases category: schema-design -applies_to: [postgresql] +applies_to: [postgresql, prisma] confidence: verified sources: - https://www.postgresql.org/docs/current/sql-altertable.html -last_verified: 2026-07-23 + - https://www.postgresql.org/docs/current/sql-createindex.html + - https://github.com/prisma/orm/issues/22922 + - https://github.com/prisma/orm/discussions/10601 + - https://github.com/prisma/orm/issues/14456 + - https://github.com/prisma/prisma-engines/blob/main/schema-engine/ARCHITECTURE.md +last_verified: 2026-09-06 related: [databases-schema-design-column-data-types, databases-schema-design-foreign-keys-and-referential-actions, databases-indexing-index-write-cost, databases-schema-design-nullability-and-defaults] --- @@ -48,6 +53,8 @@ both shapes across the window, so DB migration and app release need not be atomi |------|------| | Type change where old type is binary-coercible to new (e.g. `text`→`varchar`, no collation change) | No rewrite, and indexes are not rebuilt — the fast path. Verify against your exact types before assuming instant | | `CREATE INDEX CONCURRENTLY` fails midway | It leaves an `INVALID` index that still costs writes; `DROP INDEX` it and retry, don't leave it | +| The migration runner executes a multi-statement migration file as one implicit transaction (Prisma Migrate on PostgreSQL sends the file through a multi-statement simple-query call) | `CREATE INDEX CONCURRENTLY` fails with `25001 cannot run inside a transaction block`. Put that statement alone in its own migration file, on one line, with no `SET lock_timeout` or second statement beside it — a one-statement file is not wrapped, and Prisma has no per-migration opt-out | +| The rule's premise is absent: the index targets a table created in the same release, or one with no live traffic | Plain `CREATE INDEX` inside the migration — `CONCURRENTLY` buys nothing on an empty or idle table and adds the transaction constraint above. Check the premise (row count, live traffic) before applying any row of the Do table, then check the runner can execute the chosen form | | The brief `ACCESS EXCLUSIVE` step queues behind a long-running query | The `ALTER` waits for the lock *and every query that arrives behind it also waits* — a lock queue pile-up. Set a short `lock_timeout` on the DDL and retry, so it backs off instead of freezing traffic | | Batched backfill during expand | Keep each batch a short transaction and throttle — a single `UPDATE` over the whole table holds row locks and generates dead tuples faster than autovacuum clears them ([databases-operations-autovacuum-and-wraparound]) | | Adding a volatile default / generated / identity column is unavoidable | It rewrites the table under `ACCESS EXCLUSIVE`; schedule it as a maintenance-window operation, not a live deploy | @@ -55,4 +62,9 @@ both shapes across the window, so DB migration and app release need not be atomi ## Sources - https://www.postgresql.org/docs/current/sql-altertable.html — lock levels per ALTER form, NOT VALID / VALIDATE, non-volatile default fast path, type-change rewrite rules -- https://www.postgresql.org/docs/current/sql-createindex.html — CREATE INDEX CONCURRENTLY lock behavior +- https://www.postgresql.org/docs/current/sql-createindex.html — CREATE INDEX CONCURRENTLY lock behavior; "a regular CREATE INDEX command can be performed within a transaction block, but CREATE INDEX CONCURRENTLY cannot" +- https://github.com/prisma/orm/issues/22922 — "Confusing transaction semantics in Postgres migrations": a migration holding two `CREATE INDEX CONCURRENTLY` statements fails with `cannot run inside a transaction block` while a one-statement migration succeeds; the reporter traced the wrapping to the engine's multi-statement simple-query path, and a later comment reports a single statement split across lines being wrapped too +- https://github.com/prisma/orm/issues/14456 — "Support `CREATE INDEX CONCURRENTLY` (PostgreSQL)": open feature request carrying the same `cannot run inside a transaction block` error +- https://github.com/prisma/orm/discussions/10601 — "Disable transactions for a single migration": `ERROR: CREATE INDEX CONCURRENTLY cannot run inside a transaction block` under Prisma Migrate; no per-migration opt-out exists +- https://github.com/prisma/prisma-engines/blob/main/schema-engine/ARCHITECTURE.md — "Why does Migrate not run migrations in a transaction by default?": Migrate adds no `BEGIN`/`COMMIT` of its own, which is why the wrapping above is implicit and depends on statement count +- Field evidence 2026-08-31 (linkly-calendar, prisma 6.19.2): a plan prescribed `CREATE INDEX CONCURRENTLY` for a table created in the same migration; the table had no rows and the two-statement file would have run in the implicit transaction, so the plan was changed to a plain `CREATE INDEX` diff --git a/wiki/databases/transactions/isolation-level-selection.md b/wiki/databases/transactions/isolation-level-selection.md index 9ad88cc..217a661 100644 --- a/wiki/databases/transactions/isolation-level-selection.md +++ b/wiki/databases/transactions/isolation-level-selection.md @@ -8,7 +8,7 @@ sources: - https://www.postgresql.org/docs/current/transaction-iso.html - https://dev.mysql.com/doc/refman/8.0/en/innodb-transaction-isolation-levels.html last_verified: 2026-07-10 -related: [databases-query-optimization-existence-and-count-checks, databases-schema-design-requirements-to-tables, databases-transactions-optimistic-vs-pessimistic-locking] +related: [databases-query-optimization-existence-and-count-checks, databases-schema-design-requirements-to-tables, databases-transactions-optimistic-vs-pessimistic-locking, databases-transactions-multi-row-reorder-on-a-shared-resource] --- # Choosing Transaction Behavior for Concurrent Writes diff --git a/wiki/databases/transactions/multi-row-reorder-on-a-shared-resource.md b/wiki/databases/transactions/multi-row-reorder-on-a-shared-resource.md new file mode 100644 index 0000000..e699216 --- /dev/null +++ b/wiki/databases/transactions/multi-row-reorder-on-a-shared-resource.md @@ -0,0 +1,88 @@ +--- +id: databases-transactions-multi-row-reorder-on-a-shared-resource +domain: databases +category: transactions +applies_to: [postgresql, general] +confidence: verified +sources: + - https://www.postgresql.org/docs/current/explicit-locking.html + - https://www.postgresql.org/docs/current/sql-createtable.html + - https://www.postgresql.org/docs/current/sql-set-constraints.html + - https://www.postgresql.org/docs/current/sql-insert.html + - "Field evidence 2026-08-31 (linkly-calendar orchestration run, review findings t1 F2 / t3 F1): a shared-calendar reorder shipped as unguarded per-row updates; fixed to a parent-row lock inside one transaction with a DB upsert" +last_verified: 2026-09-06 +related: [databases-transactions-isolation-level-selection, databases-transactions-optimistic-vs-pessimistic-locking, backend-common-orm-transaction-boundaries] +--- + +# Rewriting Several Rows at Once on a Resource Several Writers Share + +## When this applies + +Designing or reviewing an operation that updates many rows of one parent in a +single user action — drag-to-reorder positions in a shared list, bulk status +changes, re-ranking, re-numbering — where the parent (a shared calendar, board, +playlist, team) is edited by more than one client; a review asks "what happens +when two users reorder at once"; a `unique (parent_id, position)` constraint +exists or is proposed. + +## Do this + +**Settle three things at design time, before the first migration; on a shared +resource the concurrent writer is the normal path, not an exception.** + +1. **Transaction boundary: one transaction per user action, holding a lock on + the parent row.** `SELECT ... FROM parent WHERE id = $1 FOR UPDATE` as the + first statement, then every child write, then commit. Two simultaneous + reorders then run one after the other, each seeing the other's committed + result; without it, row-level last-writer-wins interleaves two intended orders + into one nobody asked for ([databases-transactions-isolation-level-selection], + multi-row invariant row). + +2. **Uniqueness on position: decide whether the constraint exists, and how the + transient state passes it.** + +| Constraint on `(parent_id, position)` | Do | +|---------------------------------------|----| +| Wanted, and the write is a full rewrite of the parent's rows | Declare it `UNIQUE (...) DEFERRABLE INITIALLY DEFERRED` so the check runs at commit, or run `SET CONSTRAINTS ... DEFERRED` inside the transaction; an immediate constraint fails mid-rewrite when row B takes A's old slot before A has moved | +| Wanted, and the constraint must stay immediate | Two-phase write inside the transaction: shift every affected row to a temporary range (`position = -position`, or `+ offset`), then to the final values | +| Not wanted | The parent lock in step 1 is the ordering guarantee; state in the schema comment that positions are unique by protocol, not by constraint, and add the uniqueness assertion to the reorder's tests | + +3. **Final-state rule: define what the committed result is when writes race.** + Pick one and encode it in the write: + +| Rule | Write shape | +|------|-------------| +| Whole-list replace (the last committed action defines the full order) | The client sends the full ordered id list; the server rewrites all positions with `INSERT ... ON CONFLICT (parent_id, item_id) DO UPDATE SET position = EXCLUDED.position` (upsert) inside the locked transaction | +| Merge (each action moves one item; the others keep their relative order) | The server recomputes positions from the current committed order plus the one move; the client sends the move, not the list | +| Reject stale (the action was computed on an older list) | Version column on the parent; the write carries the version it read; 0 affected rows → 409 ([databases-transactions-optimistic-vs-pessimistic-locking]) | + +4. **Test with two writers.** One test opens two transactions, issues both + reorders, and asserts that the committed order equals what the chosen rule + predicts and that positions are unique after both commit; a single-writer + test proves none of the three decisions. + +## Edge cases + +| Case | Then | +|------|------| +| Rows are inserted and deleted in the same action as the reorder | Same transaction, same parent lock; delete first, then upsert positions, so a deleted row cannot hold a slot the constraint check sees | +| The ORM issues one `UPDATE` per row under autocommit | Wrap the action explicitly ([backend-common-orm-transaction-boundaries]); per-row autocommit is the interleaving case in step 1 | +| Many parents are reordered in one request | Lock the parent rows in one ordered `SELECT ... FOR UPDATE` (ascending id) so lock order stays global and deadlocks are avoided | +| Positions are fractional or sparse keys (LexoRank-style) so a move touches one row | Step 2's constraint question disappears for the moving row, but two movers inserting between the same neighbours still produce equal keys; keep the parent lock, or make the key unique and retry on conflict | +| Readers must see a consistent order mid-rewrite | Readers at Read Committed see either the pre-commit or the post-commit order, never the interleaving, because the rewrite is one transaction | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Update each row's position in its own statement with no surrounding transaction | One transaction per action, parent row locked first | Two concurrent actions interleave per row and commit an order neither user intended | +| Add `UNIQUE (parent_id, position)` and update positions in place | Make it `DEFERRABLE INITIALLY DEFERRED`, or write through a temporary range | An immediate unique check fails on the first row that lands in another row's not-yet-vacated slot | +| Treat the concurrent-reorder case as "rare, handle later" | Decide the three rules now and test with two writers | On a shared resource the second writer is the default; a later retrofit changes the write shape and the API | + +## Sources + +- https://www.postgresql.org/docs/current/explicit-locking.html — `FOR UPDATE` "causes the rows retrieved by the SELECT statement to be locked as though for update. This prevents them from being locked, modified or deleted by other transactions until the current transaction ends" +- https://www.postgresql.org/docs/current/sql-createtable.html — `DEFERRABLE` / `NOT DEFERRABLE`: "Checking of constraints that are deferrable can be postponed until the end of the transaction (using the SET CONSTRAINTS command)" +- https://www.postgresql.org/docs/current/sql-set-constraints.html — "DEFERRED constraints are not checked until transaction commit" +- https://www.postgresql.org/docs/current/sql-insert.html — `ON CONFLICT DO UPDATE`, "also known as UPSERT" +- Field evidence 2026-08-31 (linkly-calendar orchestration run, review findings t1 F2 and t3 F1): the shared-calendar reorder was implemented as unguarded per-row updates; the review required the three decisions above and the fix landed as a pessimistic parent lock inside a single transaction with a DB-level upsert diff --git a/wiki/databases/transactions/optimistic-vs-pessimistic-locking.md b/wiki/databases/transactions/optimistic-vs-pessimistic-locking.md index f29c42b..4ff36f6 100644 --- a/wiki/databases/transactions/optimistic-vs-pessimistic-locking.md +++ b/wiki/databases/transactions/optimistic-vs-pessimistic-locking.md @@ -8,7 +8,7 @@ sources: - https://vladmihalcea.com/optimistic-vs-pessimistic-locking/ - https://www.postgresql.org/docs/current/explicit-locking.html last_verified: 2026-07-10 -related: [databases-transactions-isolation-level-selection] +related: [databases-transactions-isolation-level-selection, databases-transactions-multi-row-reorder-on-a-shared-resource] --- # Optimistic vs Pessimistic Locking diff --git a/wiki/frontend/design/anti-slop-visual-design.md b/wiki/frontend/design/anti-slop-visual-design.md index b93ee6d..20569ab 100644 --- a/wiki/frontend/design/anti-slop-visual-design.md +++ b/wiki/frontend/design/anti-slop-visual-design.md @@ -9,7 +9,7 @@ sources: - "https://claude.com/blog/improving-frontend-design-through-skills — Anthropic Engineering, 2025-11-12 (distributional-convergence mechanism, font avoid-list + taxonomy, extreme-contrast thresholds, domain-derived aesthetic direction, second-order convergence warning; Korean translation: velog.io/@xxziiko)" - "https://github.com/pbakaus/impeccable — README (gray-text-on-colored-background and bounce/elastic-easing anti-patterns; command list unstable across versions, cited for README-level rules only)" last_verified: 2026-08-21 -related: [frontend-design-html-in-canvas, frontend-accessibility-interactive-elements, frontend-design-responsive-layout, frontend-design-design-canvas-workflow] +related: [frontend-design-html-in-canvas, frontend-accessibility-interactive-elements, frontend-design-responsive-layout, frontend-design-design-canvas-workflow, frontend-design-lightness-steps-on-dark-surfaces] --- # Making Web UI Look Designed, Not Generated @@ -53,7 +53,7 @@ Color: | No pure `#000`/`#fff` | Tint paper and ink toward the anchor hue: light paper `oklch(96–98% 0.005–0.015 H)`, dark paper `oklch(12–16% 0.008–0.015 H)` | | Tint the grays toward the anchor hue | A warm accent with cool-gray body copy reads wrong even to viewers who can't name why; on colored surfaces, tint the text toward the surface hue too — neutral gray text on a colored background is a recognized generated-design tell | | One dominant color field, sharp minority accent | "Dominant colors with sharp accents outperform timid, evenly-distributed palettes" (Anthropic); draw palette direction from IDE themes and cultural aesthetics rather than generic web-palette generators | -| Dark mode: elevation is lightness, not shadow | Higher surface ≈ +3% lightness per level; keep the hue fixed across modes; reduce body font-weight by ~50 to offset light-on-dark optical bolding | +| Dark mode: elevation is lightness, not shadow | Higher surface ≈ +3% lightness per level; keep the hue fixed across modes; reduce body font-weight by ~50 to offset light-on-dark optical bolding; a state a user must *identify* (selected, on/off) cannot ride on a lightness step below L 30% — measure the sRGB ratio and carry it on outline, chroma, or shape ([frontend-design-lightness-steps-on-dark-surfaces]) | Typography, layout, motion, states: diff --git a/wiki/frontend/design/lightness-steps-on-dark-surfaces.md b/wiki/frontend/design/lightness-steps-on-dark-surfaces.md new file mode 100644 index 0000000..37efbd2 --- /dev/null +++ b/wiki/frontend/design/lightness-steps-on-dark-surfaces.md @@ -0,0 +1,83 @@ +--- +id: frontend-design-lightness-steps-on-dark-surfaces +domain: frontend +category: design +applies_to: [css, general] +confidence: verified +sources: + - https://www.w3.org/TR/WCAG21/ + - https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html + - https://www.w3.org/TR/css-color-4/ + - "Local reproduction 2026-09-06: WCAG contrast ratios recomputed from the engine-measured sRGB bytes; a CSS Color 4 OKLab→sRGB conversion reproduced those bytes within ±2" +last_verified: 2026-09-06 +related: [frontend-design-anti-slop-visual-design, frontend-accessibility-interactive-elements] +--- + +# Distinguishing States by OKLCH Lightness on a Dark Surface + +## When this applies + +Designing fill colors for a dark UI where two or more states (idle / hover / +selected / disabled, or elevation levels) are told apart by OKLCH lightness +steps and any step sits below about L 30%; writing `oklch(L C H)` token ladders +for dark surfaces; reviewing a dark-theme token set that calls its steps +"perceptually distinct" because the L values differ. + +## Do this + +1. **Verify a step by the sRGB contrast ratio it produces, not by the lightness + difference.** Convert each token to sRGB (the browser does this at paint + time), compute WCAG relative luminance, and take `(L1 + 0.05) / (L2 + 0.05)`. + The OKLCH `L` axis is perceptually uniform, but the sRGB bytes a dark color + lands on are few and close together, and the `+ 0.05` term flattens every + difference near black — so equal `L` steps yield shrinking ratios as they go + darker. Measured on a WKWebView (Tauri, macOS), chroma ≈ 0.015, hue ≈ 215: + +| OKLCH L | sRGB | Ratio vs L 13% | +|---------|------|----------------| +| 13% | rgb(3, 8, 10) | 1.00 | +| 19% | rgb(14, 21, 23) | 1.09 | +| 22% | rgb(20, 28, 30) | 1.15 | +| 27% | rgb(31, 40, 43) | 1.34 | +| 72% | rgb(155, 167, 170) | text-range reference | + + Four rungs of a 5–6 pp ladder never reach 1.4:1; a 6 pp step that is obvious + at L 70% is 1.09:1 at L 13%. + +2. **Pick the axis by what the difference must carry:** + +| The difference must | Do | +|---------------------|----| +| Identify a component or its state boundary (selected vs idle, focus, a toggle's on/off fill) | Meet WCAG 1.4.11's 3:1 against the adjacent color. At L < 30% that ratio is out of reach for lightness alone inside a dark palette, so carry it on another axis: an outline or border token, a chroma jump (tinted vs neutral), an icon or shape change, or lightness from a far rung (L ≥ 45%) | +| Suggest elevation or grouping (card on page, hover wash) that a user need not detect to operate the UI | Lightness steps stay — the +3%-per-level elevation rule in [frontend-design-anti-slop-visual-design] is this case — and the ratio check records them as decorative | + +3. **Keep the check in the token pipeline.** Store the ladder as OKLCH tokens, + generate the sRGB values and pairwise ratios with a script at build or review + time, and fail the review when a pair the table marks as "identify" is under + 3:1. Name the measuring script beside the tokens so a later ladder edit + re-runs it. + +## Edge cases + +| Case | Then | +|------|------| +| A hand-written OKLCH→sRGB converter disagrees with the browser | Trust the engine: draw the color to a canvas and read it back (`fillStyle` + `getImageData`), then fix the converter until it matches byte for byte — the measurement above was validated this way on four rungs | +| The ladder is for text on the dark surface | Apply the text thresholds (4.5:1 body, 3:1 large) against the surface; a 72% rung on a 13% surface is in range, the low rungs are not | +| Only the darkest rung is out of reach and the ladder is otherwise fine | Move the whole ladder up (start at L ≥ 20%) rather than widening one gap; per-rung tweaks reintroduce the unevenness OKLCH was chosen to avoid | +| The state is also signalled by a border, icon, or text label with its own 3:1 ratio | The fill step may stay decorative; the check applies to whichever cue carries the identification | + +## Instead of + +| If you are about to | Do this instead | Why | +|---------------------|-----------------|-----| +| Approve a dark-state ladder because the `L` values differ by 5–8 pp | Compute the sRGB contrast ratio for each adjacent pair and gate on it | Below L 30% those steps land at about 1.1–1.3:1 — under the 3:1 UI-component floor | +| Widen the lightness gap until the state reads | Switch the distinguishing cue to outline, chroma, or shape, keeping the fills close | A gap wide enough to read pulls the "dark" state into mid-tones and breaks the dark theme; another axis buys the distinction without the lightness cost | +| Cite "OKLCH is perceptually uniform" as proof the steps are visible | Cite the measured ratio | Uniformity describes the axis; visibility is the ratio of the sRGB colors the page paints | + +## Sources + +- https://www.w3.org/TR/WCAG21/ — contrast ratio defined as `(L1 + 0.05) / (L2 + 0.05)` on sRGB relative luminance +- https://www.w3.org/WAI/WCAG21/Understanding/non-text-contrast.html — SC 1.4.11: user-interface components and the visual information required to identify them "have a contrast ratio of at least 3:1 against adjacent color(s)" +- https://www.w3.org/TR/css-color-4/ — OKLCH lightness "clearly reflect[s] the visual lightnesses" of colors (in contrast to HSL); sRGB is gamma-encoded and colors are encoded to it at paint time +- Local reproduction 2026-09-06 (Python, CSS Color 4 OKLab→linear-sRGB matrices): `oklch(13% 0.015 215)` → rgb(2, 9, 11), 19% → (12, 22, 24), 22% → (19, 28, 31), 27% → (30, 40, 43), 72% → (155, 167, 170), within ±2 of the engine-measured bytes; WCAG ratios from the measured bytes: 13% vs 19% = 1.091, 22% vs 27% = 1.15, 13% vs 27% = 1.339 +- Field measurement 2026-09-02 (linkly-crew, Tauri macOS WKWebView): canvas `fillStyle` + `getImageData` readback of the four rungs matched the project's OKLCH→sRGB converter byte for byte; the state ladder was redesigned onto outline and chroma cues diff --git a/wiki/frontend/index.md b/wiki/frontend/index.md index 88af713..c10e28e 100644 --- a/wiki/frontend/index.md +++ b/wiki/frontend/index.md @@ -86,3 +86,4 @@ Match your situation to a "load when" line; load only matching pages. | [anti-slop-visual-design](design/anti-slop-visual-design.md) | Styling or restyling web UI without a design spec; picking the theme/aesthetic direction for a new screen (the committed non-generic direction is the default, not an upgrade); output looks "AI-generated" or template-like; choosing colors, fonts, page structure, or motion for new UI; reviewing a UI diff for template tells; writing reusable design guidance for an LLM | | [responsive-layout](design/responsive-layout.md) | Building or reviewing UI that must work across viewport sizes (phone → desktop); choosing breakpoints, touch-target sizes, fluid type, or responsive images; a layout overflows horizontally or breaks on mobile; fixing a zoom/reflow accessibility failure (WCAG 1.4.4/1.4.10/2.5.8) | | [html-in-canvas](design/html-in-canvas.md) | Wanting shader/3D/canvas-composited effects on real interactive HTML (forms, buttons, sections); about to hand-draw UI widgets inside a canvas with manual hit-testing; adding a canvas effect layer to an existing page | +| [lightness-steps-on-dark-surfaces](design/lightness-steps-on-dark-surfaces.md) | Designing or reviewing dark-UI fill tokens where states or elevation levels differ by OKLCH lightness steps and any step sits below about L 30%; a token ladder is called "perceptually distinct" because its L values differ; deciding whether a state cue meets WCAG 1.4.11's 3:1 or must move to outline/chroma/shape; validating an OKLCH→sRGB converter against the browser | diff --git a/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md b/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md index 8aeb575..2a0d4d5 100644 --- a/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md +++ b/wiki/infrastructure/agent-orchestration/code-graph-as-orientation-layer.md @@ -10,7 +10,7 @@ sources: - https://getautonoma.com/blog/parallel-ai-agent-prs - https://www.tiarebalbi.com/en/blog/code-graphs-coding-agents-delivery-shape - https://github.com/Graphify-Labs/graphify -last_verified: 2026-09-04 +last_verified: 2026-09-06 related: [infrastructure-agent-orchestration-worktree-isolated-workers, infrastructure-agent-orchestration-session-context-token-budget, infrastructure-agent-orchestration-control-signals-vs-primary-artifacts, qa-process-completion-claims] --- @@ -81,6 +81,7 @@ dispatch; deciding whether a graph result can stand as evidence in a plan. | The graph is stale and rebuilding is cheap (graphify `update ` is AST-only, no LLM) | Offer the rebuild as an explicit choice to the human; it writes into the user's checkout outside the run's own state, so it is never run on the orchestrator's own initiative | | Workers run in git worktrees | The graph output directory is usually gitignored, so no worktree carries it; pass `--graph /graphify-out/graph.json` — a read of a main-checkout path is the sanctioned way to consume shared read-only input — and remember the graph reflects the integration base, not the worker's branch | | The CLI exits 0 on a missing node, a missing graph file, or a JSON decode error | Validate the graph file yourself (`jq -e '.nodes'`) and detect "no node matching" from stdout; the exit code carries no signal | +| `graphify update` (or its post-commit/post-checkout hook) on a repository whose graph exceeds 5,000 nodes exits 1 with "too large for HTML viz" | Read it as a visualization failure, not a graph failure: the rebuild writes `GRAPH_REPORT.md` and `graph.json` before the HTML export raises, so the graph is already current — confirm with the mtime and `jq -e '.nodes'` checks above and record the run as updated. The message's suggested `--no-viz` flag is not parsed by `update` (0.4.23); when the hook itself must exit 0, call `graphify.watch._rebuild_code` from Python with `graphify.export.to_html` replaced by a wrapper that catches that `ValueError` | | Routes, handlers, or imports are generated by framework convention, decorators, or dynamic imports | The graph under-reports edges there; widen the search with grep on the convention (route table, decorator name) before trusting a "no callers" result | | The graph and a file disagree | The file wins; record the disagreement as a staleness incident and drop the graph for the rest of the task | | Uncommitted edits exist in the checkout | The mtime-vs-log check does not count them; a graph is "fresh" relative to commits only — re-run the check after the edits land if the plan depends on them | @@ -100,3 +101,4 @@ dispatch; deciding whether a graph result can stand as evidence in a plan. - https://getautonoma.com/blog/parallel-ai-agent-prs — map each task to the files it will touch via the dependency graph before assigning; parallelize only disjoint file sets - https://www.tiarebalbi.com/en/blog/code-graphs-coding-agents-delivery-shape — a graph MCP exposing ten tools costs tens of thousands of context tokens whether queried or not - https://github.com/Graphify-Labs/graphify — `explain`/`path`/`query --budget`, `update` (AST-only, no LLM), `hook install` post-commit/post-checkout; exit-0-on-error, empty `graph` metadata, and the query-vs-explain quality gap measured on graphifyy 0.4.23 with a 1,306-node graph, 2026-09-04 +- Local source reading 2026-09-06 (graphifyy 0.4.23: `watch.py` `_rebuild_code`, `export.py` `MAX_NODES_FOR_VIZ = 5_000`, `__main__.py` `update`): the rebuild writes the report and `graph.json`, then `to_html` raises `ValueError("... too large for HTML viz. Use --no-viz ...")`; the `except` returns `False` and `update` exits 1; no `--no-viz` option is parsed anywhere in the CLI. Field reproduction 2026-09-04 (a 9,663-node repository): the failed `update` had already refreshed `graph.json`, and with `to_html` wrapped the same rebuild completed in about 3 s. PyPI listed graphifyy 0.9.54 as latest on 2026-09-06, so re-check `graphify update --help` on the installed version before relying on this row diff --git a/wiki/infrastructure/agent-orchestration/session-context-token-budget.md b/wiki/infrastructure/agent-orchestration/session-context-token-budget.md index c23e719..b44b107 100644 --- a/wiki/infrastructure/agent-orchestration/session-context-token-budget.md +++ b/wiki/infrastructure/agent-orchestration/session-context-token-budget.md @@ -85,6 +85,7 @@ an orchestrated run. | A subagent's result must reach the main session | Have it return only a text summary — the verbose intermediate output stays in the subagent's own context and is never itself pasted into the caller | | Several images must stay referenceable across a long session | Upload each through the Files API and pass its `file_id` instead of inlining base64 bytes on every turn | | The session is a single chat interaction rather than an orchestrated run | The 4x/15x/7x multipliers above do not apply; the accumulated-context mechanism in directive 1 still does | +| Measuring a live session's own context from the transcript store (Claude Code writes one `.jsonl` per session under `~/.claude/projects//`) | Pass the newest-mtime transcript file, not the directory: the directory accumulates every past session of that project, so a directory-wide report aggregates them all and old sessions past the threshold emit warnings unrelated to the run — the live session is the only file being written, so the newest file is it | ## Instead of @@ -105,3 +106,4 @@ an orchestrated run. - https://platform.claude.com/docs/en/build-with-claude/vision — current image-token formula `⌈width / 28⌉ × ⌈height / 28⌉` (28x28-pixel patches); base64 images resent in full on every turn; Files API `file_id` keeps payload size flat - https://www.anthropic.com/engineering/multi-agent-research-system — multi-agent systems measured at ~15x the tokens of a single chat interaction (~4x per individual agent); reserved for high-value, parallelizable, or context-exceeding tasks - Field evidence 2026-08-21 (dev-loop orchestration run): the coordinator session ended a run at approximately 501k tokens of accumulated context over 593 API calls, with approximately 164M cumulative cache-read tokens billed across the run; a post-run review found approximately 2.2MB of screenshot PNGs had been Read directly into the coordinator's own context over the run instead of delegated to a subagent per directive 3 — a measured instance of that directive's cost when skipped +- https://code.claude.com/docs/en/sessions — transcripts are stored as `/.jsonl`, where `` is the working-directory path with non-alphanumeric characters replaced by `-`. Measured 2026-08-25 (dev-loop repo): the project's directory held 49 `.jsonl` files and a directory-wide report printed 10 `warn:` lines, none for the live coordinator session (peak 216,403, under threshold); the newest-file form returned a 2-row table with empty stderr diff --git a/wiki/infrastructure/observability/alerting.md b/wiki/infrastructure/observability/alerting.md index 4a28926..56e1d9e 100644 --- a/wiki/infrastructure/observability/alerting.md +++ b/wiki/infrastructure/observability/alerting.md @@ -52,6 +52,7 @@ a condition should page, open a ticket, or only appear on a dashboard. | No SLO defined yet | Interim: static symptom thresholds with duration conditions, set from the current baseline; define the SLO, then convert to burn-rate alerts | | Single instance down, load balancer keeps user error rate at zero | Ticket or auto-heal, never a page — users are unaffected | | Batch/cron job with no live traffic to measure | Alert on absence of success ("job has not succeeded within its window"), not only on failure events — a job that never ran emits no failure | +| A pipeline has a fallback source and a primary-source failure silently switches to it | Alert on the primary source's success count reaching zero (or the fallback share crossing a threshold) as its own symptom: a run that completes on the fallback is degraded operation, not success, and the downstream symptom ("not enough signals today") is worded as the consumer's own shortfall, so it never names the outage | | Low-traffic service where one error spikes the error rate | Add a minimum-request-count condition alongside duration | ## Instead of @@ -62,8 +63,10 @@ a condition should page, open a ticket, or only appear on a dashboard. | Page on CPU > 80% | Page on latency/error SLO burn; keep CPU on the diagnosis dashboard | High CPU with healthy latency is not user impact | | Keep an "acknowledge and ignore" alert because it has always existed | Delete it or demote it to a ticket within the week | Every tolerated false page erodes trust in the next real one | | Page a human for a failure with a known automatic remediation | Automate the remediation; page only when it fails | Pages are for judgment, not for running a script | +| Count a run as healthy because the fallback filled every slot | Emit and alert on the primary-source usage count | The fallback masks the outage; the field case below ran green for seven days with zero output | ## Sources - https://sre.google/sre-book/monitoring-distributed-systems/ — symptoms vs causes; maximum signal, minimum noise; pages must be actionable - https://sre.google/workbook/alerting-on-slos/ — burn-rate alerting; fast-burn page vs slow-burn ticket, multiwindow conditions +- Field incident 2026-08-14 to 08-21 (korea-data-suite / stock-signal-bot): after the KRX credential expired, the pykrx primary failed on every run and the Naver fallback succeeded 29/30; every run logged normal completion while the signal count stayed at 0 for seven consecutive days and the Telegram notice said only "a day with too few signals"; a primary-success-count-is-zero alert was added diff --git a/wiki/testing/async/async-testing.md b/wiki/testing/async/async-testing.md index 49b91e1..0904226 100644 --- a/wiki/testing/async/async-testing.md +++ b/wiki/testing/async/async-testing.md @@ -57,6 +57,7 @@ un-awaited promises; or an async test intermittently interferes with the next te | Case | Then | |------|------| | Testing a debounce/throttle interval | Advance to just before the interval and assert nothing fired, then advance past it and assert it fired — both sides of the boundary | +| Asserting that a lock/lockout was *explicitly* cleared by an event that fires only after advancing fake time past the lock's own TTL (a 10 s auto-return clearing a 3 s lockout) | After the clearing event, rewind the fake system clock to just before the lock's original expiry (`vi.setSystemTime` / `jest.setSystemTime`) and assert "not locked" there, so the verdict is explained only by the explicit clear; then comment out the clear and require red ([testing-quality-tests-that-cannot-fail]) — with wait ≥ TTL the lock expires on its own and the test stays green with the clear deleted | | A condition wait (`waitFor`) is needed while fake timers are active | Advance the fake clock explicitly before/while awaiting the condition, or use the wait utility's fake-timer-aware mode; otherwise the poll's own timers never fire | | Runner reports an unhandled rejection after the suite passes | A promise was created without `await`/`return` — find it and await it; do not silence the warning | | Assertions run inside a `.then`/callback the test never awaits | Add `expect.assertions(n)` / `expect.hasAssertions()` so the test fails when the callback is skipped, then restructure to await-then-assert | @@ -81,3 +82,4 @@ un-awaited promises; or an async test intermittently interferes with the next te - https://testing-library.com/docs/dom-testing-library/api-async/ — `waitFor`/`findBy`: polling on a condition with interval and bounded timeout - https://martinfowler.com/articles/nonDeterminism.html — poll/callback on the completion condition instead of bare sleeps - Field reproduction 2026-08-04 (Node v25.8.1, `readline` over a `PassThrough`, three sequential `question` calls): a single `input.write('one\ntwo\nthree\n')` yielded `["one"]` and then hung; writing the same three lines one per `setImmediate` turn yielded `["one","two","three"]` and completed — the `question`/`'line'` interaction this behavior implies is not documented by https://nodejs.org/api/readline.html and is grounded only in this reproduction +- https://vitest.dev/api/vi.html — `vi.setSystemTime` under fake timers "simulates a user changing the system clock". Field reproduction 2026-08-21 (mechameleon-web, `room-engine.test.ts` "clears an active seek lockout via the auto-return"): with the rewind in place, commenting out `lockouts.clear()` turned exactly that test red; without it the 10 s advance outlived the 3 s lockout and the test could not fail