feat(settings): nửa giao diện của chống mất khoá BYO — ba bề mặt từ chối ghi đè kho hỏng - #94
Merged
Merged
Conversation
… guard Presentational half of the BYO-key loss guard, built for the S1-D design pass so Gate 1 could approve UI on something clickable. - store-unreadable-notice.tsx: the one card all three surfaces will render when the key store cannot be read. Words come in as props so each surface reads them from its own i18n namespace; it stamps data-proto-state's sibling identity marker so a hand-copied card fails by absence. - chong-mat-khoa-byo-giao-dien-proto.tsx: three states, stamping data-proto-state on the root; an unrecognised state renders unknown:<name> rather than falling through to a real frame. - proto/[slug]/page.tsx: THEME_PIN. layout.tsx adds .dark to <html> when localStorage has no theme and the OS prefers dark, so on a dark-set machine EVERY proto page rendered dark -- including the half a capture matrix or an a11y sweep called "light". Measured: a bare URL paints <html class="dark"> while curl on that same URL counts 0 occurrences of class="dark", so a guard reading curl output scores it light. An explicit theme param now pins the root class. A bare URL is left alone on purpose: three older prototypes and their guards predate this and are not this route's to re-measure. - scripts/settings/check-a11y-proto.sh: axe in real Chrome over 3 states x 2 themes, reading BOTH axes back off the RENDERED DOM via ui-capture --html rather than off curl, plus a distinct-pairs check. - tsconfig.json: declare build/kkt-a11y. This is not lint housekeeping -- it is what PREVENTS Next rewriting the file. Measured both arms: declared, the file is byte-identical after a run; undeclared, Next injects the path and reformats the whole file. Declare first, then run the guard. Scanning the light theme for the first time immediately surfaced a real serious violation (muted-foreground on a bg-destructive/5 tint at 12px = 4.33:1); fixed by moving hierarchy off colour onto size and weight.
T2. Six criteria AC-9..AC-14 were written and approved at the parent dossier's Gate 1, then left scope under Amendment 1 (31/08) and were kept verbatim in its Out of scope section for a successor contract. This is that successor. Two criteria widened by amendment, both from evidence rather than taste: - AC-10/AC-12 Given now covers all five read failures, not just 503. The verbatim text asked only for the 503 cell while the parent's own carry-forward lesson #2 demanded every read error; the two disagreed, and the current main leaves an empty saveable form in all five. - AC-11 must survive a double-click on the confirm button. Coverage scan: 3 axes, 39 meaningful cells, 7 Core (18%). Industry leg measured in place rather than recalled -- macOS Keychain refuses a write into a corrupt store (exit 45, bytes unchanged) but its READ path still reports empty, so the industry gets one half right and the half this contract fixes wrong. Gap probe returned 1 P0, 3 P1, 1 P2; all five fixed in artifacts, none deferred to the human. Evals 10 -> 12. Design pass: 12 frames, 3 states x 2 breakpoints x 2 themes, real components in a static frame. Four findings patched in place, including the light-theme blind spot that made the light half of every capture and every a11y sweep render dark; the first honest light scan then found a real serious contrast violation. Four findings deferred to this gate, one of which is that two ALREADY SIGNED dossiers carry the same blind spot -- reported, not re-pinned.
Three surfaces each rolled their own fetch against /api/settings/env and each got the error path wrong differently: the settings dialog swallowed failures into a logger and left an empty saveable form, abi-node-shell never checked the response at all, and the media-library panel checked but could only say 'read failed'. Writing that logic a fourth time is the mechanism, not an accident of any one of them. readEnvForBrowser states the SUCCESS condition -- 200 and parses and env is a plain object -- rather than enumerating failures, so a shape nobody has thought of lands on the safe side by construction. Not status === 503: a proxy 502 and an HTML error page never carry the code, and those are two of the three cases the parent dossier ordered carried forward. saveEnvKeys refuses to write onto a store it could not read (PUT replaces the whole map, so writing on a bad read is the original data-loss bug). replaceUnreadableStore is the only destructive write and lives here rather than in the settings component, so 'the node panels have no escape button' holds by construction. 14 cases: five failure shapes x (unreadable, no PUT), a positive control that a healthy store does merge and does write exactly once, and the flagged write carrying an empty env. Red direction run: narrowing the gate to status === 503 fails exactly 6 of them, each naming its shape.
… parity guard 13 new keys under Settings.storeUnreadable and Workspace.storeUnreadable, in all five locales. Vietnamese is verbatim the wording the approved prototype showed at Gate 1, so the shipped screen says what was approved. The guard compares ALL five files, not just the new namespaces: a test scoped to the new namespaces is satisfied by a new key placed in the WRONG namespace, which is the same self-satisfying shape this dossier exists to avoid. The 76 Japanese keys already missing on 2026-09-01 are frozen as a counted debt, and the count is checked in BOTH directions -- an allowlist that may only grow is how a parity guard quietly stops guarding, so a key since translated must be removed from the debt or this fails. Three red directions run, not argued: - adding the keys to en.json alone: 5 tests fail naming every missing key; - deleting one NEW key from ja: the work-package assertion fires, naming it; - setting the constant to 77 against a real debt of 76: fails, proving the pin is two-directional.
…figured one A corrupt key store used to surface as 'Chưa gọi được media-library: thiếu MEDIA_LIBRARY_API_KEY' -- a sentence that sends the user to enter their key again, walking them into the overwrite this dossier exists to prevent. ResolveConfigResult gains a third member, so the compiler forces both consumers to tell the two apart; it flagged four sites, which is the whole point of the repo's compile-time-only contract rule. ORDER IS THE DESIGN: the process.env fallback runs BEFORE any verdict about the store, so one corrupt file cannot take down an env-var deployment. Unreadable then wins over 'missing' even when the environment supplied SOME values, since the half we lack may be inside the file we cannot read. Five cases on a real temp dir, no mocked seam. Healthy fixtures go through saveEnvStore -- the app's own writer -- because a fixture the test shapes to match the reader only proves the two halves agree with each other. Two red directions run: - restoring the forgiving reader fails (c) and (e) while (d) STAYS GREEN, which is what distinguishes a fix from an over-correction that blocks env-var deployments; - hand-writing the healthy fixture instead of using saveEnvStore fails (a), proving the round trip is real. Five pre-existing suites stubbed this seam with loadEnvStore only; their stubs now also provide readEnvStore. 121/121 green across media-library.
…y store AC-10 and AC-11. The screen replaces the key form with a card naming the cause and promising nothing changed; Save stays MOUNTED and disabled rather than hidden, because hiding it teaches nobody that saving is what got blocked and moves every control below it between renders. One escape, behind an alertdialog that states both halves of what is about to happen, behind a useRef latch so a double click is still one write. All five read failures land in the blocked state, not just the 503 the criterion literally named: a proxy 502 and an HTML error page never carry the code. Red directions run, and one of them found a bug IN THE TEST: - gutting the reader to always report ok fails all five shapes, so each is load-bearing rather than decorative; - narrowing the gate to a 503-only check fails 2 of 5 (500 and shape). The plan predicted 4; 502-html and network survive because they are caught by separate gates, the JSON parse and the fetch try/catch. Recording the measured number rather than the predicted one; - hiding Save instead of disabling it fails all five blocked cases; - removing the latch produces 2 PUTs on a double click; - vague confirm wording fails, naming which half went missing; - a PUT body missing env did NOT fail at first. The assertion defaulted a missing env to an empty object before counting its keys, so the default supplied the very shape the assertion was meant to demand. It now checks presence, plain-object-ness and emptiness separately, and both a missing env and a carried-forward one fail loudly, printing the body.
…ken store AC-12. Neither the media-library config panel nor the key box inside abi-node-shell may send a write when the store cannot be read, and neither gets an escape button -- the destructive write lives only in env-client and is not reachable from either component, so that property is structural rather than a rule someone has to remember. abi-node-shell previously did its own read-then-write pair and never checked whether the read succeeded, so an unreadable store gave it env === undefined and it sent the PUT anyway. The server refused with a 409 and the data survived, but the request left the browser, which is what the criterion forbids. NodeKeyPrompt gains a store-unreadable phase that returns early: rendering the input beside a warning would leave a control that cannot work, and would leave this surface holding a write path. It is deliberately not the `invalid` phase -- that one is a statement about a KEY, and telling the user their key is bad invites them to type a new one into a store that cannot be read. Only the strings this dossier ADDS go through next-intl; the file's other hardcoded Vietnamese labels are pre-existing debt the contract explicitly declined to clean up. Measurement is the full matrix, 2 surfaces x 5 read-failure shapes = 10 cases per eval, with the case count pinned as its own assertion, plus positive controls proving each surface still writes on a healthy store. Red direction: narrowing the reader to a 503-only check -- the actual defect on main -- fails exactly 6 of the 10 wire cases (500, 502-html and shape on both surfaces), each naming surface, shape and the number of PUTs that escaped. The 503 and network shapes stay green because they are caught by other gates.
E11. The behaviour matrix in E6/E7 proves the three surfaces handle all five read shapes today; it cannot stop a fourth surface being written next month with a fourth hand-rolled fetch. Three places each rolling their own, all three wrong in a different way, is the MECHANISM that produced this bug rather than an accident of any one of them -- so one guard holds the shape while the tests hold the instances. It found something on its very first run: settings-dialog still named the endpoint directly in its normal save path. That path now goes through putEnvMap, which exists because the settings screen edits the whole map at once and needs cleared values to actually delete -- saveEnvKeys merges onto a fresh read and would resurrect every key the user just cleared. Different write, same one file naming the endpoint. Test files are excluded deliberately: they must name the endpoint to stub it, and a guard nobody can satisfy gets deleted, which is worse than not having one. The second arm catches an env-client that stops naming the endpoint at all -- renamed, emptied, refactored away -- because a guard passing for want of anything to find is measuring nothing. Teeth script perturbs a COPY of the tree three ways and asserts each: the real tree is green, a second non-test caller exits 1 AND names the offending file, and an emptied reader exits 2.
E12. The locale-parity guard only reads the five JSON files; the UI evals only assert Vietnamese literals. Both stay green when a call site hands the component a hardcoded Vietnamese label, and together they still cannot tell the difference, so AC-13 would report DONE while an English, Japanese, Korean or Chinese user reads a key-loss warning in Vietnamese. The template for that mistake already sits in a file this work package edits: abi-node-shell's KEY_PROMPT_BASE is hardcoded Vietnamese, pre-existing debt the contract explicitly declined to clean up. The test asserts en and vi DIFFER for each key before asserting the en value renders -- if the two catalogues ever agreed, the case would pass on a hardcoded string and prove nothing. A vi negative control catches the opposite error, a component that always answers in English. Writing it surfaced a bug in the shared harness: it located the dialog trigger by its Vietnamese label, so the screen could not be opened under any other locale -- exactly the axis this eval exists to exercise. The harness now derives the trigger name from whichever catalogue is in use. Red direction: replacing one t() call with the literal Vietnamese string fails, naming Settings.storeUnreadable.title.
Thirteen executor keys, added last so that check-eval-filters.mjs never saw a key pointing at a test file that did not exist yet. Every one is a whole-file run: no eval filters by test name, because `vitest -t` exits 0 when the filter matches nothing, whereas a whole-file run on an empty file fails loudly. Two evals added beyond the plan, for measurements that turned out to be load-bearing: E13 pins the shared reader itself (the foundation both AC-10 and AC-12 stand on), and E14 is the teeth for the E11 structural guard -- a static guard that is permanently green looks exactly like one whose grep is misspelled, and E14 is the only place that proves it can go red. config.yaml diff is additions only (0 removed lines), so it composes with the parallel session editing the same file. Contract moves to implemented. Verified on this tree: lint clean, typecheck clean, 797 passed / 5 skipped across 70 files, production build succeeds, verify:plugins OK, a11y sweep PASS with 6/6 pages drawing the state AND theme asked for, and tsconfig.json shows no diff after a full build -- the declare-the-dist-dir-first ordering holds. One environment repair on the way through, unrelated to this change: better_sqlite3.node was compiled for NODE_MODULE_VERSION 137 while this machine now runs Node v26.7.0, so four src/db tests could not load it. `pnpm rebuild better-sqlite3` fixed it. No db file, package.json or lockfile is touched by this dossier.
…ts that hid them
Verification found real bugs. Three were mine, and each was invisible to an eval
I had written to prevent exactly that class.
1. DEAD CONTROL. Both blocked on-canvas surfaces offered exactly one way
forward, a button dispatching "oneflow:open-settings" -- and nothing in the
tree ever listened for it. The user landed in a card whose single control did
nothing, which is a worse dead end than the state it was explaining. My E6
asserted a button with the right LABEL existed; it never asserted the button
DID anything. A label is not a behaviour.
Fixed as an exported constant with a registered listener, matching the repo's
own pattern (ONBOARDING_RECOVERY_EVENT, TASK_CANCEL_REQUEST_EVENT) instead of
a bare string duplicated at two dispatch sites. Both halves are now asserted,
in two files on purpose: a dispatch with no listener and a listener with no
dispatch look identical from either side alone.
2. VIETNAMESE LEAKING INTO FIVE LOCALES. readEnvForBrowser returned literal
Vietnamese reasons that three surfaces then interpolated into a localized
{reason} slot, so an English user read "...stored keys (phản hồi không phải
JSON)." The repo had already fixed this exact defect one file over. Failure
reasons are now a ReadFailure code union translated at the display boundary;
four new cause keys in all five locales.
3. STORE-UNREADABLE COLLAPSED AT THE SERVER BOUNDARY. resolveConfig grew a third
kind so a broken store stops reading as "not configured" -- and client.server
mapped both kinds back to MISSING_CONFIG. The node branches on the code
alone, so a broken store rendered the configure-me panel with zero fields and
a live Save button. It now has its own STORE_UNREADABLE code. The
add-media-library dossier's own AC-6 guard caught the missing sentence in all
five locales the moment the code appeared -- someone else's guard doing its
job on my change.
Also: pluginEnv is now Array-checked, so the module's stated "positive
assertion" invariant holds for both fields rather than one; and a stale
_acceptance/config.yaml.bak that config-patch left behind and `git add` swept in
is removed, with a *.bak ignore rule.
Every fix has its red direction run: reverting the event wiring fails all 10
matrix cases naming "dead control"; removing the listener fails the open-screen
case; both were verified to actually go red rather than assumed.
…ss plumbing Round 1 reported E10 failed with exit 4. It had not measured anything: the inlined args dropped the capture flags, so design-gate answered "no target file given". An args mistake that reads like a product failure is the worst kind, because the round shows red on a tree that is fine. E10 now runs the same shape as E9: its own dev server on its own dist dir, its own captures, and the same data-proto-state read-back, so a frame that silently fell back to another state is caught rather than graded. A command that captures its own input can be reproduced by hand and cannot fail for a reason unrelated to the tree. build/kkt-gate is declared in tsconfig BEFORE the wrapper ever ran -- the ordering established earlier today, where declaring the dist dir first is what PREVENTS Next rewriting the file rather than merely satisfying lint afterwards. Verified: tsconfig diff after the run is the one added line, nothing reformatted. 6/6 frames PASS. Still the reference slop measurement, not an accessibility result -- per AC-14 the criterion fails whenever the axe sweep is not PASS, whatever this says.
…ird time S4 round 2 passed all 14 evals and still returned REJECT: `pnpm typecheck` failed, and eight findings landed. Two things in it matter more than the list. THE SAME ERROR CLASS RECURRED. Round 1 found a Vietnamese literal leaking out of the READ path of a shared lib into four other locales; it was fixed. Round 2 found the identical literal still in the WRITE path, reaching users through `replaceFailed`. Repairing instances of one defect twice is the signal that the SHAPE permits it, so `SaveOutcome.detail` is no longer a string: it is a `WriteFailure` code union, and there is nowhere left in this module's public surface to put a sentence. The compiler now refuses what review had to catch. THE TYPECHECK FAILURE WAS MINE, not the known build race. Round 1 reported E10 failing because my inlined args dropped its capture flags; my fix gave it a self-contained wrapper with its own dist dir under build/ — which tsconfig's own `"**/*.ts"` sweeps, so transient files there raced tsc into TS6053. A REFERENCE measurement broke a command on the commit checklist. Reverted: the wrapper and its tsconfig line are gone, and E10 is recorded as a known limit for the human to rule on. AC-14 rests entirely on E9, which is what its own text always said. Findings fixed, each verified rather than assumed: - the client discarded the server's 409 ENV_STORE_UNREADABLE, so a store that broke between the read and the write surfaced on the node as "your key is invalid" — inviting exactly the retype this dossier exists to prevent; - a failed WRITE was rendered through the same invalid-key phase, for the same reason, on disk-full or a dropped connection; - settings save() swallowed every write failure into a logger: spinner stops, edits still on screen, nothing said. That is the silence this dossier was written to remove, in the screen that removes it; - the heading-level evidence was measured on a prototype whose outline the product does not have. Measured: base-node-shell, abi-node-shell, add-media-library-node, media-library-config-panel and node-key-prompt render ZERO headings between them, so the h2 the panels claimed to sit under exists only in the prototype the a11y sweep scans. The prototype now mirrors what ships and both surfaces take the default level; - `readFailed` was a required prop nothing read after the refactor; - the structural guard matched only double-quoted literals. Widening it to the bare path then flagged PROSE — the route documenting its own handlers, and a design comment. It now matches the path in any of the three JS string delimiters with comment lines dropped, and has teeth for all three cases including the one where prose must NOT trip it.
The only finding S4 round 2 triaged as IN CONTRACT, and it was in my own guard. JA_DEBT_COUNT = 76 was asserted with toBe, and the comment claimed the debt "can shrink; it can never silently grow". That was not what the code checked. jaDebt is recomputed from the files every run, so translating one debt key while adding one new untranslated key leaves the total at exactly 76 and the suite passes -- a new untranslated string ships green, which is the one thing the guard exists to stop. The work-package assertion covered only two namespaces; every other namespace was unguarded. The frozen list is now the actual 76 keys, compared as a set in both directions: a newly missing key fails wherever it lives, and a key that has since been translated must be removed from the list or the suite fails, so the allowlist cannot quietly become a place where debt accumulates. Red direction run, and it is the exact swap a count cannot see: translate Workspace.nodes.music.fast, delete Settings.storeUnreadable.title. Total debt measured afterwards: still exactly 76. The count-based guard was green on that tree; the set-based one fails naming Settings.storeUnreadable.title. Also corrects a claim I recorded wrongly. I wrote that declaring a transient build/ dist dir in tsconfig breaks typecheck with TS6053. Tested both arms here (build/kkt-probe declared, directory absent then present, static): typecheck clean both ways. Session amazing-kapitsa-45dd7a measured the same two arms on build/bko-a11y, also clean. An include glob matching nothing is a no-op and TS6053 is a file-not-found for an explicit reference. The real cause is CONCURRENCY -- tsc enumerating build/** while a dev server creates or tears down a dist dir underneath, during a parallel suite. That matters because the two write-ups give opposite advice, and only the concurrency one is actionable. It also means E9's a11y wrapper carries the same hazard: it boots a dev server under build/kkt-a11y and runs alongside pnpm typecheck. It was green this round by timing, not by immunity, and the dossier now says so.
…rvers S4 round 1 was REJECTed by a suite command, not an eval: pnpm typecheck exited 2 with TS6053 on two files under build/kkt-gate/types/**, while all 14 evals passed. Root cause, reproduced with `tsc --noEmit --listFiles`: tsconfig's broad "**/*.ts" include plus an exclude of only node_modules/desktop made tsc read 10 files under build/ — the transient dist dirs the three a11y wrappers spin dev servers into. tsc globbed them, then read them after the server had moved on. The build/*/types/** entries in `include` are NOT the cause and stay: they exist so Next finds its own entry already present and leaves tsconfig.json alone (next.config.ts:33 KNOWN COST). Next reads the include array, tsc reads exclude, so the two properties separate cleanly in one file. Measured on this tree: - tsc files under build/: 10 -> 0 - pnpm typecheck exit 0, pnpm lint:check exit 0 (biome accepts JSONC comments) - a real dev server on NEXT_DIST_DIR=build/kkt-a11y serving the proto route leaves tsconfig.json byte-identical (sha unchanged) - route type coverage intact: 37 .next/types/** files still in the program, including app/proto/[slug]/page.ts Scope: this is a repo-wide config fix landing inside a T2 dossier, which the contract's Known limits block had declared out of scope. That block is rewritten to match, and the trade is logged in decisions.jsonl for Gate 2. aml-a11y and bko-a11y get the same immunity without changing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… round 2's ledger Two rounds produced the same error class from different directories: tsc reading a generated types dir while a parallel process wrote it. Round 1 was build/kkt-gate/types/** (closed by the tsconfig exclude in c015514); round 2 was .next/types/**, written by `pnpm build` — which the 31/08 split had moved into the same parallel lane as `pnpm typecheck`. executors.test.build_typecheck was written sequential on purpose for exactly this race; its own comment says so. The 31/08 reason for splitting it (a tool-killed build swallowing typecheck's signal) is now covered by TOOL-KILL-RULE: a 600000ms floor plus a killedByTool field that tells "killed" apart from "failed". Both single keys stay in executors: — only suite_keys changes, so signed dossiers that reference them still resolve. Also lands round 2's evidence: the workflow returned runLogWriteFailed:true, so its report and 23 run-log lines were computed but never written. Persisted verbatim — the round counter reads `## Iterations`, and a missing round 2 would make the next round mint r2 run ids a second time. Round 2 itself: 13/13 evals PASS, failedEvals empty, and every one of the 11 review findings triaged inContract:false — so rejectFindings is empty and the machine is not allowed to touch any of them. They go to Gate 2, including a HIGH one where replaceUnreadableStore can wipe a healthy store after a transient read error. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…SS, 13/13 evals All seven suite commands exit 0, including `pnpm build && pnpm typecheck` — the merged sequential key that closed the race two rounds spent chasing. Two corrections applied to the machine's output, both stated rather than silent: - runLogWriteFailed came back true for the third round running, so the report and 22 run-log lines were persisted by hand, verbatim. - The round-3 synthesizer rewrote the whole `## Iterations` section from its own view and got the history wrong: it described round 1 as "13/13 evals, four files missing under .next/types/**", which is round 2's shape. Round 1 was 14/14 with two files under build/kkt-gate/types/**. The round 1 and round 2 lines are restored verbatim from the versions committed at 7c78ec4 and 4d8dd32. Verdict is PASS but the review is not silent: 13 findings, 3 of them inContract and all three about measurement shape rather than product code (five error shapes collapsed to one bit; AC-13's second half asserted by presence rather than relation; an early return that swallows a case's assertions). The other 10 are out of contract and include three HIGH product defects, the sharpest being that the destructive store wipe is authorized by a client-side read failure the server never re-checks. All of it goes to Gate 2. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er dropped pre-merge-check answered "xanh-sạch — máy đi tiếp, KHÔNG mời ký", and its own reason line says why: "Known limits rỗng · Ngoài hợp đồng rỗng". But triage returned 13 findings, 10 of them inContract:false, three of those HIGH. The synthesizer computed them and left the section empty, so the gate read that emptiness as "nothing for the owner to decide" and skipped the human. Lands what the machine already knew: - review-findings.md rewritten from round 3 (it was still round 1's, from 01/09) - `## Ngoài hợp đồng` now carries Ngoài-1..10; the "Owner chọn" column stays blank on purpose — that column is the Gate 2 decision, not the machine's. The sharpest of the three HIGH: the destructive store wipe is authorized by a client-side read failure that the server never re-checks, so a transient 401/502 during a settings open can end with an empty PUT erasing a healthy key store — the same key loss this dossier exists to prevent, reached by another door. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…2 pending
CI blocked the PR on a dossier that is not this branch's subject:
VIOLATION [conformance-l0]: evidence is stale — code changed after verify
(verified_commit 292d740); Changed: src/app/api/media-library/route.test.ts
Local runs missed it because they passed --slug, which scopes the gate to one
dossier. CI scopes to the whole PR. The filter hid a real violation for four runs.
The change is necessary, not incidental: this feature adds readEnvStore to the
env store module and resolveConfig now reads through it, so that test's mock had
to gain the new export. It trips conformance-l0 because E12 declares
`src/app/api/**` in its paths while measuring node_cached across three layers —
a glob far wider than what it asserts. E12 itself is green, 11/11.
Round 2: 15/15 evals, every suite command exit 0 (the seventh is byte-identical
to E3's cmd and was deduped, so its evidence lives under E3).
The signature is NOT carried forward. `Manh 2026-08-07` attested to evidence at
292d740; moving it onto evidence at fe711ec would be forging it. human_signoff is
empty and contract status is back to `verified` until a human signs.
Machine output corrected in two places, both stated:
- runLogWriteFailed came back true for the fourth time today; report and 23
run-log lines written by hand.
- The synthesizer attributed this round to "sau khi fork plugin OpenAI —
dang-ky-fork-openai", which has nothing to do with it. Replaced with the real
cause. Known limits and Ngoài hợp đồng were absent entirely and were added; the
ten findings there belong to chong-mat-khoa-byo-giao-dien and were already
decided at its Gate 2, so they carry a pointer rather than a second triage.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Round 2 at fe711ec, after chong-mat-khoa-byo-giao-dien touched E12's declared paths. 15/15 evals, no judgment items. Nothing carried over from the 07/08 signature — that one attested to a different tree. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…dger The roadmap guard only turns red once a dossier flips to signed-off, so signing it is what created the missing row: 31 signed items, 30 ledger lines. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Step 6 of the signoff ritual, which I skipped twice: this repo opts in via PRODUCT-MAP.md in risk_tiers.t1_skip_globs, so the map is machine-generated from records the gate just changed and belongs in the signature commit. It was short one delivered entry, 30 against 31 dossiers. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Vấn đề
Kho khoá BYO không phân biệt "chưa lưu khoá nào" với "không đọc được kho khoá" — hai trạng thái phát ra cùng một tín hiệu, nên ba bề mặt nhập khoá hiển thị form rỗng trên một kho đang hỏng, và lần Lưu kế tiếp xoá sạch mọi khoá đang có.
Hồ sơ cha
chong-mat-khoa-byođã đóng nửa phía kho (đã ký 31/08). PR này là nửa giao diện: màn Cài đặt và hai bề mặt trên canvas nói rõ kho hỏng và từ chối ghi đè.Thay đổi
Một bộ đọc duy nhất —
src/lib/settings/env-client.tslà nơi DUY NHẤT ngoài test chạm/api/settings/env. Trước đó ba bề mặt tự viết ba lần,mainđếm được 6 lời gọi ở 3 tệp — chính cơ chế đẻ ra khuyết tật này. Guard cấu trúccheck-one-env-reader.shcanh con số đó, vàcheck-one-env-reader-teeth.shchứng minh guard biết đỏ.Năm hình dạng lỗi, không phải một.
503 · 500 · 502-html · shape · network— mọi lượt đọc không cho ra map dùng được đều là kho-hỏng. Một bản váif (status === 503)sẽ xanh ở ca đầu rồi gửi PUT ở bốn ca còn lại; ma trận toàn phần 2 bề mặt × 5 hình dạng chặn đúng chỗ đó.Ba bề mặt, một component chia sẻ —
store-unreadable-notice.tsx, đánh dấudata-testidđể test khẳng định bề mặt thật render CHÍNH nó chứ không phải bản chép tay. Đó là dây nối khiến sàn a11y đo trên bản mẫu nói được điều gì đó về màn Cài đặt.Copy ở cả 5 locale, kèm guard parity đóng băng nợ
jathành một TẬP 76 khoá (không phải một con số) và một ô đo ghim quan hệ giữa chuỗi hiển thị với tệp thông điệp.Hai sửa hạ tầng repo-wide, cả hai đã khai ở §Known limits của hợp đồng:
tsconfig.jsonthêm"build"vàoexclude— tsc thôi duyệt các dist dir tạm mà ba bọc a11y ghi vào (đo: 10 tệp → 0, và Next vẫn im vìincludegiữ nguyên).feature_loop.suite_keysgộp lạiexecutors.test.build_typecheck—next buildghi.next/typestrong lúc tsc đọc nó; khoá gộp vốn tuần tự cố ý cho đúng cuộc đua này.Kiểm chứng
S4 round 3: PASS — 13/13 ô đo, cả 7 lệnh suite exit 0 (
evidence-report.md,verified_commit: 4d8dd32).Ba vòng: r1 và r2 đều REJECT vì
pnpm typecheck— không ô đo nào đỏ, mà là một cuộc đua giữa tsc và một tiến trình song song ghi thư mục types. r1 ởbuild/**, r2 ở.next/types/**: cùng lớp lỗi, khác thư mục. r3 đóng bằng cách tách hai làn thay vì vá theo tên thư mục.Cổng:
pre-merge-check.sh --base origin/main→ clean,rules ran=4 declared-off=0,OK: PASS, signed off by Phan Le Manh 2026-09-02.Đã ký kèm giới hạn — đọc trước khi merge
Hội đồng review tìm 13 finding. Cổng 2 đã định đoạt từng mục (bảng Ngoài, chi tiết ở
review-findings.md):khong-noi-sai-ve-kho-khoa, chưa mở. Nặng nhất: lượt ghi phá huỷ được cấp phép bởi một lỗi đọc phía client mà server không kiểm lại — một cú 401/502 thoáng qua lúc mở màn Cài đặt có thể kết thúc bằng PUT rỗng xoá sạch một kho đang lành. Đó là mất khoá, vào bằng cửa khác. Hợp đồng này khoanh vùng theo bề mặt, khuyết tật sống ở bất biến phía server — nên nó cần AC riêng chứ không phải một bản vá nữa ở đây.Hai giới hạn về bộ đo, tách riêng vì khác lớp: mục 8 — bộ tổng hợp của kit trả
runLogWriteFailed: truecả ba vòng và để mục «Ngoài hợp đồng» rỗng, mà cổng đọc thân rỗng là lời khai «không có gì», nên vòng này suýt đi tiếp KHÔNG mời ký (cổng không fail-open; nó phân biệt vắng≠rỗng có chủ đích). Mục 9 —t1-escapethì fail-open thật.Test plan
pnpm build && pnpm typecheck·pnpm lint:check·pnpm test·pnpm verify:plugins·pnpm gen:abi— exit 0cd sdk && pytestexit 0pre-merge-check.sh --base origin/maincleanenv-store.json, mở Cài đặt và hai bề mặt canvas, xác nhận không lượt PUT nào rời đitf:unauthorized) đúng như đã khai🤖 Generated with Claude Code