From ebce562b5921fd9508f4437d548e37061cbc55c1 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 12:59:29 +0000 Subject: [PATCH 01/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20cli.?= =?UTF-8?q?json:=20three=20refs=20scored=20per=20RUNNER=20rule=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit migrate-plan-apply-json and verify-verdict-exit-mapping drop automated (their unit pins score no CLI-exit or payload clause) with a knownGaps line each; dev-automigrate-policy's ref names the clauses each driver pin scores, adds sql-driver-unique-tenancy.test.ts, and records the production force-off case as a defective pin that scores nothing. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- docs/qa/platform-checklist/areas/cli.json | 31 +++++++++++++++-------- 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json index eb0a92b38b..b933595c2f 100644 --- a/docs/qa/platform-checklist/areas/cli.json +++ b/docs/qa/platform-checklist/areas/cli.json @@ -223,7 +223,7 @@ "title": "os migrate: bare command is a read-only plan, apply is safe-by-default, re-runs are idempotent, and --json exits 0 on success (#4873)", "since": "v15", "status": "active", - "revision": 6, + "revision": 7, "priority": "P1", "surface": "cli", "personas": ["operator (local shell)"], @@ -231,6 +231,9 @@ "app": "showcase", "requires": [ "a scratch app + persistent sqlite DB whose base schema was synced by one prior `os dev` boot (plan itself defers DDL since #3917, so the tables must pre-exist), e.g. -d file:/tmp//migrate.db" + ], + "knownGaps": [ + "no pin scores a clause of this item at its layer (RUNNER rule 6, a pin scores only the layer it drives), so the item carries no automated ref. Every clause's oracle is the CLI's own output or exit code. packages/cli/src/utils/format.exit-code.test.ts calls emitJson / emitText in-process (unit): it shows that the success path leaves process.exitCode alone and that a duration can no longer reach the exit-code slot (#4873). That is unit-layer evidence for acceptance[2], never its pass: acceptance[2] is scored on the `echo $?` collected after each subcommand's --json success. automated may return when a pin spawns `os migrate … --json` and asserts the shell's exit code" ] }, "steps": [ @@ -313,7 +316,6 @@ "audit-mail-bodies" ], "traps": ["stale-dist"], - "automated": { "kind": "unit", "ref": "packages/cli/src/utils/format.exit-code.test.ts — covers acceptance[2] ONLY (#4873: emitJson / emitText leave the exit code alone on success and a duration can no longer reach the exit-code slot); acceptance[0], [1] and [3]–[6] need the run" }, "source": [ "packages/cli/src/commands/migrate/index.ts + plan.ts (#2186 bare-command-is-plan; #3917 enforced never-mutates; occupancy warning) + apply.ts (--allow-destructive / --force / --yes / in_sync)", "packages/cli/src/utils/format.ts#CliExitCode (CliExitCode narrows the emitJson exit slot — the #4873 fix, commit 83df2fd)", @@ -346,6 +348,12 @@ "date": "2026-10-20", "change": "variants: audit-mail-bodies added, the 19th registered subcommand (#24693: the one-off mask of the mail bodies that sys_audit_log / sys_activity copies of sys_email and sys_identity_email rows still hold in clear). The source note's count moves from 18 to 19 with it. No clause changed", "ref": "#24693" + }, + { + "revision": 7, + "date": "2026-10-20", + "change": "automated removed, fixtures.knownGaps[0] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4, 'Refs that claim more than their pins assert'), round 15 of wave anchor #24579. The ref said format.exit-code.test.ts covers acceptance[2]. That pin calls emitJson / emitText in-process, while acceptance[2] asks for `echo $?` after each subcommand's --json success, which only a spawned CLI answers. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause, so automated is dropped (theme-B rule) and the knownGap says what the pin does assert. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -354,7 +362,7 @@ "title": "objectstack verify: verdict vocabulary is closed, exit is nonzero exactly on failure verdicts, and the inconclusive split (needs-fixture/skipped) never fails the run", "since": "v16", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "cli", "personas": ["operator (local shell)"], @@ -362,7 +370,8 @@ "app": "showcase", "requires": ["examples/app-showcase as the verified app (its config is the stock target)"], "knownGaps": [ - "no stock fixture ships a GUARANTEED-failing app, so the exit-1 side of the hardFailures mapping must be staged (point --app at a scratch config engineered to produce a fidelity gap or 5xx create) — record how it was staged in the evidence rather than skipping the side silently" + "no stock fixture ships a GUARANTEED-failing app, so the exit-1 side of the hardFailures mapping must be staged (point --app at a scratch config engineered to produce a fidelity gap or 5xx create) — record how it was staged in the evidence rather than skipping the side silently", + "no pin scores a clause of this item at its layer (RUNNER rule 6, a pin scores only the layer it drives), so the item carries no automated ref. packages/cli/src/commands/verify-tenancy-posture.test.ts asserts resolveVerifyMultiTenant, the exported decision, under the real tenancy-posture resolver; its own docblock says it is 'not an end-to-end `os verify` invocation'. That is unit-layer evidence for acceptance[4], never its pass: acceptance[4] scores the posture run's payload (multiTenant) or its loud error. automated may return when a pin runs os verify under a walled OS_TENANCY_POSTURE and reads that payload" ] }, "steps": [ @@ -419,7 +428,6 @@ "skipped (inconclusive — never fails)" ], "traps": ["stale-dist", "wrong-persona"], - "automated": { "kind": "unit", "ref": "packages/cli/src/commands/verify-tenancy-posture.test.ts — covers acceptance[4] ONLY (#5262: a walled OS_TENANCY_POSTURE selects the multi-tenant suite, and single-org deployments keep the single-org suite); acceptance[0]–[3] (the verdict vocabulary, the hardFailures mapping, the needs-fixture split, the --rls hole) need the run" }, "source": [ "packages/cli/src/commands/verify.ts#hardFailures (hardFailures sum, exit contract, resolveVerifyMultiTenant / #5262 / ADR-0105 D1, separate RLS stack rationale)", "packages/verify/src/verify.ts#ObjectVerifyResult (the six-member status union; 400-VALIDATION_FAILED→needs-fixture vs 5xx→create-failed classification — re-pointed #18104: VALIDATION_FAILED survives in this file only inside a regex literal; the union this item means is declared on ObjectVerifyResult and the classification that reads it is isValidation)", @@ -427,7 +435,8 @@ ], "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: verdict-to-exit-code mapping read from verify.ts + verify.ts (verdict union), with the inconclusive split and the #5262 posture-honesty negative as load-bearing clauses; the unstageable always-failing fixture recorded as a knownGap", "ref": "claude/platform-test-checklist-ocwugl" }, - { "revision": 2, "date": "2026-10-19", "change": "automated.ref says which clause its pin covers — round 2's Tier 1 record (#24598, via wave anchor #24579 §3, comment 6272032736) names this ref among those that over-claim clause coverage: its seven tests assert the #5262 posture selection (acceptance[4]) and nothing of acceptance[0]–[3]. Re-read at base 9a7838edf5. No clause changed", "ref": "#24649" } + { "revision": 2, "date": "2026-10-19", "change": "automated.ref says which clause its pin covers — round 2's Tier 1 record (#24598, via wave anchor #24579 §3, comment 6272032736) names this ref among those that over-claim clause coverage: its seven tests assert the #5262 posture selection (acceptance[4]) and nothing of acceptance[0]–[3]. Re-read at base 9a7838edf5. No clause changed", "ref": "#24649" }, + { "revision": 3, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[1] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4, 'Refs that claim more than their pins assert'), round 15 of wave anchor #24579. The ref said verify-tenancy-posture.test.ts covers acceptance[4]. Its seven tests call resolveVerifyMultiTenant only, while acceptance[4] scores the os verify run's payload or error. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause, so automated is dropped (theme-B rule) and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", "ref": "#PENDING-24579R15" } ] }, { @@ -1717,7 +1726,7 @@ "title": "autoMigrate off|safe: a dev boot self-heals ONLY the safe-categorized drift and reports each applied op, `off` (the driver default) and NODE_ENV=production never touch the schema, and a duplicates-blocked tighten is never auto-applied", "since": "v15", "status": "active", - "revision": 4, + "revision": 5, "priority": "P1", "surface": "cli", "personas": ["operator (local shell)"], @@ -1730,7 +1739,8 @@ ], "knownGaps": [ "the duplicates-BLOCKED leg (ADR-0120 D4 tightenNullSafeOnly recreate: duplicate rows found by the pre-flight probe → op blocked with a row report, old index left in place) needs planted duplicate rows under a legacy platform-wide unique index — stageable only by writing rows directly into the scratch sqlite file before boot. The leg is unit-pinned (sql-driver-unique-tenancy.test.ts 'auto-tightens at boot … when the probe is clean' + its duplicates sibling); a run may cite the pin per RUNNER rule 6 instead of hand-staging, and must record WHICH it did", - "`autoMigrate` is honoured by the native sqlite / postgres / mysql drivers only — the sqlite-wasm driver is constructed without it (the guidance entry on SqliteWasmConfigSchema says writing it there changes nothing), and turso's config declares no such key (standalone-stack.ts). Do not stage the wasm/turso arms expecting behavior" + "`autoMigrate` is honoured by the native sqlite / postgres / mysql drivers only — the sqlite-wasm driver is constructed without it (the guidance entry on SqliteWasmConfigSchema says writing it there changes nothing), and turso's config declares no such key (standalone-stack.ts). Do not stage the wasm/turso arms expecting behavior", + "DEFECTIVE PIN, scores no clause (QA run #24840; routed to the wave anchor #24579 as a test fix, not repaired by a ledger edit): sql-driver-schema-drift.test.ts 'is force-disabled under NODE_ENV=production (warns, does not alter)' stages relax_not_null, which ADR-0113 made needs_confirm. A safe reconcile never applies needs_confirm, so the column stays NOT NULL with or without the production guard: its sibling case 'does NOT auto-relax a stray NOT NULL — needs_confirm survives dev auto-reconcile' asserts that same outcome with no NODE_ENV set. The case also asserts no warning. Its green says nothing about the production arm of reconcileAndWarnDrift (packages/drivers/driver-sql/src/sql-driver.ts). Score acceptance[2] only from steps[3]'s live boot, which re-plants the legacy index: replace_unique_index is the safe-category drift that arm must hold back" ] }, "variants": [ @@ -1796,7 +1806,7 @@ "an authored `autoMigrate` on a sqlite-wasm datasource appearing to DO something is a FAIL of the declared boundary (the wasm driver is constructed without it — the guidance entry says writing it changes nothing); conversely, do not file the wasm no-op as a missing capability" ], "traps": ["stale-dist", "destructive-in-place"], - "automated": { "kind": "unit", "ref": "packages/drivers/driver-sql/src/sql-driver-schema-drift.test.ts" }, + "automated": { "kind": "unit", "ref": "packages/drivers/driver-sql/src/sql-driver-unique-tenancy.test.ts — acceptance[4] and negative[2], at the driver: its 'bare-composite tightening + duplicate pre-flight (ADR-0120 D4)' describe, where 'auto-tightens at boot under autoMigrate: safe when the probe is clean' converges with zero drift, and 'BLOCKS the tightening on duplicate NULL-organization rows — old index kept, rows reported' keeps the old index through initObjects, reports the op BLOCKED naming the duplicate group, and refuses even an allowDestructive apply until the duplicate is gone; + packages/drivers/driver-sql/src/sql-driver-schema-drift.test.ts — negative[0] ONLY ('does NOT auto-apply destructive drift (orphan column kept; warned instead)'). Its 'is force-disabled under NODE_ENV=production (warns, does not alter)' case scores NOTHING (knownGaps[2]). acceptance[0]–[3] and [5], and negative[1] and [3], are boot-log and schema-dump clauses: they need the scratch boots" }, "source": [ "packages/spec/src/data/driver/common.zod.ts#SqlAutoMigrateSchema (SqlAutoMigrateSchema z.enum(['off','safe']) — 'Dev-only, loosen-only schema self-heal (#2186) … force-disabled under NODE_ENV=production') + sqlite.zod.ts (the authored key; the wasm not-honoured guidance)", "packages/drivers/driver-sql/src/sql-driver.ts#SqlDriverConfig (SqlDriverConfig.autoMigrate docblock: non-destructive alters only), (default 'off'), (reconcileAndWarnDrift — the managed+safe gate, the production ignore warning verbatim, safe-only applyMigrationEntries allowDestructive:false, per-op auto-reconciled info line, post-reconcile re-detect, warn-once loop)", @@ -1809,7 +1819,8 @@ { "revision": 1, "date": "2026-08-30", "change": "new item (sweep 2026-08-30, angle 2): the autoMigrate off|safe policy had no coverage although it is the one path that mutates a dev database at boot. Grounded verify-first, correcting the register's sketch on two points: (1) 'safe' is the LOOSEN-only subset (relax NOT NULL, widen varchar, index ops, clean-probe tighten) — not 'additive' diffs; (2) the register's sql-driver.ts/4652-4654 cites the config/field declarations, but the enforcement lives in reconcileAndWarnDrift with the managed-mode gate, the production force-off warning, and allowDestructive:false — all now pinned as clauses. The dev-injection arm (storage-driver.ts / serve.ts, 'safe' only when isDev) was found while grounding and added as its own clause so a keyless dev boot's self-heal is not misread as a driver default. enumSource pins SqlAutoMigrateSchema expect 2", "ref": "#sweep-2026-08-30" }, { "revision": 2, "date": "2026-10-17", "change": "fixture and steps re-pointed (checklist-accuracy finding, #23878 close-out): a config-declared datasource named `default` is refused by defineStack, `required: true` no longer makes a NOT NULL column (storage.notNull, ADR-0113), and relax_not_null is needs_confirm, so the step-0 drift the safe leg relied on was never auto-appliable. The fixture now names a datasource routed by datasourceMapping default:true; step 0 stages one drift per category (storage.notNull relax = needs_confirm, a planted legacy platform-wide unique index = safe, a removed field = destructive); the steps read a schema dump instead of `os migrate plan`, which composes no driver for a config-declared primary and says its plan is PARTIAL; the safe variant lists the real safe ops; clauses 0, 1 and 3's verify follow. No clause's ruling changed. Re-run on main c91af8ed6 on a minimal scratch stack: datasource named default → build exit 2 with the reserved-name refusal; qa_main + mapping built. off and omitted boots each warned all three drifts and left the dump byte-identical; `os migrate plan` (also with --database-url) reported 'Examined 0 managed table(s)' and a PARTIAL plan. safe boot: '[schema-drift] auto-reconciled replace_unique_index on qa_thing.code', the legacy qa_thing_code_unique gone from the dump, the relax and orphan warnings still printed; the second safe boot printed no reconcile line. NODE_ENV=production with safe: the named ignore warning verbatim, all three warnings, dump unchanged. Keyless os dev over a planted legacy index: auto-reconciled replace_unique_index; non-dev os start over a re-planted one: the warning only, the index kept. Duplicates leg on the pin: driver-sql sql-driver-unique-tenancy.test.ts (incl. 'auto-tightens at boot under autoMigrate: safe when the probe is clean' and 'BLOCKS the tightening on duplicate NULL-organization rows') + sql-driver-schema-drift.test.ts, 38 passed", "ref": "#24036" }, { "revision": 3, "date": "2026-10-19", "change": "steps[4], acceptance[5] (clause, verify) and source[3] re-pointed to the injection site that exists — checklist-accuracy finding of the 18.0 QA sweep (QA run #24551, cli.dev-automigrate-policy acceptance[5] / steps[4]; wave anchor #24579 §3): the cited storage-driver.ts `isDev ? { autoMigrate: 'safe' } : {}` and serve.ts telemetry branch moved. Re-derived at base 9a7838edf5: since #21733 the decision lives in packages/runtime/src/dev-auto-migrate.ts (devAutoMigrateConfig), read by createStandaloneStack's default datasource, resolveStorageDefinition's fallback and the telemetry sibling. The run found the behaviour matching the clause; the clause now names the one decision and the three readers", "ref": "#24649" }, - { "revision": 4, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). source[4] cited lines 322-342 of sql-driver-index-drift.test.ts in the comma-range spelling (authored in 45397edf53 as the list 112-113 and 322-342). Those lines are unchanged at this base: the describe block \"dev auto-reconcile (autoMigrate: 'safe')\", which source[4] now names. A test file declares no symbol worth anchoring, so the block is named in prose. The count of passed tests that history[1] records after a file name is not a citation, and that row is a dated record, so it is left. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" } + { "revision": 4, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). source[4] cited lines 322-342 of sql-driver-index-drift.test.ts in the comma-range spelling (authored in 45397edf53 as the list 112-113 and 322-342). Those lines are unchanged at this base: the describe block \"dev auto-reconcile (autoMigrate: 'safe')\", which source[4] now names. A test file declares no symbol worth anchoring, so the block is named in prose. The count of passed tests that history[1] records after a file name is not a citation, and that row is a dated record, so it is left. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" }, + { "revision": 5, "date": "2026-10-20", "change": "automated.ref narrowed per clause, fixtures.knownGaps[2] added — checklist-accuracy findings of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4), round 15 of wave anchor #24579. (1) The bare ref named sql-driver-schema-drift.test.ts alone. By RUNNER rule 6 (a pin scores only the layer it drives) that pin scores negative[0] only. (2) Its production force-off case stages relax_not_null, needs_confirm since ADR-0113, so it passes with or without the guard and asserts no warning. It scores no clause, and the knownGap records it for the anchor to route as a test fix. (3) sql-driver-unique-tenancy.test.ts, which acceptance[4].verify and knownGaps[0] already named, was missing from the ref. Its ADR-0120 D4 cases assert acceptance[4] and negative[2] at the driver, the layer that test-oracle clause names. Read at base b1676441ba. No clause changed", "ref": "#PENDING-24579R15" } ] }, { From 72dffb6493f8aab1960fb02a44f3769d3ab75eed Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:01:05 +0000 Subject: [PATCH 02/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20reco?= =?UTF-8?q?rds-forms.json:=20three=20items=20re-scored=20per=20RUNNER=20ru?= =?UTF-8?q?le=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit field-unique-enforcement's ref names what its driver pins score and stops claiming REST; acceptance[1]-[3] and steps[4] cite those pins as driver-level evidence. encrypted-field-behavior's engine pin scores no api clause, and acceptance[3] is blocked(fixture) on stock. cascading-multilevel-and-clear scores acceptance[1] from the objectui spec's live-API case, not the validator unit pin. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../areas/records-forms.json | 44 +++++++++++++------ 1 file changed, 31 insertions(+), 13 deletions(-) diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index b3d8872bff..16bbe19f59 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -3850,7 +3850,7 @@ "title": "Cascade clear-semantics and the server boundary: stale-child clear on parent change, the WRITTEN-value server gate, and where cascade scope is client-only", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P1", "surface": "mixed", "personas": [ @@ -3887,7 +3887,7 @@ { "clause": "SELECT server WRITTEN-value gate: a directly-POSTed child inconsistent with its parent (both in the payload) is rejected with code 'invalid_option' naming province; the consistent one is accepted — the objectql rule-validator, not the picker, is the boundary", "oracle": "api", - "verify": "{country:'cn',province:'ca'} → invalid_option (message key option_unavailable) on province; {country:'cn',province:'zj'} → 200. rule-validator.ts evaluateOptionVisibility re-evaluates the picked option's visibleWhen over the merged record. Pinned by packages/objectql/src/validation/rule-validator.option-visibility.test.ts", + "verify": "{country:'cn',province:'ca'} → invalid_option (message key option_unavailable) on province; {country:'cn',province:'zj'} → 200. rule-validator.ts evaluateOptionVisibility re-evaluates the picked option's visibleWhen over the merged record. Pinned at the HTTP door by objectui e2e/live/cascading-options.spec.ts 'the server rejects an out-of-set option value, and accepts an in-set one' ({country:'us', province:'zj'} → 400 VALIDATION_FAILED with invalid_option on province; {country:'cn', province:'zj'} → 201). packages/objectql/src/validation/rule-validator.option-visibility.test.ts calls evaluateValidationRules directly: validator-layer evidence only, never this clause's pass (RUNNER rule 6, a pin scores only the layer it drives)", "evidence": "both responses" }, { @@ -3928,7 +3928,7 @@ ], "automated": { "kind": "e2e", - "ref": "objectui: e2e/live/cascading-options.spec.ts (acceptance[0]'s client clear-on-change, through its re-filter case: country cn offers gd and zj, us offers ca and tx, and the chosen Zhejiang leaves the province trigger. It never submits, so 'the stale province is not carried into the submit' is driven by hand); packages/objectql/src/validation/rule-validator.option-visibility.test.ts (server WRITTEN-value gate: acceptance[1], and acceptance[2]'s unwritten child through its 'does not check a field the patch never wrote' case)" + "ref": "objectui: e2e/live/cascading-options.spec.ts (acceptance[0]'s client clear-on-change, through its re-filter case: country cn offers gd and zj, us offers ca and tx, and the chosen Zhejiang leaves the province trigger. It never submits, so 'the stale province is not carried into the submit' is driven by hand. Its second case, 'the server rejects an out-of-set option value, and accepts an in-set one', is acceptance[1] at the HTTP door: it POSTs {country:'us', province:'zj'} to the live backend and asserts 400 VALIDATION_FAILED with an invalid_option field on province, then {country:'cn', province:'zj'} and asserts 201); packages/objectql/src/validation/rule-validator.option-visibility.test.ts (scores NO clause of this item: it calls evaluateValidationRules directly, so its written-value rejection and its 'does not check a field the patch never wrote' case are validator-layer evidence for acceptance[1] and acceptance[2], which score the POST and PATCH answers — RUNNER rule 6)" }, "source": [ "packages/objectql/src/validation/rule-validator.ts#evaluateOptionVisibility (evaluateOptionVisibility — WRITTEN-value only via `!(name in data) continue`; a predicate that cannot be evaluated refuses the write as rule_violation / unevaluable (ADR-0137 D2), except a system write with no acting user whose predicate reads one; invalid_option/option_unavailable)", @@ -3968,6 +3968,12 @@ "date": "2026-10-20", "change": "automated.stale removed and automated.ref scoped per pin — round 14 of wave anchor #24579 (18.0 sweep), on the objectui pointer 6284006658. objectui#13132 (objectui 0b36ffa) repaired the cascading-options.spec.ts re-filter case the stale record named, and the console pin moved to 616df6598b42 (PR #24829), which contains 0b36ffa. Its blob is 648d5c917d19 at the old pin 12dc7b47c340 and 5f65228948b7 at both 0b36ffa and 616df6598b42; the case now waits for the flat ModalForm's modal-form-footer instead of md-form-submit. objectui measured its whole live suite at 0b36ffa as 61 passed (11.0m) on the shipped showcase; no live run exists at 616df6598b42, so the verdict there is NOT MEASURED. Re-read at the new pin, the case asserts acceptance[0]'s re-filter and cleared trigger but never submits, so the ref says the submit half is driven by hand. The objectql pin's scope (acceptance[1] and acceptance[2]), which only the stale record's runnerRule carried, moves into the ref unchanged. No clause changed", "ref": "#24857" + }, + { + "revision": 6, + "date": "2026-10-20", + "change": "automated.ref and acceptance[1].verify re-scored per RUNNER rule 6 — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at cascading-multilevel acceptance[1], round 15 of wave anchor #24579. acceptance[1] scores the direct POST's answer. Its verify and the ref (round 14 moved the objectql pin's scope into it) scored it, and acceptance[2], from rule-validator.option-visibility.test.ts, which calls evaluateValidationRules directly. By the rule (a pin scores only the layer it drives) that pin scores neither clause. Re-read at the console pin 616df6598b42 (blob 5f65228948b7 of cascading-options.spec.ts, unchanged since round 14), the ref's own objectui spec carries a second case, 'the server rejects an out-of-set option value, and accepts an in-set one', which POSTs to the live backend and asserts the out-of-set 400 VALIDATION_FAILED with invalid_option on province and the in-set 201. That is acceptance[1] at the HTTP door, so the ref and the verify cite it; no live run exists at that pin, so its verdict there is NOT MEASURED. acceptance[2] needs the run. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -3976,7 +3982,7 @@ "title": "Secret/encrypted field: ciphertext at rest, masked on every read, no-op mask re-submit, and fail-CLOSED without an ICryptoProvider (ADR-0100)", "since": "v16", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "mixed", "personas": [ @@ -3990,7 +3996,7 @@ ], "knownGaps": [ "f_secret is NOT seeded (the Specimen rows omit it) — drive a live UI/API write to exercise the encrypt path.", - "the fail-CLOSED path is PIN-ONLY of necessity (QA run #21784, parked on #22017 row 22). Every stock boot wires LocalCryptoProvider. The 'no OS_SECRET_KEY under the production guard' boot this note once suggested never comes up: LocalCryptoProvider refuses to start in production without a stable key, and serve.ts rethrows that refusal. A boot without @objectstack/service-settings is not a stock composition. Score acceptance[3] from packages/objectql/src/secret-fields.test.ts ('fail-closed: writing a secret field with no CryptoProvider throws': the write throws and neither store holds a row). The field-type-matrix's 'seed deliberately omits it' note is about SEEDING, not serve-time wiring.", + "the fail-CLOSED path has NO live leg on stock, of necessity (QA run #21784, parked on #22017 row 22). Every stock boot wires LocalCryptoProvider. The 'no OS_SECRET_KEY under the production guard' boot this note once suggested never comes up: LocalCryptoProvider refuses to start in production without a stable key, and serve.ts rethrows that refusal. A boot without @objectstack/service-settings is not a stock composition. So acceptance[3] is recorded blocked(fixture) on stock, citing this gap. packages/objectql/src/secret-fields.test.ts ('fail-closed: writing a secret field with no CryptoProvider throws': the write throws and neither store holds a row) is engine-layer evidence beside that verdict, never its pass (RUNNER rule 6, a pin scores only the layer it drives). The field-type-matrix's 'seed deliberately omits it' note is about SEEDING, not serve-time wiring.", "inspecting the raw business-row column / sys_secret ciphertext needs DB access (file:/tmp//data.db) — where the API is the only surface, assert the mask on read + the absence of plaintext in any API response instead." ] }, @@ -4025,7 +4031,7 @@ { "clause": "fail-CLOSED without a provider: with no ICryptoProvider registered, a non-empty f_secret write is REFUSED ('Refusing to store cleartext … fail-closed') and NOTHING is persisted — cleartext never reaches the business row; a `password` write needs no provider (plaintext at rest, masked on read)", "oracle": "api", - "verify": "the unwired-boot POST throws the fail-closed error and the row is absent/unset; engine.ts encryptSecretFields (`if (!this.cryptoProvider) throw`). Pinned by packages/objectql/src/secret-fields.test.ts. No stock boot runs without a provider (fixtures.knownGaps), so this is a unit-pin verdict", + "verify": "the unwired-boot POST throws the fail-closed error and the row is absent/unset; engine.ts encryptSecretFields (`if (!this.cryptoProvider) throw`). No stock boot runs without a provider (fixtures.knownGaps[1]), so on stock this clause is recorded blocked(fixture); packages/objectql/src/secret-fields.test.ts 'fail-closed: writing a secret field with no CryptoProvider throws' is engine-layer evidence beside that verdict, never its pass (RUNNER rule 6, a pin scores only the layer it drives)", "evidence": "the refusal + a GET/list confirming no persisted cleartext" }, { @@ -4048,7 +4054,7 @@ ], "automated": { "kind": "api", - "ref": "packages/objectql/src/secret-fields.test.ts (encrypt/mask/fail-closed); packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts masked vectors for f_secret & f_password)" + "ref": "packages/qa/dogfood/test/field-zoo-roundtrip.dogfood.test.ts (+ field-zoo.matrix.ts masked vectors for f_secret & f_password) — acceptance[0] (its API oracle, the masked GET: knownGaps[2] makes the raw-column read the DB-inspectable extra), acceptance[1] and negative[0], at the HTTP door: the showcase is booted in-process, POST /data/showcase_field_zoo writes both fields, and GET /data/showcase_field_zoo/ID reads them back as SECRET_MASK, never the written value. packages/objectql/src/secret-fields.test.ts drives a real ObjectQL engine over a stub driver, with no HTTP, so it scores NO api clause of this item: it is engine-layer evidence beside acceptance[0]'s DB-inspectable leg ('encrypts on write: row stores a ref, sys_secret holds ciphertext, no cleartext on the row'), and for acceptance[2] ('update: echoing the read mask back does NOT overwrite the stored secret'), acceptance[3] ('fail-closed: writing a secret field with no CryptoProvider throws'), acceptance[4] (the 'objectql aggregate() rejects credential fields (ADR-0100 / #3171)' describe) and negative[1]–[3]. acceptance[2] and [4] are driven over the API; acceptance[3] is blocked(fixture) on every stock composition (knownGaps[1])" }, "source": [ "packages/objectql/src/secret-fields.ts#SECRET_REF_PREFIX (SECRET_MASK, SECRET_REF_PREFIX, collectSecretFields/collectMaskedReadFields/collectCredentialFields)", @@ -4070,6 +4076,12 @@ "date": "2026-10-06", "change": "partial-coverage gap of QA run #21784 parked on #22017 (row 22). A4 was pin-only of necessity: an unwired-crypto boot refuses to start. Disposition: pin. The knownGap had offered a production boot without OS_SECRET_KEY as the unwired path; LocalCryptoProvider refuses to start there (serve.ts rethrows 'Refusing to start in production'), so the knownGap and A4's verify now name secret-fields.test.ts ('fail-closed: writing a secret field with no CryptoProvider throws') as the leg's only oracle", "ref": "#22017" + }, + { + "revision": 3, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause; acceptance[3].verify and fixtures.knownGaps[1] rewritten — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the acceptance[2] and [4] guards are pinned at the engine only), round 15 of wave anchor #24579. secret-fields.test.ts drives a real ObjectQL engine over a stub driver, with no HTTP. By RUNNER rule 6 (a pin scores only the layer it drives) it scores none of this item's api clauses, so the ref names it engine-layer evidence for acceptance[2]–[4] and negative[1]–[3], and names the dogfood pin's own clauses (acceptance[0]'s API oracle, acceptance[1], negative[0]). The same rule makes acceptance[3]'s 'unit-pin verdict' (the #22017 row 22 disposition) a rule-6 contradiction, located here beyond #24840's list: no stock composition runs without a provider, so the clause is now recorded blocked(fixture) with the pin beside it. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -4186,7 +4198,7 @@ "title": "Field-level `unique` on the write path: colliding writes refused 409 UNIQUE_VIOLATION on both driver families, NULL-distinct, organization-scoped by default, whole-batch refusal, autonumber resync converges", "since": "v17", "status": "active", - "revision": 4, + "revision": 5, "priority": "P1", "surface": "api", "personas": [ @@ -4210,7 +4222,7 @@ "POST /api/v1/data/qa_unique {name:'r1', sku:'A-1'} → 2xx; POST {name:'r2', sku:'A-1'} → capture the refusal; count rows", "POST two rows with sku absent → both land (NULL-distinct)", "PATCH r2 to sku:'A-1' → capture the refusal and re-read r2; PATCH r1 touching only `nick` → 2xx (no self-collision)", - "predicate update stamping ONE value onto BOTH rows (the updateMany path — client data.updateMany / the batch door where exposed; otherwise score clause 4 from its unit pin and say so) → capture the refusal and prove neither row changed", + "predicate update stamping ONE value onto BOTH rows (the updateMany path — client data.updateMany / the batch door where exposed; otherwise record clause 4 blocked(fixture) naming the missing door — its unit pin is driver-level evidence beside that verdict, never its pass (RUNNER rule 6, a pin scores only the layer it drives)) → capture the refusal and prove neither row changed", "run the pinned suites: memory-unique-constraint.test.ts (driver-memory), sql-driver-8577-tenant-scoped-declared-unique.test.ts + sql-driver-declared-index-organization-respelling.test.ts (SQL family)" ], "acceptance": [ @@ -4223,19 +4235,19 @@ { "clause": "NULLs are NULL-DISTINCT, exactly as under SQL UNIQUE: two rows whose unique field carries no value both land — an optional unique column does not refuse its second row", "oracle": "api", - "verify": "both no-sku POSTs return 2xx and both rows read back (module note memory-unique-constraint.ts; pin)", + "verify": "both no-sku POSTs return 2xx and both rows read back (module note memory-unique-constraint.ts). The memory-driver case 'NULL stays NULL-DISTINCT, exactly as under SQL UNIQUE' in memory-unique-constraint.test.ts is driver-level evidence beside that answer, never this clause's pass (RUNNER rule 6, a pin scores only the layer it drives)", "evidence": "the two creates + the list read" }, { "clause": "the update paths are constrained too: an update onto a taken value is refused and the row keeps its old value; a row never collides with ITSELF (an update leaving the unique field alone passes)", "oracle": "api", - "verify": "the PATCH refusal + the unchanged re-read; the self-update 2xx (pins: packages/drivers/driver-memory/src/memory-unique-constraint.test.ts, the cases 'update onto a taken value is refused and the row keeps its old value' and 'a row does not collide with ITSELF — an update that leaves the unique field alone passes')", + "verify": "the PATCH refusal + the unchanged re-read; the self-update 2xx (driver-level evidence beside those answers, never this clause's pass, RUNNER rule 6, a pin scores only the layer it drives: packages/drivers/driver-memory/src/memory-unique-constraint.test.ts, the cases 'update onto a taken value is refused and the row keeps its old value' and 'a row does not collide with ITSELF — an update that leaves the unique field alone passes')", "evidence": "both responses + the re-read" }, { "clause": "updateMany refuses the WHOLE batch BEFORE mutating anything — a predicate update stamping one value onto two rows leaves zero rows changed, never a half-applied batch", "oracle": "api", - "verify": "post-refusal re-reads of every matched row are byte-identical to before (packages/drivers/driver-memory/src/memory-driver.ts#updateMany 'Prepare and CHECK every row before mutating any of them'; pin). If no REST surface reaches updateMany on the boot under test, score from the pin and record the substitution", + "verify": "post-refusal re-reads of every matched row are byte-identical to before (packages/drivers/driver-memory/src/memory-driver.ts#updateMany 'Prepare and CHECK every row before mutating any of them'). The pin's 'updateMany refuses BEFORE mutating anything — no half-applied batch' is memory-driver evidence only, never this clause's pass (RUNNER rule 6, a pin scores only the layer it drives): if no REST door reaches a predicate update on the boot under test, record the clause blocked(fixture) naming the missing door, with the pin's output beside it", "evidence": "the refusal + full before/after row reads" }, { @@ -4262,7 +4274,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/drivers/driver-memory/src/memory-unique-constraint.test.ts (refusal shape, NULL-distinct, every write path, scope matrix, autonumber converge, boundary cases); packages/drivers/driver-sql/src/sql-driver-8577-tenant-scoped-declared-unique.test.ts + sql-driver-declared-index-organization-respelling.test.ts (SQL family, 409 UNIQUE_VIOLATION over real REST)" + "ref": "packages/drivers/driver-memory/src/memory-unique-constraint.test.ts — acceptance[4]'s memory-family legs (the \"[#13197] scoping matches driver-sql's ADR-0120 D1/D3 rule\" describe), acceptance[5] ('the refusal is recognisable to the ENGINE, so the autonumber resync converges'), negative[2] ('updateMany refuses BEFORE mutating anything — no half-applied batch') and negative[3]'s memory-family half, at the memory driver; + packages/drivers/driver-sql/src/sql-driver-8577-tenant-scoped-declared-unique.test.ts + packages/drivers/driver-sql/src/sql-driver-declared-index-organization-respelling.test.ts — acceptance[4]'s SQL-family legs and negative[3]'s SQL-family half, at SqlDriver over better-sqlite3. NO pin drives a REST door: the SQL pins map each driver refusal through a test-local createAsApi helper to the { status: 409, code: 'UNIQUE_VIOLATION' } envelope rest-server.ts derives, so acceptance[4]'s 'same 409 UNIQUE_VIOLATION over REST' wording rests on that mapping, and every pin is driver-level evidence for acceptance[0]–[3], never their pass: those clauses score the REST answer on the stock SQLite boot" }, "source": [ "packages/drivers/driver-memory/src/memory-unique-constraint.ts#scope (#13197/#13249, commit 56c093c — single judgment point; scope table read off driver-sql, arm for arm)", @@ -4296,6 +4308,12 @@ "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). acceptance[2].verify cited line 141 of memory-unique-constraint.test.ts and acceptance[3].verify lines 627-631 of memory-driver.ts in the comma-range spelling (authored in 45397edf53 as the lists 135 and 141, and 611 and 627-631). Lines 135 and 141 are unchanged at this base: the cases 'update onto a taken value is refused and the row keeps its old value' and 'a row does not collide with ITSELF — an update that leaves the unique field alone passes', which acceptance[2].verify now names. Lines 627-631 held findOne even at that commit; the comment the verify quotes, 'Prepare and CHECK every row before mutating any of them', sat in updateMany then and does now, so acceptance[3].verify anchors memory-driver.ts#updateMany. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" + }, + { + "revision": 5, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause; acceptance[1].verify, acceptance[2].verify, acceptance[3].verify and steps[4] rewritten — checklist-accuracy findings of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4), round 15 of wave anchor #24579. (1) The ref said the two SQL pins assert '409 UNIQUE_VIOLATION over real REST'. They drive SqlDriver over better-sqlite3 and map its refusal to that envelope through a test-local createAsApi helper, so no REST door is driven. (2) The sweep-wide rule-6 contradiction #24840 names at acceptance[1]–[3]: those clauses score the REST answer, and their verifies and steps[4] told the runner to score them from the memory-driver pin ('score from the pin'). By RUNNER rule 6 (a pin scores only the layer it drives) the pins are now cited as driver-level evidence only, and an updateMany door the boot does not expose is recorded blocked(fixture). The ref names the clauses each pin scores: acceptance[4] (test oracle) and [5], and negative[2] and [3]. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From 1ea4a658f3712d627d646f19673710f161071e52 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:02:15 +0000 Subject: [PATCH 03/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20inte?= =?UTF-8?q?gration-system.json:=20four=20refs=20scored=20per=20RUNNER=20ru?= =?UTF-8?q?le=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit connector-stdio-default-deny's ref narrows to negative[0]; webhook-lifecycle drops automated (its dogfood pin asserts half of acceptance[0]); the drift gate's ref names the test-oracle clauses its plugin pins score, and its "unit fallback" texts cite those pins as plugin-layer evidence beside a blocked(fixture) verdict; datasource-admin-lifecycle's second pin is respelled as a full path. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../areas/integration-system.json | 55 +++++++++++++------ 1 file changed, 38 insertions(+), 17 deletions(-) diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 6b44c60d9d..317360d827 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -221,7 +221,7 @@ "title": "Declarative stdio connector transports are denied by default; the host allowlists exact commands (declarativeStdio) — and the deny is a config fault (boot fatal), not a degrade", "since": "v15.1", "status": "active", - "revision": 2, + "revision": 3, "priority": "P1", "surface": "cli", "fixtures": { @@ -275,7 +275,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/connectors/connector-mcp/src/mcp-provider.test.ts" + "ref": "packages/connectors/connector-mcp/src/mcp-provider.test.ts — negative[0] ONLY, at the provider unit: in its 'mcp provider declarative stdio policy (#3055)' describe, 'DENIES a declarative stdio transport by default, as a plain (non-retryable) fault' shows that with no policy the factory rejects a stdio transport before the client factory is handed one (seen.transport stays undefined), so nothing is spawned. The same cases assert the default-deny text, the allowlist-mismatch text and the non-retryable class at the provider: evidence for acceptance[0]–[2] at that layer only, never their pass, since those clauses score the boot output. acceptance[3] (GET /automation/connectors) and negative[1] (no degraded husk on the boot) need the run" }, "source": [ "docs/plans/release-15.1-test-plan.md §B4 (#3059)", @@ -294,6 +294,12 @@ "date": "2026-08-07", "change": "expanded to deep-test contract: concrete steps, multi-clause acceptance, negatives, variants", "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 3, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The one pin drives createMcpProviderFactory over a fake client factory. By RUNNER rule 6 (a pin scores only the layer it drives) it scores negative[0] (on a deny, nothing reaches the spawner) and is provider-layer evidence for the boot-output clauses acceptance[0]–[2]. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -546,7 +552,7 @@ "title": "Outbound webhooks are read from the registry (spec object→object_name, no sys_webhook row) and switched through the activation ledger, fire per trigger variant through the sys_http_delivery outbox with HMAC + timeout honored, redeliver terminal rows tenant-scoped via POST /api/v1/webhooks/redeliver, and reject retired trigger kinds", "since": "v15", "status": "active", - "revision": 9, + "revision": 10, "priority": "P1", "surface": "mixed", "fixtures": { @@ -558,7 +564,8 @@ ], "knownGaps": [ "PREDICATE (multi:true) WRITES ARE UNREACHABLE OVER REST BY DESIGN (run #7690) — do not re-derive this, and do not file the 400 as a defect. #3897 made the batch routes parse their body against the spec contract, and Zod object schemas STRIP unknown keys: `options.multi` and `options.where` can no longer ride into the engine's delete/update options, and `POST /data/:object/deleteMany` now deletes per id (packages/rest/src/rest-server.ts). That is a security boundary, not a gap. The bulk_update/bulk_delete clause therefore needs a FLOW `update_record` / `delete_record` node authored with multi:true, fired through the api trigger — that is the supported predicate-write door", - "acceptance[2], the bulk pair, is BLOCKED(dependency) on the org-walled showcase (QA run #23166): a flow node's predicate multi-write publishes its bulk event with NO organizationId, the org-owned subscription refuses it (auto-enqueuer.ts organization match), and an org-less subscription cannot be authored to receive it. Whether flow-node multi-writes should carry the organization is a maintainer question raised on #23166, not ruled; score acceptance[2] blocked(dependency) until it is. The same run did not attempt negative[1] or negative[2] (time budget): a re-run owes them, and their text is unchanged" + "acceptance[2], the bulk pair, is BLOCKED(dependency) on the org-walled showcase (QA run #23166): a flow node's predicate multi-write publishes its bulk event with NO organizationId, the org-owned subscription refuses it (auto-enqueuer.ts organization match), and an org-less subscription cannot be authored to receive it. Whether flow-node multi-writes should carry the organization is a maintainer question raised on #23166, not ruled; score acceptance[2] blocked(dependency) until it is. The same run did not attempt negative[1] or negative[2] (time budget): a re-run owes them, and their text is unchanged", + "no pin scores a full clause of this item at its layer (RUNNER rule 6, a pin scores only the layer it drives), so the item carries no automated ref. packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts boots its own fixture stack (fixtures/webhook-materialization-fixture.ts, not the showcase). At the engine it asserts that no sys_webhook row is written for a declared webhook, and that the declared-webhook reader hands a matching record event to an AutoEnqueuer over a hand-driven realtime stub. That is acceptance[0]'s no-row half only: the activation door's 200 and 404 are never driven, and nothing of acceptance[1]–[6] is asserted. automated may return when a pin drives the activation door and a delivery on the showcase" ] }, "variants": [ @@ -633,10 +640,6 @@ "seed-data-thin", "stale-dist" ], - "automated": { - "kind": "e2e", - "ref": "packages/qa/dogfood/test/webhook-materialization.dogfood.test.ts" - }, "source": [ "packages/spec/src/automation/webhook.zod.ts#WebhookTriggerType (WebhookTriggerType enum + why undelete/api are absent; materialization contract; strict shape #4001)", "packages/spec/liveness/webhook.json (9 props live on the registry read; label/description dead since the boot seeder retired)", @@ -701,6 +704,12 @@ "date": "2026-10-20", "change": "fixtures.requires[0]'s walled sentence and scratch-webhook recipe corrected — QA run #24783 (finding F4), round 11 of wave anchor #24579. 'Under a walled posture it would be refused as belonging to no organization' predates ADR-0131 ruling A: a declared webhook is deployment-wide under every posture (auto-enqueuer.ts admitsOrganization answers true for CachedSubscription.deploymentWide), delivers one row per event stamped with that event's organization, and is switched on by the operator under a wall, as wall-declared-webhook-deployment-wide.dogfood.test.ts pins and QA run #24783 measured on both postures. The scratch sys_webhook recipe now names its required definition_json and the lower-case method values sys-webhook.object.ts declares. No clause changed", "ref": "#24801" + }, + { + "revision": 10, + "date": "2026-10-20", + "change": "automated removed, fixtures.knownGaps[2] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The one dogfood pin asserts acceptance[0]'s no-sys_webhook-row half on its own fixture stack and drives no activation door. By RUNNER rule 6 (a pin scores only the layer it drives) no full clause is pinned, so automated is dropped (theme-B rule) and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ], "enumSource": { @@ -1181,7 +1190,7 @@ "title": "The /api/v1/datasources admin lifecycle: all 11 routes behind ONE manage_platform_settings floor (drivers catalog included), runtime create with provenance+health, draft test probe, credential migration into the store, secret never echoes (hasSecret only), bad drafts 400 DATASOURCE_ADMIN_ERROR, unwired federation degrades 503 naming external-datasource", "since": "v16", "status": "active", - "revision": 4, + "revision": 5, "priority": "P1", "surface": "api", "personas": [ @@ -1282,7 +1291,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/services/service-datasource/src/__tests__/admin-routes.test.ts (+ __tests__/envelope.conformance.test.ts) — pins route behavior + envelope; the LIVE-mount half is not pinned, drive os dev for it" + "ref": "packages/services/service-datasource/src/__tests__/admin-routes.test.ts + packages/services/service-datasource/src/__tests__/envelope.conformance.test.ts — pins route behavior + envelope; the LIVE-mount half is not pinned, drive os dev for it" }, "source": [ "packages/services/service-datasource/src/admin-routes.ts#requireDatasourceAdmin (the eleven routes; the uniform floor docblock + requireDatasourceAdmin + DATASOURCE_ADMIN_CAPABILITY #9391/#9593; splitSecret keeps the secret out of the persisted draft; resolve()/badRequest() per-service attribution #4225/#4249; getDatasource credential-stripped + hasSecret; POST /test draft probe; POST /:name/migrate-credential)", @@ -1317,6 +1326,12 @@ "date": "2026-10-20", "change": "checklist-accuracy findings of QA run #24615 (18.0 sweep round 2, ba4afcc2; wave anchor #24579 §3, round 2 part 2). The served driver id is `mongodb`, not `mongo` (steps[2], acceptance[0].clause); the sqlite config key is `filename`, and `file` is an alias authoring refuses (steps[3], steps[4]); the health field is spelled `status`, beside `statusReason` (steps[5], acceptance[4] clause and verify). steps[3] and acceptance[2].clause said POST /test is registered before the :name routes; it is registered after every one of them (admin-routes.ts), and 'test' stays uncaptured because no POST route takes a bare :name. The code comment that says otherwise is reported, not edited. steps[8] carries the live bind-arm recipe #24615 proved and names the wire statuses. Not loosened: no clause's oracle or verdict changed", "ref": "#24717" + }, + { + "revision": 5, + "date": "2026-10-20", + "change": "automated.ref respelled — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a ref that misses its own pin), round 15 of wave anchor #24579. The second pin was spelled '(+ __tests__/envelope.conformance.test.ts)', a partial path in a parenthesis, so the run's ref parser missed it and ran it separately (24/24). It is now a full repo path after ' + ', as the other multi-pin refs spell it. The ref's scope is unchanged. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -1439,7 +1454,7 @@ "title": "Boot-time external-schema drift gate (ADR-0015 §5.2 Gate 2): onMismatch defaults to 'fail' and refuses boot naming the object and mismatched column; 'warn' proceeds logging the diff; 'ignore' is silent; checkOnBoot:false skips a datasource with a loud info line (boot step only — the interval sweep is unaffected); a partial metadata read withholds the all-clear instead of faking one", "since": "v16", "status": "active", - "revision": 3, + "revision": 4, "priority": "P1", "surface": "cli", "personas": [ @@ -1452,7 +1467,7 @@ "the shipped external datasource + fixture DB (showcase_external — IN SYNC by construction, provisioned idempotently each boot by external-fixture.ts)" ], "knownGaps": [ - "NO stock drift fixture exists — drift must be INDUCED, and only in a scratch copy: the fixture DB is re-provisioned in-sync each boot, and showcase_external deliberately declares onMismatch:'warn' (showcase-external.datasource.ts) precisely so drift can never brick the stock showcase. To induce, in a scratch app copy: the recipe QA run #24615 proved adds a field to the federated OBJECT instead of altering the fixture DB — ExternalCustomer gained `qa_drift_col: Field.text`, which no remote column backs, a measured missing_column — and it survives the boot's idempotent re-provisioning because the fixture's own table definition is untouched. Altering the fixture DB between boots is the weaker route (initObjects re-runs at every boot and may heal an added or dropped column; verify any such drift survives the re-run before scoring), as is binding a scratch federated object to a missing or mismatched table. Without an induced drift, the live arms of clauses 1-3 are blocked(fixture) and the unit pins are the fallback evidence", + "NO stock drift fixture exists — drift must be INDUCED, and only in a scratch copy: the fixture DB is re-provisioned in-sync each boot, and showcase_external deliberately declares onMismatch:'warn' (showcase-external.datasource.ts) precisely so drift can never brick the stock showcase. To induce, in a scratch app copy: the recipe QA run #24615 proved adds a field to the federated OBJECT instead of altering the fixture DB — ExternalCustomer gained `qa_drift_col: Field.text`, which no remote column backs, a measured missing_column — and it survives the boot's idempotent re-provisioning because the fixture's own table definition is untouched. Altering the fixture DB between boots is the weaker route (initObjects re-runs at every boot and may heal an added or dropped column; verify any such drift survives the re-run before scoring), as is binding a scratch federated object to a missing or mismatched table. Without an induced drift, the live arms of clauses 1-3 are blocked(fixture), and the unit pins are plugin-layer evidence beside that verdict, never a pass (RUNNER rule 6, a pin scores only the layer it drives)", "the 'fail'-DEFAULT arm additionally needs a datasource that OMITS external.validation.onMismatch (the `?? 'fail'` default, external-validation-plugin.ts; an unreadable definition also lands there — the def loader swallows the read failure to undefined) — stock showcase never exercises it live", "INDUCE MEASURED drift, not an outage: only MEASURED diffs reach the onMismatch policy — an 'unreachable' row (remote or definition could not be read) warns loudly and CONTINUES boot by maintainer ruling 2026-08-23 (#11166, external-validation-plugin.ts), so breaking connectivity (deleting the fixture DB file, bad credentials) tests the wrong arm and a non-aborting boot there is correct, not a gate failure", "clause 5's partial-read arm (a degraded metadata loader) is not reasonably inducible live — it is scored via its pin (list-diagnosed-consumer-sweep.test.ts), declared here rather than pretending a loader-outage fixture exists" @@ -1471,8 +1486,8 @@ { "clause": "the DEFAULT is 'fail' and it refuses boot: with drift present and onMismatch undeclared (or explicit 'fail'), boot aborts with ExternalSchemaMismatchError whose message names the object, the datasource, and the mismatched column per diff line (renderDiffMessage) — the highest-consequence branch here: a mis-typed federation must not come up serving garbage", "oracle": "log", - "verify": "the induced-drift boot's abort output against external-errors.ts; the default pinned at external-validation-plugin.ts (resolveOnMismatch `?? 'fail'`; an unreadable datasource definition ALSO lands on it — the memoized def loader answers undefined on a failed read). Only MEASURED diffs reach this throw — an 'unreachable' row warns and continues by ruling (#11166), see knownGaps. Unit fallback: external-validation-plugin.test.ts 'throws … default (fail) policy' + 'defaults to fail when the datasource definition is unavailable'", - "evidence": "the boot abort log (or the unit-test output when the live arm is blocked(fixture))" + "verify": "the induced-drift boot's abort output against external-errors.ts; the default pinned at external-validation-plugin.ts (resolveOnMismatch `?? 'fail'`; an unreadable datasource definition ALSO lands on it — the memoized def loader answers undefined on a failed read). Only MEASURED diffs reach this throw — an 'unreachable' row warns and continues by ruling (#11166), see knownGaps. The unit pins external-validation-plugin.test.ts 'throws … default (fail) policy' and 'defaults to fail when the datasource definition is unavailable' drive runValidation over stubbed services and assert the error class, not this message: plugin-layer evidence, never this clause's pass (RUNNER rule 6, a pin scores only the layer it drives). Without an induced-drift boot the clause is blocked(fixture) (knownGaps[0], [1])", + "evidence": "the boot abort log; when the live arm is blocked(fixture), that verdict with its fixture cited, the unit-test output beside it as lower-layer evidence" }, { "clause": "'warn' proceeds and logs: the same drift under onMismatch:'warn' (the stock showcase policy) completes boot and logs '[external-validation] external schema drift' carrying datasource, object, and the structured diffs", @@ -1489,8 +1504,8 @@ { "clause": "checkOnBoot IS ENFORCED at the boot step — the rev-1 declared≠enforced finding is FIXED by #13149 (2026-08-29) and must not be re-filed: (a) a datasource with external.validation.checkOnBoot:false is dropped BEFORE any verdict — no onMismatch policy applies to it, a measured mismatch on it cannot abort boot, no unreachable-remote warning is raised for it, and its objects are not counted in the all-clear; (b) the skip is LOUD — one info line names the skipped datasource(s), says their federated objects were NOT gated, and says background drift checking is unaffected; (c) only an explicit false opts out: an absent key (schema default true), an unparseable/legacy row, or a definition the metadata service could not hand back all VALIDATE (the safe direction), and the misspellings checkonboot/validateonboot are parse-REJECTED with a did-you-mean, never folded", "oracle": "log", - "verify": "boot the drifted scratch fixture with checkOnBoot:false — boot completes and the '[external-validation] boot schema validation SKIPPED …' info line names it (external-validation-plugin.ts; bootCheckEnabled `!== false`); a sibling datasource in the same boot is still judged. Unit pins: external-validation-checkonboot.test.ts (all five describes — skip, unchanged default, per-datasource scope, one parsed spelling, boot-step-only scope)", - "evidence": "the completed-boot log with the skip line (or the pin output where the live arm is blocked(fixture))" + "verify": "boot the drifted scratch fixture with checkOnBoot:false — boot completes and the '[external-validation] boot schema validation SKIPPED …' info line names it (external-validation-plugin.ts; bootCheckEnabled `!== false`); a sibling datasource in the same boot is still judged. external-validation-checkonboot.test.ts (all five describes — skip, unchanged default, per-datasource scope, one parsed spelling, boot-step-only scope) drives the plugin over stubbed services: plugin-layer evidence for this clause, never its pass (RUNNER rule 6, a pin scores only the layer it drives)", + "evidence": "the completed-boot log with the skip line; where the live arm is blocked(fixture), that verdict with its fixture cited, the pin output beside it as lower-layer evidence" }, { "clause": "the checkOnBoot skip is BOOT-STEP-ONLY, by maintainer ruling (2026-08-29): a datasource declaring both checkOnBoot:false and checkIntervalMs still gets its background drift checker armed — the two keys answer different questions ('gate my startup on this' vs 'watch this while I run'), and arming a watcher is not an opt back in to the boot gate", @@ -1525,7 +1540,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/runtime/src/external-validation-plugin.test.ts (fail-default / warn / ignore / default-when-unreadable + drift-event emission) + packages/runtime/src/external-validation-checkonboot.test.ts (#13037/#13149 — the checkOnBoot gate, its scope, and the one-spelling rule) + packages/runtime/src/list-diagnosed-consumer-sweep.test.ts (#6504 withheld all-clear) — the LIVE boot-abort arm is not pinned; an induced-drift boot is required for it" + "ref": "packages/runtime/src/external-validation-plugin.test.ts — acceptance[6] and negative[4] (its 'ExternalValidationPlugin — background drift detection (ADR-0015 §5.2)' describe: one external.schema.drift event per drifted object, and 'runDriftCheck is a no-op (no throw) when the scoped validation rejects') + packages/runtime/src/external-validation-checkonboot.test.ts — acceptance[4] (its 'scope: the gate covers the BOOT STEP ONLY' describe) + packages/runtime/src/list-diagnosed-consumer-sweep.test.ts — acceptance[5] and negative[2] (the #6504 boot-gate describe). All three drive ExternalValidationPlugin over stubbed services. Their fail-default / warn / ignore / default-when-unreadable cases and the other checkOnBoot describes are evidence for acceptance[0]–[3] at that layer only, never their pass: those clauses score the boot log, and need an induced-drift boot" }, "source": [ "packages/runtime/src/external-validation-plugin.ts#runValidation (Gate 2 kernel:ready runValidation; the #13149 checkOnBoot gate + skip info line; the #11166 unreachable carve-out — warn and continue, only measured diffs reach onMismatch; the fail throw), (bootCheckEnabled — explicit false only, safe direction), (resolveOnMismatch — `?? 'fail'`), (memoized def loader; a failed read answers undefined → strict defaults), (announceAllClear #6504; the INCOMPLETE-sweep warn), (background drift checks + external.schema.drift events; the boot-step-only ruling ⛔)", @@ -1553,6 +1568,12 @@ "date": "2026-10-20", "change": "checklist-accuracy findings of QA run #24615 (18.0 sweep round 2, ba4afcc2; wave anchor #24579 §3). knownGaps[0] preferred altering the fixture DB; the recipe #24615 proved adds a field to the federated object (a measured missing_column) and survives re-provisioning, so the gap leads with it. steps[0] names --log-level info: the all-clear, SKIPPED and armed-timer lines are info-level and absent at the default warn level. No clause changed", "ref": "#24717" + }, + { + "revision": 4, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause; acceptance[0] (verify, evidence), acceptance[3] (verify, evidence) and fixtures.knownGaps[0] rewritten — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at drift-gate acceptance[0] and [3], round 15 of wave anchor #24579. acceptance[0]–[3] score the boot log, and the item told the runner to score them from the plugin pins when the live arm is blocked ('Unit fallback', 'the unit pins are the fallback evidence'). The three pins drive ExternalValidationPlugin over stubbed services. By RUNNER rule 6 (a pin scores only the layer it drives) they score the test-oracle clauses acceptance[4]–[6] and negative[2] and [4], and are cited as plugin-layer evidence beside a blocked(fixture) verdict for the rest. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From 356032a971134416955c8c60d3508b2cdd3667fd Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:03:04 +0000 Subject: [PATCH 04/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20dash?= =?UTF-8?q?boards.json:=20two=20refs=20scored=20per=20clause?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dataset-report-authoring's bare ref narrows to acceptance[0]; empty-null-bucket-boundaries adds the empty-group-bucket-parity suite its acceptance[4] verify already names. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../platform-checklist/areas/dashboards.json | 20 +++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index bbf491ea24..dd1b6811a3 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -461,7 +461,7 @@ "title": "Dataset-only authoring works end to end: layout-less designer drafts save/publish, and every ReportType renders from the same semantic datasets", "since": "v16", "status": "active", - "revision": 3, + "revision": 4, "priority": "P1", "surface": "mixed", "personas": [ @@ -550,7 +550,7 @@ ], "automated": { "kind": "e2e", - "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts" + "ref": "packages/qa/dogfood/test/dashboard-designer-roundtrip.dogfood.test.ts — acceptance[0] ONLY, at the HTTP door (the showcase booted in-process): the layout-less draft PUT /meta/dashboard/NAME?mode=draft answers 200, and its POST …/publish answers 200. Its read-back asserts three widgets and no layout, but not acceptance[1]'s dataset + values form or the absence of the legacy inline-analytics keys. acceptance[1]–[7] and the negatives need the run" }, "traps": [ "hydration-race", @@ -582,6 +582,12 @@ "date": "2026-10-19", "change": "negative[1] corrected from QA run #24612 (18.0 sweep round 2, ba4afcc2), re-read at objectstack f89d29e382: the dangling-binding guard is an author-time rule run at publish and on an active save, never on a draft (runtime-authoring-gate.ts D1), so a draft with a dangling dataset, measure or dimension answers 200 and its publish answers 422 with the named rule. The negative now names the publish door, so a draft 200 is not read as the blank-without-error FAIL. Wave anchor #24579 round 2 §3 (6272314386)", "ref": "#24671" + }, + { + "revision": 4, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The dogfood pin drives the draft-save and publish doors (acceptance[0]), and reads the dashboard back for its widget count and missing layout only. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -590,7 +596,7 @@ "title": "Empty results, NULL group buckets and single datapoints degrade honestly — designed empty states, consistent bucket labels, no one-bar collapse", "since": "v16", "status": "active", - "revision": 1, + "revision": 2, "priority": "P2", "surface": "mixed", "personas": [ @@ -656,7 +662,7 @@ ], "automated": { "kind": "conformance", - "ref": "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts" + "ref": "packages/qa/dogfood/test/date-bucket-parity-conformance.test.ts (acceptance[3]) + packages/qa/dogfood/test/empty-group-bucket-parity.test.ts (acceptance[4]: on driver-sql over better-sqlite3 and on driver-sqlite-wasm, a plain and a date-bucketed groupBy over NULL rows key the empty bucket as a real null, with the same keys, key types and totals on the pushed-down path and the in-memory path)" }, "traps": [ "single-datapoint", @@ -674,6 +680,12 @@ "date": "2026-08-07", "change": "new item: boundary behavior (empty window, NULL bucket, single datapoint) pinned to the two bucket-parity conformance suites", "ref": "claude/platform-test-checklist-ocwugl" + }, + { + "revision": 2, + "date": "2026-10-20", + "change": "automated.ref adds empty-group-bucket-parity.test.ts for acceptance[4] — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a ref that misses its own pin), round 15 of wave anchor #24579. acceptance[4].verify names that suite and the ref did not. Read at base b1676441ba: it drives two real drivers and applyInMemoryAggregation over the same rows and asserts the same keys, key types and totals on both paths, which is acceptance[4]'s test oracle. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From 6e2b87a4e42342494a6df7c5f25e53843d97e266 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:04:23 +0000 Subject: [PATCH 05/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20plat?= =?UTF-8?q?form-core.json:=20four=20refs=20scored=20per=20RUNNER=20rule=20?= =?UTF-8?q?6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit protocol-18-handshake narrows to its test-oracle clause; the package-first dogfood pin scores no clause of metadata-authoring-roundtrip (it calls protocol.saveMetaItem in-process); settings-audit's ref narrows to the no-keyed-digest arm; lifecycle-retention-sweep says acceptance[5] is the enumSource ratchet's, not the pin's. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../areas/platform-core.json | 41 ++++++++++++++----- 1 file changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 3824df08e3..4aef7f0bbb 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -416,7 +416,7 @@ "title": "Metadata authoring round-trip: draft → publish on a WRITABLE package; read-only packages and locked types are server-side refused", "since": "v16", "status": "active", - "revision": 5, + "revision": 6, "priority": "P1", "surface": "mixed", "personas": ["seeded admin (admin@objectos.ai / admin123)"], @@ -458,7 +458,7 @@ { "clause": "DENY side of the package gate: a runtime-only create targeting a read-only code/installed package is REJECTED with writable_package_required — not silently coerced to a package-less orphan (the pre-ADR-0070 #2252 bug)", "oracle": "api", - "verify": "the refusal names writable_package_required (pinned by packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts)", + "verify": "the refusal names writable_package_required at the PUT door steps[5] drives; packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts is protocol-layer evidence beside that answer, never this clause's pass (RUNNER rule 6, a pin scores only the layer it drives): it calls protocol.saveMetaItem in-process on a booted CRM stack", "evidence": "the refusal response" }, { @@ -482,7 +482,7 @@ "traps": ["dispatcher-vs-hono-route", "stale-console-bundle"], "automated": { "kind": "api", - "ref": "packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts; objectui: e2e/live/studio-record-page.spec.ts (acceptance[2] ONLY: it creates a record page bound to showcase_invoice and asserts the captured PUT /meta/page body's type 'record', its object, and regions seeded with a record:highlights block), e2e/live/studio-object-designer.spec.ts (scores NO clause of this item: its cases drive the object designer and the Studio package switcher, and no clause here scores either)" + "ref": "packages/qa/dogfood/test/package-first-authoring.dogfood.test.ts (scores NO clause of this item: it calls protocol.saveMetaItem in-process on a booted CRM stack — the WRITABLE_PACKAGE_REQUIRED refusal with its writable-base control, then the package lifecycle — and drives no /api/v1/meta door, so it is protocol-layer evidence for acceptance[3] only); objectui: e2e/live/studio-record-page.spec.ts (acceptance[2] ONLY: it creates a record page bound to showcase_invoice and asserts the captured PUT /meta/page body's type 'record', its object, and regions seeded with a record:highlights block), e2e/live/studio-object-designer.spec.ts (scores NO clause of this item: its cases drive the object designer and the Studio package switcher, and no clause here scores either)" }, "source": [ "packages/runtime/src/route-ledger.ts (PUT /meta/:type/:name, GET /meta/_drafts, GET /meta/:type/:name/published, POST /packages/:id/publish-drafts)", @@ -495,7 +495,8 @@ { "revision": 2, "date": "2026-08-11", "change": "annotated the objectui e2e/live/studio-record-page.spec.ts half of this item's automated.ref as STALE (run #7695): it fills the Object control as an input when it is now a role=combobox button, so it fails against the current surface, not against a defect. The Studio-designer clause now says drive it by hand until the spec is re-pointed; the dogfood pin and studio-object-designer.spec.ts are unaffected. Re-pointing lives in objectui (cross-repo half of #7753 item 6)", "ref": "#7753" }, { "revision": 3, "date": "2026-10-16", "change": "steps[1], [3] and [7] and acceptance[0].verify and [5].verify re-pointed (checklist-accuracy findings of QA run #23939, wave #23878). A plain PUT /meta/view/NAME saves state=active, so the draft never appeared in /meta/_drafts — ?mode=draft is required — and a view's name must be qualified OBJECT.view (#23939 used showcase_task.os_qa_view_RUNID). The Studio half now names the create form (/_console/apps/showcase_app/metadata/page/new) and its 'list' default, which has to be set to 'record' for a record page. Not loosened: every clause still scores the same server reads", "ref": "#24036" }, { "revision": 4, "date": "2026-10-20", "change": "automated.stale.observedIn, partial, why and ownedBy refreshed — the carried note from round 6 of wave anchor #24579 (its report, comment 6280484387), the refresh round 6 made on studio-authoring.record-page-roundtrip in PR #24697. Tier 1 run #24598 re-measured studio-record-page.spec.ts red at the sweep's console pin 4016370af3c9 (0 passed / 1 failed), and the spec is byte-identical at the shipped pin 12dc7b47c340, so the marker first written from #7695 still holds; ownedBy names the family card objectstack-ai/objectui#13104. The why also said studio-object-designer.spec.ts was unaffected; #24598 ran its F1 and F4 red (F2 and F3 passed), and partial now says so. No clause changed", "ref": "#24717" }, - { "revision": 5, "date": "2026-10-20", "change": "automated.stale removed, automated.ref scoped per objectui spec, and acceptance[2].verify's stale note rewritten — round 14 of wave anchor #24579 (18.0 sweep), on the objectui pointer 6284006658. objectui#13132 (objectui 0b36ffa) repaired both specs the stale record named, and the console pin moved to 616df6598b42 (PR #24829), which contains 0b36ffa. studio-record-page.spec.ts: blob 181b2562c18d at the old pin 12dc7b47c340, 3844a60ccd79 at both 0b36ffa and 616df6598b42; it now sets Page type to record and picks the object from the Object combobox. studio-object-designer.spec.ts: blob 5967807df335, then 58a7f1cbf024 at both; F1 and F4 repaired. objectui measured its whole live suite at 0b36ffa as 61 passed (11.0m) on the shipped showcase; no live run exists at 616df6598b42, so the verdict there is NOT MEASURED. Re-read at the new pin, the ref keeps only what this record claimed: studio-record-page.spec.ts asserts acceptance[2], the clause the stale record sent to hand-driving, and studio-object-designer.spec.ts, on which no clause here rests, now says so. The dogfood pin is unchanged. No clause changed", "ref": "#24857" } + { "revision": 5, "date": "2026-10-20", "change": "automated.stale removed, automated.ref scoped per objectui spec, and acceptance[2].verify's stale note rewritten — round 14 of wave anchor #24579 (18.0 sweep), on the objectui pointer 6284006658. objectui#13132 (objectui 0b36ffa) repaired both specs the stale record named, and the console pin moved to 616df6598b42 (PR #24829), which contains 0b36ffa. studio-record-page.spec.ts: blob 181b2562c18d at the old pin 12dc7b47c340, 3844a60ccd79 at both 0b36ffa and 616df6598b42; it now sets Page type to record and picks the object from the Object combobox. studio-object-designer.spec.ts: blob 5967807df335, then 58a7f1cbf024 at both; F1 and F4 repaired. objectui measured its whole live suite at 0b36ffa as 61 passed (11.0m) on the shipped showcase; no live run exists at 616df6598b42, so the verdict there is NOT MEASURED. Re-read at the new pin, the ref keeps only what this record claimed: studio-record-page.spec.ts asserts acceptance[2], the clause the stale record sent to hand-driving, and studio-object-designer.spec.ts, on which no clause here rests, now says so. The dogfood pin is unchanged. No clause changed", "ref": "#24857" }, + { "revision": 6, "date": "2026-10-20", "change": "automated.ref and acceptance[3].verify narrowed — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: acceptance[3]'s verify names the dogfood pin as covering the PUT door), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 14's ref edit: package-first-authoring.dogfood.test.ts calls protocol.saveMetaItem in-process on a booted CRM stack and never PUT /api/v1/meta. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause of this item, and the ref and acceptance[3].verify now say so. The objectui studio-record-page.spec.ts keeps acceptance[2]. No clause changed", "ref": "#PENDING-24579R15" } ] }, { @@ -2567,7 +2568,7 @@ "title": "ADR-0057 lifecycle sweep: backdated telemetry rows past retention are reaped BOUNDED under a system context on the hourly sweep, onlyWhen scoping spares live rows, an archive-declaring object is NEVER hot-deleted unarchived (retained + reported archive-pending when no archive datasource exists), the transient class stays on the primary, and OS_LIFECYCLE_DISABLED=1 disarms the sweep", "since": "v16", "status": "active", - "revision": 4, + "revision": 5, "priority": "P1", "surface": "mixed", "personas": ["operator (local shell + direct sqlite access)", "seeded admin (API reads)"], @@ -2628,7 +2629,7 @@ { "clause": "the class vocabulary this item's variants ride is the spec's own: LifecycleClassSchema = record|audit|telemetry|transient|event (5), with audit/telemetry/event forming the system-ledger carve-out set in the engine (SYSTEM_LEDGER_LIFECYCLE_CLASSES — transient deliberately absent because transient objects stay on the primary and never reach the gate)", "oracle": "build", - "verify": "read the enum at packages/spec/src/data/object.zod.ts and the engine set at packages/objectql/src/engine.ts; the enumSource pin below fails this item loudly when the spec grows a sixth class", + "verify": "read the enum at packages/spec/src/data/object.zod.ts and the engine set at packages/objectql/src/engine.ts; the item's enumSource ratchet below (check:platform-checklist's variants-freshness check, not a test pin) fails this item loudly when the spec grows a sixth class. It counts LifecycleClassSchema's members only, so the engine set is read by hand", "evidence": "both source excerpts" } ], @@ -2654,7 +2655,7 @@ "traps": ["eventual-consistency", "clock-skew", "destructive-in-place", "absence-inference"], "automated": { "kind": "unit", - "ref": "packages/objectql/src/lifecycle/lifecycle-service.test.ts (archive-pending skip, bounded reap, rotation fallback, governance)" + "ref": "packages/objectql/src/lifecycle/lifecycle-service.test.ts (archive-pending skip, bounded reap, rotation fallback, governance) — acceptance[2]'s pinned half ('retains everything and reports archive-pending when the archive datasource is missing') and acceptance[3] (the 'LifecycleService.sweep — unguarded reap batching (#5194)' describe), plus negative[0]–[1], over a fake engine that records the reads and deletes the sweep issues. acceptance[0], [1] and [4] need the live boot. acceptance[5] is no pin's: its oracle is the build read of the two sources. The item's enumSource (check:platform-checklist's variants-freshness ratchet on LifecycleClassSchema, expect 5) holds only its class count, not the engine's SYSTEM_LEDGER_LIFECYCLE_CLASSES membership" }, "source": [ "packages/objectql/src/lifecycle/lifecycle-service.ts#SYSTEM_CTX (the ADR-0057 enforcer: header contract — reaper/rotator/archiver, the never-hot-delete-unarchived safety rule, #5194 bounds; SYSTEM_CTX; DEFAULT_LIFECYCLE_SWEEP_MS 3_600_000; DEFAULT_LIFECYCLE_INITIAL_DELAY_MS 60_000; REAP_BATCH_SIZE 500 / REAP_MAX_BATCHES_PER_SWEEP 20; enabled getter incl. OS_LIFECYCLE_DISABLED; start()/timer; archive safety; the aggregate sweep line)", @@ -2688,6 +2689,12 @@ "date": "2026-10-16", "change": "fixtures.requires[0], knownGaps[1] and steps[1] re-pointed (checklist-accuracy finding of QA run #23939, wave #23878). The knownGap said no telemetry datasource exists on stock, but `os dev` on a file DB now provisions data.telemetry.db beside the primary and routes telemetry/audit/event-classed objects to it — sys_automation_run and sys_audit_log live there, which is where #23939 seeded them. The seeding step now names the file per object; a row seeded into the primary would sit outside the sweep and read as a survivor. Not loosened: the clauses score the same reap, survivors and archive-pending report", "ref": "#24036" + }, + { + "revision": 5, + "date": "2026-10-20", + "change": "automated.ref says which clause each part scores, and acceptance[5].verify names its check — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: 'acceptance[5] is checked by the item's enumSource gate, not a pin'), round 15 of wave anchor #24579. The pin drives LifecycleService.sweep over a fake engine: acceptance[2]'s pinned half, [3], and negative[0]–[1]. acceptance[5]'s count is held by the enumSource variants-freshness ratchet of check:platform-checklist (LifecycleClassSchema, expect 5), and its engine-set half by reading packages/objectql/src/engine.ts. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -2950,7 +2957,7 @@ "title": "The settings audit trail keeps no fingerprint of a secret-valued setting that a reader can match offline: both ledgers record the platform's keyed digest or none, never the value or an unkeyed hash, so a caller who can only read the audit rows learns that the secret changed and nothing about its value", "since": "v17", "status": "active", - "revision": 3, + "revision": 4, "priority": "P1", "surface": "api", "personas": [ @@ -3028,7 +3035,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/services/service-settings/src/settings-audit-secret-digest.test.ts (7 cases: 'records the provider keyed digest on both ledgers, not the unkeyed hash of the value' · 'is stable for equal values and distinct for different ones' · 'records no fingerprint on a reset' · the two legacy inline-adapter cases · the refusing-provider case · 'keep the unkeyed adapter digest of the canonical JSON, unchanged' for non-secret settings)" + "ref": "packages/services/service-settings/src/settings-audit-secret-digest.test.ts — acceptance[5] and negative[2]–[3] ONLY: the no-keyed-digest arm a stock boot cannot reach (knownGaps[0]), through 'legacy inline-adapter path with no provider records no fingerprint and reports it once' and 'a provider that refuses a keyed digest leaves the write intact and records no fingerprint'. The pin drives SettingsService over stub audit sinks. So its other cases — 'records the provider keyed digest on both ledgers, not the unkeyed hash of the value', 'is stable for equal values and distinct for different ones', 'records no fingerprint on a reset', the provider-wired inline-adapter case, and the non-secret 'keep the unkeyed adapter digest of the canonical JSON, unchanged' — are service-layer evidence for acceptance[0]–[4] and negative[0]–[1], never their pass: those score the ledger rows read back over the API (steps[0]–[6])" }, "source": [ "packages/services/service-settings/src/settings-service.ts#secretAuditDigest (one helper supplies a secret write's fingerprint on both write branches, the sys_secret path and the legacy inline-adapter path: the provider's keyedDigest, or null with a once-per-key warn; never the adapter's unkeyed digest)", @@ -3056,6 +3063,12 @@ "date": "2026-10-19", "change": "fixtures.requires[2], steps[1] and acceptance[0].clause, [3] (clause, verify) re-pointed: mail is a global-scope namespace, and the settings audit trail's global rung moved to the deployment-level sys_platform_setting_audit (ADR-0131 D7, ruling record 6257510973 Q4 A). Global-rung rows an 18.0 install wrote stay in sys_setting_audit until the upgrade ceremony moves them. Not loosened: the same keyed-digest, no-value and reset rules are scored on the same rows", "ref": "#24530" + }, + { + "revision": 4, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at settings-audit acceptance[0]–[4], round 15 of wave anchor #24579. The ref listed seven cases whose names read as acceptance[0], [2], [3] and [4]. The pin drives SettingsService over stub audit sinks and never reads sys_platform_setting_audit or sys_audit_log over the API, which is what those clauses score. By RUNNER rule 6 (a pin scores only the layer it drives) it scores acceptance[5] and its negatives [2] and [3] only (QA run #24838 scored acceptance[0]–[4] live). Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, @@ -3162,7 +3175,7 @@ "title": "Protocol 18 upgrade surfaces: a package declaring engines.protocol \"^17\" is refused 422 OS_PROTOCOL_INCOMPATIBLE naming `os migrate meta --from 17` (\"^18\" is admitted), a stored v17-shaped row is served canonical while a PUT of the same old shape is refused at parse, and a \"^18\" artifact built before a retirement boots with one warn per replayed conversion", "since": "v18", "status": "active", - "revision": 1, + "revision": 2, "priority": "P1", "surface": "mixed", "personas": [ @@ -3211,7 +3224,7 @@ ], "automated": { "kind": "unit", - "ref": "packages/runtime/src/domains/packages-install-protocol-incompatible.test.ts + packages/cli/test/migrate-meta-protocol-range.test.ts + packages/spec/src/conversions/stored.test.ts + packages/metadata-protocol/src/protocol.stored-migration.test.ts + packages/metadata/src/plugin-artifact-forward-conversion-major-step.test.ts" + "ref": "packages/runtime/src/domains/packages-install-protocol-incompatible.test.ts + packages/cli/test/migrate-meta-protocol-range.test.ts + packages/spec/src/conversions/stored.test.ts + packages/metadata-protocol/src/protocol.stored-migration.test.ts + packages/metadata/src/plugin-artifact-forward-conversion-major-step.test.ts — acceptance[0] ONLY (the test-oracle clause: every named file reports at least one test and 0 failed). None drives a public door or a boot: packages-install-protocol-incompatible.test.ts calls HttpDispatcher.handlePackages over a fake kernel (the 422 OS_PROTOCOL_INCOMPATIBLE body with its migrateCommand, for acceptance[1]); stored.test.ts unit-tests applyConversionsToStoredItem, the replay a served row rides (acceptance[2]'s serving half); plugin-artifact-forward-conversion-major-step.test.ts reads the conversion warn off a mocked logger (acceptance[3]); migrate-meta-protocol-range.test.ts and protocol.stored-migration.test.ts drive the migrate passes in-process. Each is lower-layer evidence, never the pass of acceptance[1]–[3], which need the run" }, "source": [ "packages/spec/src/kernel/protocol-version.ts#PROTOCOL_VERSION", @@ -3227,6 +3240,12 @@ "date": "2026-10-16", "change": "initial — authored from the v18 line (18.0.0-next.*) delta: the capability landed on the v18 pre-release line and the ledger carried no `since: \"v18\"` item for it", "ref": "#23879" + }, + { + "revision": 2, + "date": "2026-10-20", + "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The five pins pass as acceptance[0] asks, but none drives the install door, the GET/PUT door or an os start boot. By RUNNER rule 6 (a pin scores only the layer it drives) they score acceptance[0] only, and the ref says which lower layer each reaches for acceptance[1]–[3]. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From 70788f80b6f176f198f01b4aab287346f2f7982d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:05:57 +0000 Subject: [PATCH 06/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20auto?= =?UTF-8?q?mation.json:=20flow-node=20matrix=20drops=20automated,=20trigge?= =?UTF-8?q?r=20matrix's=20negative[0]=20pin=20note=20narrowed?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ledger text only; see the round-15 PR body for the per-finding table. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../platform-checklist/areas/automation.json | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index edd18fe744..39dadb304c 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -400,7 +400,7 @@ "title": "Every demonstrable flow node type authors in the designer, executes in a run, and surfaces its step in run logs", "since": "v15", "status": "active", - "revision": 7, + "revision": 8, "priority": "P1", "surface": "mixed", "personas": [ @@ -413,7 +413,8 @@ ], "knownGaps": [ "parallel_gateway / join_gateway / boundary_event are deliberately NOT in the matrix: BPMN-interop lowering targets whose author-facing forms are the ADR-0031 structured containers — waived with reasons in examples/app-showcase/src/coverage.ts FLOW_NODE_WAIVERS, and the coverage test enforces every OTHER enum member is authored", - "showcase_fan_out_notify (the parallel + http owner in the map above) posts to Slack and needs outbound egress. On an egress-denied runner, prove parallel on another run that carries it (showcase_nested_fan_out_reminders — a loop-body parallel), and record http as blocked(environment) rather than FAIL when its only located steps failed for want of egress (#21784 FE13). http itself is provable without egress on a SELF-PINGING scratch flow: register (POST /api/v1/automation) start → http {method: 'GET', url: 'http://127.0.0.1:/api/v1/health'} → end, trigger it (POST /api/v1/automation//trigger), read the run (http step success), then DELETE /api/v1/automation/ — measured on main c91af8ed6: 200, run completed, http success" + "showcase_fan_out_notify (the parallel + http owner in the map above) posts to Slack and needs outbound egress. On an egress-denied runner, prove parallel on another run that carries it (showcase_nested_fan_out_reminders — a loop-body parallel), and record http as blocked(environment) rather than FAIL when its only located steps failed for want of egress (#21784 FE13). http itself is provable without egress on a SELF-PINGING scratch flow: register (POST /api/v1/automation) start → http {method: 'GET', url: 'http://127.0.0.1:/api/v1/health'} → end, trigger it (POST /api/v1/automation//trigger), read the run (http step success), then DELETE /api/v1/automation/ — measured on main c91af8ed6: 200, run completed, http success", + "no pin scores a full clause of this item (RUNNER rule 6, a pin scores only the layer it drives; the theme-B rule), so the item carries no automated ref. packages/qa/dogfood/test/flow-node.dogfood.test.ts runs ONE update_record node over the HTTP trigger, with its input variable wired into the filter (the target stamped, the bystander untouched). That is one variant of acceptance[1]'s twenty, and no clause scores a single variant. automated may return when a pin drives the whole matrix, the palette feed or the unknown-type contract" ] }, "variants": [ @@ -497,10 +498,6 @@ "seed-data-thin", "automation-input" ], - "automated": { - "kind": "e2e", - "ref": "packages/qa/dogfood/test/flow-node.dogfood.test.ts — covers ONE variant of acceptance[1] only: an update_record node runs over the HTTP trigger with its input variable wired into the filter (the target stamped, the bystander untouched); the matrix, the palette, the suspending types, the region tags and the unknown-type contract need the run" - }, "source": [ "packages/spec/src/automation/flow.zod.ts#FlowNodeAction (FlowNodeAction — the built-in seed set; type validated at registerFlow, not by a closed enum)", "packages/spec/src/automation/flow.zod.ts#FLOW_STRUCTURAL_NODE_TYPES (start / end: structural — no executor, no descriptor, so absent from GET /automation/actions; end logs no step)", @@ -555,6 +552,12 @@ "date": "2026-10-19", "change": "acceptance[1] (clause, verify), steps[1] and automated.ref corrected — checklist-accuracy findings of the 18.0 QA sweep (QA run #24545, automation.flow-node-type-matrix acceptance[1] and steps[1]) and round 2's Tier 1 record (#24598, via wave anchor #24579 §3, comment 6272032736), which names this ref among those that over-claim clause coverage. \"end logs no step\" is true only of a taken end: an untaken branch's end is logged skipped (recordSkipped, service-automation engine.ts, at base 9a7838edf5); the palette has no Add node on the read-only package; and the one dogfood pin runs a single update_record node", "ref": "#24649" + }, + { + "revision": 8, + "date": "2026-10-20", + "change": "automated removed, fixtures.knownGaps[2] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: Tier 1 in name only), round 15 of wave anchor #24579. The ref already said its one dogfood pin covers ONE variant of acceptance[1]. A variant is not a clause, so no full clause is pinned: automated is dropped (theme-B rule), and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ], "enumSource": { @@ -568,7 +571,7 @@ "title": "Every flow trigger type fires and produces a run of its bound flow; the run's trigger block records the launch event", "since": "v15", "status": "active", - "revision": 5, + "revision": 6, "priority": "P1", "surface": "mixed", "personas": [ @@ -639,7 +642,7 @@ } ], "negative": [ - "anonymous POST /api/v1/automation/:name/trigger must be denied (401) with NO run row created — a 2xx or a run row is a FAIL (pinned by packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts)", + "anonymous POST /api/v1/automation/:name/trigger must be denied (401) with NO run row created — a 2xx or a run row is a FAIL (its 401 half is pinned at the HTTP door by packages/qa/dogfood/test/showcase-anonymous-deny-surfaces.dogfood.test.ts 'anonymous POST /automation/:name/trigger is denied (401)'; that case reads no run rows afterwards, so the no-run-row half is driven by hand)", "an intake POST with a wrong or missing HMAC signature must be refused (not 202) and create no task — a silent 202 on a bad signature is a FAIL" ], "traps": [ @@ -687,6 +690,12 @@ "date": "2026-10-19", "change": "acceptance[4].verify and automated.ref corrected — checklist-accuracy finding of the 18.0 QA sweep (QA run #24545, automation.trigger-type-matrix acceptance[4] / steps[5]: the screen flow launches through the action door, as screen-flow-roundtrip revision 4 already records) and round 2's Tier 1 record (#24598, via wave anchor #24579 §3, comment 6272032736), which names this ref among those that over-claim clause coverage: its two tests are the ledger ratchet only. Re-read at base 9a7838edf5. No clause changed", "ref": "#24649" + }, + { + "revision": 6, + "date": "2026-10-20", + "change": "negative[0]'s pin note narrowed, automated.ref unchanged — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: negative[0]'s pin is not in the ref), round 15 of wave anchor #24579. Read at base b1676441ba: the dogfood case asserts the anonymous trigger's 401 on the served showcase composition and never reads the runs after it. So it asserts half of negative[0] ('denied (401) with NO run row created'). A pin joins the ref only for a full clause at its layer, so it stays out, and negative[0] now says which half it pins. negative[0]'s FAIL condition is unchanged", + "ref": "#PENDING-24579R15" } ] }, From 1b8429134bbf35914fa4d8bb13375c2126a25cfb Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:06:33 +0000 Subject: [PATCH 07/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20atta?= =?UTF-8?q?chments-storage.json:=20field-accept-maxsize=20drops=20its=20ha?= =?UTF-8?q?ndler-direct=20automated=20ref?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ledger text only; see the round-15 PR body for the per-finding table. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- .../qa/platform-checklist/areas/attachments-storage.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json index 5a0d3a6b2e..26823f4d26 100644 --- a/docs/qa/platform-checklist/areas/attachments-storage.json +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -898,7 +898,7 @@ "title": "A field's declared accept/maxSize is re-enforced SERVER-SIDE on record write — the control the client widget check explicitly is not — with the declared missing-metadata and dotless-extension holes scored as documented boundaries", "since": "v17", "status": "active", - "revision": 4, + "revision": 5, "priority": "P1", "surface": "api", "personas": ["seeded admin (admin@objectos.ai) — the check is a declaration constraint, not an authz gate: it binds every caller identically, so no restricted persona is needed"], @@ -916,7 +916,8 @@ "knownGaps": [ "FIXTURE GAP (why the recipe exists): the showcase declares accept/maxSize only on ACTION PARAMS (examples/app-showcase/src/ui/actions/index.ts#ActionParamGalleryAction, its p_cover and p_attachments params) — that lane feeds the dialog widget and the ADR-0059 Confirm-while-uploading guard pinned by records-forms.upload-guard-blocks-confirm, and never reaches assertFileConstraints, which fires on OBJECT-FIELD references at record write. No showcase object field carries either key, so on stock seeds this item cannot run at all. Landing one constrained field in the showcase seeds would retire the qa-media-constraints recipe.", "CONTENT-SNIFFING BOUNDARY (record so runs do not score it as a leak): enforcement is DECLARATION-based, never content-based. sys_file.mime_type is taken verbatim from the client's own request body on both upload doors — presigned (storage-routes.ts destructure, persisted, even echoed back as the upload content-type header) and chunked — and nothing on the upload path sniffs magic bytes (no file-type/sniffing code exists in service-storage; measured by search, only fs.readdir withFileTypes matches). A caller may therefore upload arbitrary bytes while declaring image/png and pass every accept test. That is the boundary of what the platform claims; a run demonstrating it records a documented-boundary observation, never a FAIL of this item.", - "WIRE-ENVELOPE — MEASURED, the source analysis this row used to carry is superseded: FileConstraintError now declares `status: 400` beside its registered `code: 'ERR_FILE_CONSTRAINT'` (packages/services/service-storage/src/file-reference-lifecycle.ts, the same ADR-0112 shape as FileFieldBulkWriteError), so the REST declared-status passthrough answers 400 ERR_FILE_CONSTRAINT with the bounded message in the body — the message names the field and the constraint ('File type … is not permitted by the accept list declared for 'poster' (image/png, image/jpeg)', 'File exceeds the maximum size declared for 'poster' (2097152 bytes > 1048576 bytes)'), measured on main c91af8ed6. It is no longer a sanitized 500, and since #23932 closed rest no longer logs a declared client refusal as '[REST] Unhandled error' — the info-level server log carries no line for it at all. Consequence for scoring: the enforcement clauses still pass on the NO-ROW oracle, and clause 3 scores the wire answer" + "WIRE-ENVELOPE — MEASURED, the source analysis this row used to carry is superseded: FileConstraintError now declares `status: 400` beside its registered `code: 'ERR_FILE_CONSTRAINT'` (packages/services/service-storage/src/file-reference-lifecycle.ts, the same ADR-0112 shape as FileFieldBulkWriteError), so the REST declared-status passthrough answers 400 ERR_FILE_CONSTRAINT with the bounded message in the body — the message names the field and the constraint ('File type … is not permitted by the accept list declared for 'poster' (image/png, image/jpeg)', 'File exceeds the maximum size declared for 'poster' (2097152 bytes > 1048576 bytes)'), measured on main c91af8ed6. It is no longer a sanitized 500, and since #23932 closed rest no longer logs a declared client refusal as '[REST] Unhandled error' — the info-level server log carries no line for it at all. Consequence for scoring: the enforcement clauses still pass on the NO-ROW oracle, and clause 3 scores the wire answer", + "no pin scores a clause of this item at its layer (RUNNER rule 6, a pin scores only the layer it drives), so the item carries no automated ref. packages/services/service-storage/src/file-reference-lifecycle.test.ts ('accept / maxSize enforcement' describe) drives the hook HANDLER-DIRECT through a fake engine: the rejection, the UPDATE refusal ('refuses an UPDATE onto an oversize file with the same envelope, leaving the record and both files as they were'), the per-entry vocabulary, the missing-metadata pass and the no-constraints pass. That is evidence for clauses 0/1/3/4/5/6 at the handler only, never their pass, and nothing of the wire envelope (clause 2): every clause here scores the REST answer and the rows it left. The dogfood lane has no pin for this seam. automated may return when a pin drives the REST write over a booted stack" ] }, "steps": [ @@ -982,7 +983,6 @@ "⛔ NOT a violation probe: a random/unknown id token in the field passes untouched BY DESIGN — an id matching no sys_file row is treated as an external/legacy value and skipped before any constraint is read (file-reference-lifecycle.ts); probe with a REAL committed sys_file or the run measures nothing" ], "traps": ["absence-inference", "wrong-panel"], - "automated": { "kind": "unit", "ref": "packages/services/service-storage/src/file-reference-lifecycle.test.ts ('accept / maxSize enforcement' describe) — HANDLER-DIRECT: drives the hook through a fake engine, covering the semantics of clauses 0/1/3/4/5/6 at the unit level: rejection; the UPDATE refusal (clause 3: its case 'refuses an UPDATE onto an oversize file with the same envelope, leaving the record and both files as they were' asserts the refused swap and the unchanged stored value); per-entry vocabulary; missing-metadata pass; no-constraints pass. It is NOT evidence about the wired REST lane or the wire envelope (clause 2), and per the attach-requires-parent-edit clause-3 lesson a handler-direct green can coexist with different wired behaviour — the dogfood lane has NO pin for this seam, which is precisely why this item exists." }, "source": [ "packages/services/service-storage/src/file-reference-lifecycle.ts#assertFileConstraints (the rationale: the widget check is a convenience, the server re-check is the control; missing metadata is not evidence of a violation), (assertFileConstraints — maxSize guard, testable filter + early return, accept mismatch throw), (matchesAcceptEntry — exact MIME / type\\/* wildcard /.ext-against-NAME vocabulary), (the check rides copy-on-claim in the before hooks — beforeInsert, beforeUpdate — and unknown ids are skipped)", "packages/spec/src/data/field.zod.ts#maxSize (accept/maxSize declared on FieldSchema, 'Offered to the file picker AND enforced on write') + packages/spec/liveness/field.json#live (both keys `live` with this enforcement as evidence)", @@ -995,7 +995,8 @@ { "revision": 1, "date": "2026-08-20", "change": "new item from the scoped scan-functionality sweep (扫描功能): the server-side re-enforcement of field accept/maxSize had NO coverage on either lane — the only pinned guard is the client dialog's (records-forms.upload-guard-blocks-confirm), which the enforcement source itself calls 'a convenience rather than a control'. Authored with the qa-media-constraints recipe (no stock showcase field declares either key), the two source-documented holes (missing metadata, dotless-name vs extension-only accept) as documented-boundary passes, the declaration-based content boundary recorded so runs do not score sniffing absence as a leak, and the per-source wire-envelope analysis (ERR_FILE_CONSTRAINT ledgered but status-less → sanitized 500 expected) carried as a knownGap for the run to measure rather than rediscover", "ref": "claude/new-session-0pv25p" }, { "revision": 2, "date": "2026-10-16", "change": "clause 3 and knownGaps WIRE-ENVELOPE expected, per an unmeasured source reading, the sanitized 500 INTERNAL_ERROR with the prose only in the server log, and told the next run to revise the clause if a 4xx appeared (#23878 checklist-accuracy finding). It did: re-measured on main c91af8ed6 with the qa-media-constraints recipe, the accept probe and the size probe both answer 400 ERR_FILE_CONSTRAINT with the field-naming message in the body (FileConstraintError now declares status 400), the compliant control answers 201, and the info-level log carries no line for the refusal (#23932 closed). The clause's oracle moves from log to api and scores status, code and the named field; the knownGap records the measured envelope", "ref": "#24036" }, { "revision": 3, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). fixtures.requires[1] cited line 255 of storage-routes.ts, and fixtures.knownGaps[0] and source[5] line 337 of the showcase actions index.ts, in the comma-range spelling (authored in 8d74a57487 as the lists 241-243 and 255, and 334 and 337). At that commit line 255 was the mime_type of the pending sys_file row the presigned-upload route writes, and line 337 the accept/maxSize of the action gallery's p_attachments param; at this base both have moved. requires[1] now anchors storage-routes.ts#registerStorageFamily, whose POST /storage/upload/presigned handler writes that row, and knownGaps[0] and source[5] anchor index.ts#ActionParamGalleryAction. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" }, - { "revision": 4, "date": "2026-10-20", "change": "automated.ref adds clause 3, the update path — checklist-accuracy finding of QA run #24779 (wave anchor #24579 round 12). Read at base 307ce4c4b4: file-reference-lifecycle.test.ts's 'accept / maxSize enforcement' case 'refuses an UPDATE onto an oversize file with the same envelope, leaving the record and both files as they were' asserts the refused swap and the unchanged stored value at the same handler-direct level as the clauses the ref already names. No clause changed", "ref": "#24824" } + { "revision": 4, "date": "2026-10-20", "change": "automated.ref adds clause 3, the update path — checklist-accuracy finding of QA run #24779 (wave anchor #24579 round 12). Read at base 307ce4c4b4: file-reference-lifecycle.test.ts's 'accept / maxSize enforcement' case 'refuses an UPDATE onto an oversize file with the same envelope, leaving the record and both files as they were' asserts the refused swap and the unchanged stored value at the same handler-direct level as the clauses the ref already names. No clause changed", "ref": "#24824" }, + { "revision": 5, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[3] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the ref disclaims the wired lane), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 12 added the acceptance[3] update-path case: the one pin is handler-direct over a fake engine, and every clause scores the REST answer. By RUNNER rule 6 (a pin scores only the layer it drives) it scores none of them, round 12's update-path case included. automated is dropped (theme-B rule), and the knownGap carries what the pin asserts. No clause changed", "ref": "#PENDING-24579R15" } ] }, { From 01864e1a1e0e66d6dd4a46624b27ba1c8f9b6a4d Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:07:08 +0000 Subject: [PATCH 08/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20iden?= =?UTF-8?q?tity-auth.json:=20admin-lifecycle=20ref=20adds=20the=20#3023=20?= =?UTF-8?q?plugin-security=20case?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ledger text only; see the round-15 PR body for the per-finding table. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- docs/qa/platform-checklist/areas/identity-auth.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json index f582c73886..aeeb78656c 100644 --- a/docs/qa/platform-checklist/areas/identity-auth.json +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -389,7 +389,7 @@ "title": "Admin user-lifecycle operations (ban/unban, set-password, impersonate, create/set-role/remove, revoke-sessions) enforce, persist, and stay closed to non-admins", "since": "v16", "status": "active", - "revision": 8, + "revision": 9, "priority": "P1", "surface": "mixed", "personas": ["platform admin", "target user", "non-admin forger"], @@ -489,7 +489,7 @@ ], "automated": { "kind": "e2e", - "ref": "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts (clause 8 — the sys_audit_log shapes create-user / set-user-password / import-users leave) + packages/qa/dogfood/test/admin-credential-lifecycle.dogfood.test.ts (clauses 1 and 2, both sides: an explicit password supplied ALONGSIDE generatePassword:true is the one that signs in and no generated password comes back, with a generatePassword-only create as the contrast so the clause cannot be satisfied by generatePassword being inert; the created account carries its credential sys_account row; set-user-password rotates — the original password is proven to work BEFORE the rotation, the new one after, and the old one is refused 401 INVALID_EMAIL_OR_PASSWORD; a member's forged create-user and set-user-password are each 403 PERMISSION_DENIED and an anonymous call 401 UNAUTHENTICATED, each with the state read back) + packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts (clause 9, REFUSAL half, over a population DERIVED from the running stack rather than listed: honoApp.routes for the ObjectStack raw mounts registered ahead of better-auth's catch-all, UNION auth.api for the endpoints the catch-all publishes — 31 routes at this configuration. The derived set and that suite's classification table are checked for exact agreement in BOTH directions. Universal invariant over all 31: neither an anonymous caller nor a plain member ever receives a 2xx, the single classified exception being has-permission, whose 200 body is asserted to be the negative answer. Every route is fired with a payload valid enough to REACH the authorization check) + packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts (NEW 2026-08-24 — clause 0, clause 6 and the ALLOWED half of clause 9: what the PLATFORM ADMIN gets, which was previously asserted NOWHERE. ADMIN-IDENTITY CONTROL runs first and every later assertion refuses to run until it passes: the subject carries platform_admin in positions[] and isPlatformAdmin true, and sys_user.role is NOT 'admin' — so a 403 measured there can never mean 'the fixture was never an admin'. It deliberately does NOT copy remove-user-atomicity.test.ts's in-process role='admin' write, which is exactly why no earlier test observed this defect family. CLAUSE 0 + clause 9's allowed half: ban-user persists banned + ban_reason and unban-user reverses it; unlock-user, import-users and oauth2/toggle-disabled each answer 200 — every one re-read from STORAGE rather than believed from the response body. CLAUSE 6: impersonate-user answers 200 and sys_session.impersonated_by records the admin. REFUSED-BY-DESIGN: the EIGHT consumer-less routes are pinned at exactly 403 plus their exact vendor code (status AND code, ADR-0112 — a status-only pin stays green when only the code moves), each entry naming the card that ruled it, with a no-effect control proving the refused remove-user / update-user / set-role moved nothing. SWEEP over the same derived population: no route may refuse the platform admin unless it is a recorded by-design refusal, and every derived route must be classified — the classification check is the one a newly mounted route trips, and it runs first for that reason. The admitted and refused sides use SEPARATE expectation instruments, so a bug in one cannot pass the other.) NOTE this item's clause-9 prose names SIX routes; the measured surface is 31 — do not tick 31 routes off a 6-route reading. STILL MANUAL: clause 7 (the console's impersonation indicator) — its oracle is a screenshot, which no dogfood test can drive. ⛔ CLAUSES 3, 4 AND 5 ARE RULED, NOT UNPINNED COVERAGE DEBT: clause 3 (set-role flips gate outcomes), clause 4 (revoke-user-sessions kills a live session mid-flight) and clause 5 (remove-user's owner_id FK clear) each require the platform admin to COMPLETE the operation, and all three routes are among the eight that refuse him BY DESIGN — #9969 closed not_planned for the seven consumer-less routes, #9968 ruled B and retired the set_user_role action instead of re-implementing the route. What is pinned is the refusal, pinned as intended behaviour. Scoring these as missing automation would be scoring the absence of a route the maintainer declined to build. If one of them later acquires a real ObjectStack consumer and is re-mounted, admin-platform-admin-standing.dogfood.test.ts fails until its entry moves from the by-design list to the admitted list. See #9482, #9652, #9968, #9969." + "ref": "packages/qa/dogfood/test/admin-identity-audit-trail.dogfood.test.ts (clause 8 — the sys_audit_log shapes create-user / set-user-password / import-users leave) + packages/qa/dogfood/test/admin-credential-lifecycle.dogfood.test.ts (clauses 1 and 2, both sides: an explicit password supplied ALONGSIDE generatePassword:true is the one that signs in and no generated password comes back, with a generatePassword-only create as the contrast so the clause cannot be satisfied by generatePassword being inert; the created account carries its credential sys_account row; set-user-password rotates — the original password is proven to work BEFORE the rotation, the new one after, and the old one is refused 401 INVALID_EMAIL_OR_PASSWORD; a member's forged create-user and set-user-password are each 403 PERMISSION_DENIED and an anonymous call 401 UNAUTHENTICATED, each with the state read back) + packages/qa/dogfood/test/admin-route-nonadmin-refusal.dogfood.test.ts (clause 9, REFUSAL half, over a population DERIVED from the running stack rather than listed: honoApp.routes for the ObjectStack raw mounts registered ahead of better-auth's catch-all, UNION auth.api for the endpoints the catch-all publishes — 31 routes at this configuration. The derived set and that suite's classification table are checked for exact agreement in BOTH directions. Universal invariant over all 31: neither an anonymous caller nor a plain member ever receives a 2xx, the single classified exception being has-permission, whose 200 body is asserted to be the negative answer. Every route is fired with a payload valid enough to REACH the authorization check) + packages/qa/dogfood/test/admin-platform-admin-standing.dogfood.test.ts (NEW 2026-08-24 — clause 0, clause 6 and the ALLOWED half of clause 9: what the PLATFORM ADMIN gets, which was previously asserted NOWHERE. ADMIN-IDENTITY CONTROL runs first and every later assertion refuses to run until it passes: the subject carries platform_admin in positions[] and isPlatformAdmin true, and sys_user.role is NOT 'admin' — so a 403 measured there can never mean 'the fixture was never an admin'. It deliberately does NOT copy remove-user-atomicity.test.ts's in-process role='admin' write, which is exactly why no earlier test observed this defect family. CLAUSE 0 + clause 9's allowed half: ban-user persists banned + ban_reason and unban-user reverses it; unlock-user, import-users and oauth2/toggle-disabled each answer 200 — every one re-read from STORAGE rather than believed from the response body. CLAUSE 6: impersonate-user answers 200 and sys_session.impersonated_by records the admin. REFUSED-BY-DESIGN: the EIGHT consumer-less routes are pinned at exactly 403 plus their exact vendor code (status AND code, ADR-0112 — a status-only pin stays green when only the code moves), each entry naming the card that ruled it, with a no-effect control proving the refused remove-user / update-user / set-role moved nothing. SWEEP over the same derived population: no route may refuse the platform admin unless it is a recorded by-design refusal, and every derived route must be classified — the classification check is the one a newly mounted route trips, and it runs first for that reason. The admitted and refused sides use SEPARATE expectation instruments, so a bug in one cannot pass the other.) + packages/plugins/plugin-security/src/security-plugin.test.ts ('[#3023] an engine referential FK clear (__referentialFieldClear) is exempt — owner_id:null cascade is allowed for a plain member', at the plugin-security unit: negative[3]'s #3023 half — its #3031 half is admin-credential-lifecycle.dogfood.test.ts's explicit-password case — so negative[3] is pinned whole; and clause 5's exemption half, which has no public door left and which clause 5 scores from this pin. Clause 5's refusal half is pinned without its owned-row read: admin-platform-admin-standing.dogfood.test.ts asserts the 403 and re-reads the target sys_user, but never the rows that user owns, so that read is driven by hand.) NOTE this item's clause-9 prose names SIX routes; the measured surface is 31 — do not tick 31 routes off a 6-route reading. STILL MANUAL: clause 7 (the console's impersonation indicator) — its oracle is a screenshot, which no dogfood test can drive. ⛔ CLAUSES 3, 4 AND 5 ARE RULED, NOT UNPINNED COVERAGE DEBT: clause 3 (set-role flips gate outcomes), clause 4 (revoke-user-sessions kills a live session mid-flight) and clause 5 (remove-user's owner_id FK clear) each require the platform admin to COMPLETE the operation, and all three routes are among the eight that refuse him BY DESIGN — #9969 closed not_planned for the seven consumer-less routes, #9968 ruled B and retired the set_user_role action instead of re-implementing the route. What is pinned is the refusal, pinned as intended behaviour. Scoring these as missing automation would be scoring the absence of a route the maintainer declined to build. If one of them later acquires a real ObjectStack consumer and is re-mounted, admin-platform-admin-standing.dogfood.test.ts fails until its entry moves from the by-design list to the admitted list. See #9482, #9652, #9968, #9969." }, "traps": ["wrong-persona", "shared-browser-tab"], "source": [ @@ -529,6 +529,12 @@ "date": "2026-10-17", "change": "steps[9] names where Impersonate lives (checklist-accuracy finding of the identity-auth slice report on #23878 (6216631966)): only in the Users list row menu, so 'the admin surface' sent a runner to the record page, which does not offer it. Wording only", "ref": "#24036" + }, + { + "revision": 9, + "date": "2026-10-20", + "change": "automated.ref adds security-plugin.test.ts's [#3023] case — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: acceptance[5] and negative[3] cite a pin not in the ref), round 15 of wave anchor #24579. Read at base b1676441ba: the case asserts at the plugin-security unit that an owner_id:null write carrying __referentialFieldClear passes the owner-anchor guard. That is negative[3]'s #3023 half (its #3031 half is already pinned), and acceptance[5]'s exemption half, which the clause scores from this pin because no public door is left. The ref also says that acceptance[5]'s refusal half is pinned without its owned-row read. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From 9f2926409d400ab506e9283f6224274640c9efb5 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:07:43 +0000 Subject: [PATCH 09/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20appr?= =?UTF-8?q?ovals.json:=20decision-action-matrix=20re-scored=20per=20RUNNER?= =?UTF-8?q?=20rule=206?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ledger text only; see the round-15 PR body for the per-finding table. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- docs/qa/platform-checklist/areas/approvals.json | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index 2246c9cb38..a0e90b0f89 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -417,7 +417,7 @@ "title": "Every approval action executes its REST route and produces the expected state transition plus a timeline entry", "since": "v16", "status": "active", - "revision": 6, + "revision": 7, "priority": "P1", "surface": "mixed", "personas": ["dev admin (pending approver on the seeded requests; submitter of the invoice request)"], @@ -428,7 +428,8 @@ "showcase_budget_approval's manager step declares the ADR-0044 revise loop (maxRevisions: 2) and lockRecord: false; its exec step (budget > 500000) declares NO revise edge and lockRecord: true — both sides of two gates in one flow" ], "knownGaps": [ - "the ORG-LESS request the READ_BACK_FAILED clause needs arises only from schedule / time-relative / api-trigger runs, which open their requests with organization_id null by construction (#10131; #9132 pinned that behaviour rather than repairing it) — no seeded showcase flow opens one on demand, so that clause cannot be provisioned on stock seeds. QA run #21784 tried the api-trigger route and found even an api-hook approval org-stamped, so that candidate is refuted (parked on #22017 row 19). A schedule-triggered approval flow (scheduled work needs OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true) is the one untried candidate, not run-proven. The behaviour itself is unit-pinned (packages/plugins/plugin-approvals/src/approval-service.test.ts, the #12769 org-filtered read-back describe), and that pin is how acceptance[9] is scored. It proves the clause's verdict: the mutation lands, and the read-back throws READ_BACK_FAILED naming the request instead of a success with request:null, on recall, decide and comment alike, with org-less and org-matched controls. Only the wire envelope (the generic 500 APPROVAL_*_FAILED carrying that text) stays unobserved on stock: record it blocked(fixture) beside the pin's pass" + "the ORG-LESS request the READ_BACK_FAILED clause needs arises only from schedule / time-relative / api-trigger runs, which open their requests with organization_id null by construction (#10131; #9132 pinned that behaviour rather than repairing it) — no seeded showcase flow opens one on demand, so that clause cannot be provisioned on stock seeds. QA run #21784 tried the api-trigger route and found even an api-hook approval org-stamped, so that candidate is refuted (parked on #22017 row 19). A schedule-triggered approval flow (scheduled work needs OS_AUTOMATION_SCHEDULED_WORK_ENABLED=true) is the one untried candidate, not run-proven. So on stock acceptance[9] is recorded blocked(fixture), citing this gap. Two pins sit beside that verdict as lower-layer evidence, never its pass (RUNNER rule 6, a pin scores only the layer it drives). packages/plugins/plugin-approvals/src/approval-service.test.ts's 'ApprovalService — org-filtered read-back refuses loudly (#12769)' describe drives the service over a fake engine: the mutation lands, and the read-back throws READ_BACK_FAILED naming the request instead of a success with request:null, on recall, decide and comment alike, with org-less and org-matched controls. packages/rest/src/rest-approvals-wire-codes.test.ts's 'an org-filtered read-back on approve answers 500 READ_BACK_FAILED — named, not the template fallback' drives the approve route handler over a mocked service", + "no pin scores a full clause of this item at its layer (RUNNER rule 6, a pin scores only the layer it drives), so the item carries no automated ref (acceptance[9]: knownGaps[0]). negative[1]'s throttle is pinned below the remind door only. approval-service.test.ts's 'remind: notifies pending approvers, audits, and throttles repeats' and 'remind: cool-down measures from the NEWEST reminder, not the first' assert that ApprovalService.remind throws THROTTLED on the second call, over a fake engine. packages/rest/src/rest-approvals-wire-codes.test.ts's 'remind inside the cool-down answers 429 THROTTLED — the contract-documented rejection' maps a mocked rejection to 429 THROTTLED at the route handler. 'A silent second success' is the remind POST's answer on a booted server, which neither drives, so the negative is driven by hand. automated may return when a pin drives a decision route, or the remind door, on a booted stack" ] }, "variants": ["approve", "reject", "reassign", "revise (send-back)", "resubmit", "recall", "remind", "request-info", "comment"], @@ -504,7 +505,7 @@ { "clause": "the org-scope read-back gate (#12769, landed #13181): deciding a request whose row is invisible inside the caller's organization narrowing answers a LOUD named refusal — the decision IS recorded, only the echo is refused — never a well-formed 200 whose declared-non-null `request` is null on the wire", "oracle": "api", - "verify": "decide an ORG-LESS request (organization_id null — the shape every schedule / time-relative / api-trigger run mints, #10131) as an org-scoped caller the override gate admits (isOverrideActor: 'a null-org request is global and any admin may release it'): the wire answer is the 500 APPROVAL__FAILED envelope whose error text leads with READ_BACK_FAILED and names the remedy ('read the request back with a system or matching-organization context'); a follow-up re-read under a system/matching-org context shows the decision landed — status moved, the action row present, nothing rolled back. READ_BACK_FAILED is deliberately NOT in handleApprovalError's prefix map, so the generic 500 envelope carrying the named text is the designed shape, not a bug to file", + "verify": "decide an ORG-LESS request (organization_id null — the shape every schedule / time-relative / api-trigger run mints, #10131) as an org-scoped caller the override gate admits (isOverrideActor: 'a null-org request is global and any admin may release it'): the wire answer is 500 with code READ_BACK_FAILED and an error text, its prefix stripped, saying the write was recorded and naming the remedy ('read the request back with a system or matching-organization context'); a follow-up re-read under a system/matching-org context shows the decision landed — status moved, the action row present, nothing rolled back. READ_BACK_FAILED is a named 500 row of handleApprovalError's prefix map since 5b3ff63cc (packages/rest/src/approvals-routes.ts), beside RESUME_FAILED: the 500 is the designed status, not a bug to file", "evidence": "the refused decision response + the system-context re-read showing the recorded write" } ], @@ -514,10 +515,6 @@ "any action POST accepted from an actor the service should deny (wrong relationship to the request) is a FAIL even when the state happens to end up plausible" ], "traps": ["automation-input", "wrong-persona"], - "automated": { - "kind": "test", - "ref": "acceptance[9] (the READ_BACK_FAILED clause) ONLY: packages/plugins/plugin-approvals/src/approval-service.test.ts, describe 'ApprovalService — org-filtered read-back refuses loudly (#12769)'. The other clauses still need the run" - }, "source": [ "#3358 §1 (the action-table evidence this matrix grounds in)", "packages/rest/src/rest-route-ledger.ts (approve/reject/recall/revise/resubmit/reassign/remind/request-info/comment + GET /:id/actions)", @@ -539,6 +536,12 @@ "date": "2026-10-19", "change": "steps[7] and acceptance[1].verify corrected — two checklist-accuracy findings of the 18.0 QA sweep (QA run #24542, approvals.decision-action-matrix; wave anchor #24579 §3). steps[7] reminded 'the admin-submitted invoice request', which a shared boot's approvals.decision-only-via-service has already decided; any admin-submitted pending request serves, and the run used a budget request. acceptance[1] asked for 'the approve/reject edge taken', but end nodes log no step, so the run evidenced the reject by the absence of the approve edge's successor, the needs_exec decision node (examples/app-showcase/src/automation/flows/index.ts). The verify now names that discriminator. Clause text unchanged", "ref": "#24632" + }, + { + "revision": 7, + "date": "2026-10-20", + "change": "automated removed; fixtures.knownGaps[0] rewritten and fixtures.knownGaps[1] added; acceptance[9].verify's envelope corrected — QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the sweep-wide rule-6 contradiction at approvals acceptance[9], and 'the pin also asserts negative[1]'), round 15 of wave anchor #24579. (1) The ref claimed acceptance[9] from approval-service.test.ts's #12769 describe, and knownGaps[0] said that pin is how acceptance[9] is scored. The pin drives ApprovalService over a fake engine, and the clause scores the wire answer, so by RUNNER rule 6 (a pin scores only the layer it drives) it is evidence beside a blocked(fixture) verdict. (2) Its throttle cases assert negative[1] at the service, and rest-approvals-wire-codes.test.ts maps THROTTLED to 429 over a mocked service; neither drives the remind door, so negative[1] joins no ref. (3) No full clause is pinned, so automated is dropped (theme-B rule). (4) Located while rewriting knownGaps[0]: acceptance[9].verify said READ_BACK_FAILED rides the generic template-generated 500 and is deliberately absent from handleApprovalError's prefix map. It has been a named 500 row there since 5b3ff63cc (packages/rest/src/approvals-routes.ts), pinned by rest-approvals-wire-codes.test.ts. Read at base b1676441ba. No clause changed", + "ref": "#PENDING-24579R15" } ] }, From d2ffee58e5232b260a12eb68c720dd054f404eee Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 20 Oct 2026 13:19:56 +0000 Subject: [PATCH 10/10] =?UTF-8?q?docs(qa):=20round=2015=20=E2=80=94=20hist?= =?UTF-8?q?ory=20rows=20name=20this=20PR=20instead=20of=20the=20placeholde?= =?UTF-8?q?r?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 21 new history rows were committed with the placeholder ref; this swaps in the PR's number and changes nothing else. Claude-Session: https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp Co-authored-by: Claude --- docs/qa/platform-checklist/areas/approvals.json | 2 +- docs/qa/platform-checklist/areas/attachments-storage.json | 2 +- docs/qa/platform-checklist/areas/automation.json | 4 ++-- docs/qa/platform-checklist/areas/cli.json | 6 +++--- docs/qa/platform-checklist/areas/dashboards.json | 4 ++-- docs/qa/platform-checklist/areas/identity-auth.json | 2 +- docs/qa/platform-checklist/areas/integration-system.json | 8 ++++---- docs/qa/platform-checklist/areas/platform-core.json | 8 ++++---- docs/qa/platform-checklist/areas/records-forms.json | 6 +++--- 9 files changed, 21 insertions(+), 21 deletions(-) diff --git a/docs/qa/platform-checklist/areas/approvals.json b/docs/qa/platform-checklist/areas/approvals.json index a0e90b0f89..879dd9f199 100644 --- a/docs/qa/platform-checklist/areas/approvals.json +++ b/docs/qa/platform-checklist/areas/approvals.json @@ -541,7 +541,7 @@ "revision": 7, "date": "2026-10-20", "change": "automated removed; fixtures.knownGaps[0] rewritten and fixtures.knownGaps[1] added; acceptance[9].verify's envelope corrected — QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the sweep-wide rule-6 contradiction at approvals acceptance[9], and 'the pin also asserts negative[1]'), round 15 of wave anchor #24579. (1) The ref claimed acceptance[9] from approval-service.test.ts's #12769 describe, and knownGaps[0] said that pin is how acceptance[9] is scored. The pin drives ApprovalService over a fake engine, and the clause scores the wire answer, so by RUNNER rule 6 (a pin scores only the layer it drives) it is evidence beside a blocked(fixture) verdict. (2) Its throttle cases assert negative[1] at the service, and rest-approvals-wire-codes.test.ts maps THROTTLED to 429 over a mocked service; neither drives the remind door, so negative[1] joins no ref. (3) No full clause is pinned, so automated is dropped (theme-B rule). (4) Located while rewriting knownGaps[0]: acceptance[9].verify said READ_BACK_FAILED rides the generic template-generated 500 and is deliberately absent from handleApprovalError's prefix map. It has been a named 500 row there since 5b3ff63cc (packages/rest/src/approvals-routes.ts), pinned by rest-approvals-wire-codes.test.ts. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/attachments-storage.json b/docs/qa/platform-checklist/areas/attachments-storage.json index 26823f4d26..e959effad6 100644 --- a/docs/qa/platform-checklist/areas/attachments-storage.json +++ b/docs/qa/platform-checklist/areas/attachments-storage.json @@ -996,7 +996,7 @@ { "revision": 2, "date": "2026-10-16", "change": "clause 3 and knownGaps WIRE-ENVELOPE expected, per an unmeasured source reading, the sanitized 500 INTERNAL_ERROR with the prose only in the server log, and told the next run to revise the clause if a 4xx appeared (#23878 checklist-accuracy finding). It did: re-measured on main c91af8ed6 with the qa-media-constraints recipe, the accept probe and the size probe both answer 400 ERR_FILE_CONSTRAINT with the field-naming message in the body (FileConstraintError now declares status 400), the compliant control answers 201, and the info-level log carries no line for the refusal (#23932 closed). The clause's oracle moves from log to api and scores status, code and the named field; the knownGap records the measured envelope", "ref": "#24036" }, { "revision": 3, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). fixtures.requires[1] cited line 255 of storage-routes.ts, and fixtures.knownGaps[0] and source[5] line 337 of the showcase actions index.ts, in the comma-range spelling (authored in 8d74a57487 as the lists 241-243 and 255, and 334 and 337). At that commit line 255 was the mime_type of the pending sys_file row the presigned-upload route writes, and line 337 the accept/maxSize of the action gallery's p_attachments param; at this base both have moved. requires[1] now anchors storage-routes.ts#registerStorageFamily, whose POST /storage/upload/presigned handler writes that row, and knownGaps[0] and source[5] anchor index.ts#ActionParamGalleryAction. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" }, { "revision": 4, "date": "2026-10-20", "change": "automated.ref adds clause 3, the update path — checklist-accuracy finding of QA run #24779 (wave anchor #24579 round 12). Read at base 307ce4c4b4: file-reference-lifecycle.test.ts's 'accept / maxSize enforcement' case 'refuses an UPDATE onto an oversize file with the same envelope, leaving the record and both files as they were' asserts the refused swap and the unchanged stored value at the same handler-direct level as the clauses the ref already names. No clause changed", "ref": "#24824" }, - { "revision": 5, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[3] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the ref disclaims the wired lane), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 12 added the acceptance[3] update-path case: the one pin is handler-direct over a fake engine, and every clause scores the REST answer. By RUNNER rule 6 (a pin scores only the layer it drives) it scores none of them, round 12's update-path case included. automated is dropped (theme-B rule), and the knownGap carries what the pin asserts. No clause changed", "ref": "#PENDING-24579R15" } + { "revision": 5, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[3] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the ref disclaims the wired lane), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 12 added the acceptance[3] update-path case: the one pin is handler-direct over a fake engine, and every clause scores the REST answer. By RUNNER rule 6 (a pin scores only the layer it drives) it scores none of them, round 12's update-path case included. automated is dropped (theme-B rule), and the knownGap carries what the pin asserts. No clause changed", "ref": "#24891" } ] }, { diff --git a/docs/qa/platform-checklist/areas/automation.json b/docs/qa/platform-checklist/areas/automation.json index 39dadb304c..ed9932c410 100644 --- a/docs/qa/platform-checklist/areas/automation.json +++ b/docs/qa/platform-checklist/areas/automation.json @@ -557,7 +557,7 @@ "revision": 8, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[2] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: Tier 1 in name only), round 15 of wave anchor #24579. The ref already said its one dogfood pin covers ONE variant of acceptance[1]. A variant is not a clause, so no full clause is pinned: automated is dropped (theme-B rule), and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ], "enumSource": { @@ -695,7 +695,7 @@ "revision": 6, "date": "2026-10-20", "change": "negative[0]'s pin note narrowed, automated.ref unchanged — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: negative[0]'s pin is not in the ref), round 15 of wave anchor #24579. Read at base b1676441ba: the dogfood case asserts the anonymous trigger's 401 on the served showcase composition and never reads the runs after it. So it asserts half of negative[0] ('denied (401) with NO run row created'). A pin joins the ref only for a full clause at its layer, so it stays out, and negative[0] now says which half it pins. negative[0]'s FAIL condition is unchanged", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/cli.json b/docs/qa/platform-checklist/areas/cli.json index b933595c2f..3fb79b4014 100644 --- a/docs/qa/platform-checklist/areas/cli.json +++ b/docs/qa/platform-checklist/areas/cli.json @@ -353,7 +353,7 @@ "revision": 7, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[0] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4, 'Refs that claim more than their pins assert'), round 15 of wave anchor #24579. The ref said format.exit-code.test.ts covers acceptance[2]. That pin calls emitJson / emitText in-process, while acceptance[2] asks for `echo $?` after each subcommand's --json success, which only a spawned CLI answers. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause, so automated is dropped (theme-B rule) and the knownGap says what the pin does assert. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -436,7 +436,7 @@ "history": [ { "revision": 1, "date": "2026-08-07", "change": "new item: verdict-to-exit-code mapping read from verify.ts + verify.ts (verdict union), with the inconclusive split and the #5262 posture-honesty negative as load-bearing clauses; the unstageable always-failing fixture recorded as a knownGap", "ref": "claude/platform-test-checklist-ocwugl" }, { "revision": 2, "date": "2026-10-19", "change": "automated.ref says which clause its pin covers — round 2's Tier 1 record (#24598, via wave anchor #24579 §3, comment 6272032736) names this ref among those that over-claim clause coverage: its seven tests assert the #5262 posture selection (acceptance[4]) and nothing of acceptance[0]–[3]. Re-read at base 9a7838edf5. No clause changed", "ref": "#24649" }, - { "revision": 3, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[1] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4, 'Refs that claim more than their pins assert'), round 15 of wave anchor #24579. The ref said verify-tenancy-posture.test.ts covers acceptance[4]. Its seven tests call resolveVerifyMultiTenant only, while acceptance[4] scores the os verify run's payload or error. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause, so automated is dropped (theme-B rule) and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", "ref": "#PENDING-24579R15" } + { "revision": 3, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[1] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4, 'Refs that claim more than their pins assert'), round 15 of wave anchor #24579. The ref said verify-tenancy-posture.test.ts covers acceptance[4]. Its seven tests call resolveVerifyMultiTenant only, while acceptance[4] scores the os verify run's payload or error. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause, so automated is dropped (theme-B rule) and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", "ref": "#24891" } ] }, { @@ -1820,7 +1820,7 @@ { "revision": 2, "date": "2026-10-17", "change": "fixture and steps re-pointed (checklist-accuracy finding, #23878 close-out): a config-declared datasource named `default` is refused by defineStack, `required: true` no longer makes a NOT NULL column (storage.notNull, ADR-0113), and relax_not_null is needs_confirm, so the step-0 drift the safe leg relied on was never auto-appliable. The fixture now names a datasource routed by datasourceMapping default:true; step 0 stages one drift per category (storage.notNull relax = needs_confirm, a planted legacy platform-wide unique index = safe, a removed field = destructive); the steps read a schema dump instead of `os migrate plan`, which composes no driver for a config-declared primary and says its plan is PARTIAL; the safe variant lists the real safe ops; clauses 0, 1 and 3's verify follow. No clause's ruling changed. Re-run on main c91af8ed6 on a minimal scratch stack: datasource named default → build exit 2 with the reserved-name refusal; qa_main + mapping built. off and omitted boots each warned all three drifts and left the dump byte-identical; `os migrate plan` (also with --database-url) reported 'Examined 0 managed table(s)' and a PARTIAL plan. safe boot: '[schema-drift] auto-reconciled replace_unique_index on qa_thing.code', the legacy qa_thing_code_unique gone from the dump, the relax and orphan warnings still printed; the second safe boot printed no reconcile line. NODE_ENV=production with safe: the named ignore warning verbatim, all three warnings, dump unchanged. Keyless os dev over a planted legacy index: auto-reconciled replace_unique_index; non-dev os start over a re-planted one: the warning only, the index kept. Duplicates leg on the pin: driver-sql sql-driver-unique-tenancy.test.ts (incl. 'auto-tightens at boot under autoMigrate: safe when the probe is clean' and 'BLOCKS the tightening on duplicate NULL-organization rows') + sql-driver-schema-drift.test.ts, 38 passed", "ref": "#24036" }, { "revision": 3, "date": "2026-10-19", "change": "steps[4], acceptance[5] (clause, verify) and source[3] re-pointed to the injection site that exists — checklist-accuracy finding of the 18.0 QA sweep (QA run #24551, cli.dev-automigrate-policy acceptance[5] / steps[4]; wave anchor #24579 §3): the cited storage-driver.ts `isDev ? { autoMigrate: 'safe' } : {}` and serve.ts telemetry branch moved. Re-derived at base 9a7838edf5: since #21733 the decision lives in packages/runtime/src/dev-auto-migrate.ts (devAutoMigrateConfig), read by createStandaloneStack's default datasource, resolveStorageDefinition's fallback and the telemetry sibling. The run found the behaviour matching the clause; the clause now names the one decision and the three readers", "ref": "#24649" }, { "revision": 4, "date": "2026-10-20", "change": "line citations re-pointed — the comma-range spelling that round 7 of wave anchor #24579 named (18.0 sweep; round 7's report 6281433885, out_of_scope_findings[1]). source[4] cited lines 322-342 of sql-driver-index-drift.test.ts in the comma-range spelling (authored in 45397edf53 as the list 112-113 and 322-342). Those lines are unchanged at this base: the describe block \"dev auto-reconcile (autoMigrate: 'safe')\", which source[4] now names. A test file declares no symbol worth anchoring, so the block is named in prose. The count of passed tests that history[1] records after a file name is not a citation, and that row is a dated record, so it is left. The spelling is what #13786's strip left when it removed only the first range of a ':A,B' list, and the line-citation limb reads only the colon spellings, so no gate caught it. Where the surrounding words still describe the range the strip removed, the new citation names that code too. No clause's assertion changed", "ref": "#24752" }, - { "revision": 5, "date": "2026-10-20", "change": "automated.ref narrowed per clause, fixtures.knownGaps[2] added — checklist-accuracy findings of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4), round 15 of wave anchor #24579. (1) The bare ref named sql-driver-schema-drift.test.ts alone. By RUNNER rule 6 (a pin scores only the layer it drives) that pin scores negative[0] only. (2) Its production force-off case stages relax_not_null, needs_confirm since ADR-0113, so it passes with or without the guard and asserts no warning. It scores no clause, and the knownGap records it for the anchor to route as a test fix. (3) sql-driver-unique-tenancy.test.ts, which acceptance[4].verify and knownGaps[0] already named, was missing from the ref. Its ADR-0120 D4 cases assert acceptance[4] and negative[2] at the driver, the layer that test-oracle clause names. Read at base b1676441ba. No clause changed", "ref": "#PENDING-24579R15" } + { "revision": 5, "date": "2026-10-20", "change": "automated.ref narrowed per clause, fixtures.knownGaps[2] added — checklist-accuracy findings of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4), round 15 of wave anchor #24579. (1) The bare ref named sql-driver-schema-drift.test.ts alone. By RUNNER rule 6 (a pin scores only the layer it drives) that pin scores negative[0] only. (2) Its production force-off case stages relax_not_null, needs_confirm since ADR-0113, so it passes with or without the guard and asserts no warning. It scores no clause, and the knownGap records it for the anchor to route as a test fix. (3) sql-driver-unique-tenancy.test.ts, which acceptance[4].verify and knownGaps[0] already named, was missing from the ref. Its ADR-0120 D4 cases assert acceptance[4] and negative[2] at the driver, the layer that test-oracle clause names. Read at base b1676441ba. No clause changed", "ref": "#24891" } ] }, { diff --git a/docs/qa/platform-checklist/areas/dashboards.json b/docs/qa/platform-checklist/areas/dashboards.json index dd1b6811a3..22dd0263c6 100644 --- a/docs/qa/platform-checklist/areas/dashboards.json +++ b/docs/qa/platform-checklist/areas/dashboards.json @@ -587,7 +587,7 @@ "revision": 4, "date": "2026-10-20", "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The dogfood pin drives the draft-save and publish doors (acceptance[0]), and reads the dashboard back for its widget count and missing layout only. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -685,7 +685,7 @@ "revision": 2, "date": "2026-10-20", "change": "automated.ref adds empty-group-bucket-parity.test.ts for acceptance[4] — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a ref that misses its own pin), round 15 of wave anchor #24579. acceptance[4].verify names that suite and the ref did not. Read at base b1676441ba: it drives two real drivers and applyInMemoryAggregation over the same rows and asserts the same keys, key types and totals on both paths, which is acceptance[4]'s test oracle. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/identity-auth.json b/docs/qa/platform-checklist/areas/identity-auth.json index aeeb78656c..36a5bf40d0 100644 --- a/docs/qa/platform-checklist/areas/identity-auth.json +++ b/docs/qa/platform-checklist/areas/identity-auth.json @@ -534,7 +534,7 @@ "revision": 9, "date": "2026-10-20", "change": "automated.ref adds security-plugin.test.ts's [#3023] case — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: acceptance[5] and negative[3] cite a pin not in the ref), round 15 of wave anchor #24579. Read at base b1676441ba: the case asserts at the plugin-security unit that an owner_id:null write carrying __referentialFieldClear passes the owner-anchor guard. That is negative[3]'s #3023 half (its #3031 half is already pinned), and acceptance[5]'s exemption half, which the clause scores from this pin because no public door is left. The ref also says that acceptance[5]'s refusal half is pinned without its owned-row read. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/integration-system.json b/docs/qa/platform-checklist/areas/integration-system.json index 317360d827..adbfb8c048 100644 --- a/docs/qa/platform-checklist/areas/integration-system.json +++ b/docs/qa/platform-checklist/areas/integration-system.json @@ -299,7 +299,7 @@ "revision": 3, "date": "2026-10-20", "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The one pin drives createMcpProviderFactory over a fake client factory. By RUNNER rule 6 (a pin scores only the layer it drives) it scores negative[0] (on a deny, nothing reaches the spawner) and is provider-layer evidence for the boot-output clauses acceptance[0]–[2]. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -709,7 +709,7 @@ "revision": 10, "date": "2026-10-20", "change": "automated removed, fixtures.knownGaps[2] added — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The one dogfood pin asserts acceptance[0]'s no-sys_webhook-row half on its own fixture stack and drives no activation door. By RUNNER rule 6 (a pin scores only the layer it drives) no full clause is pinned, so automated is dropped (theme-B rule) and the knownGap says what the pin asserts. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ], "enumSource": { @@ -1331,7 +1331,7 @@ "revision": 5, "date": "2026-10-20", "change": "automated.ref respelled — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a ref that misses its own pin), round 15 of wave anchor #24579. The second pin was spelled '(+ __tests__/envelope.conformance.test.ts)', a partial path in a parenthesis, so the run's ref parser missed it and ran it separately (24/24). It is now a full repo path after ' + ', as the other multi-pin refs spell it. The ref's scope is unchanged. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -1573,7 +1573,7 @@ "revision": 4, "date": "2026-10-20", "change": "automated.ref narrowed per clause; acceptance[0] (verify, evidence), acceptance[3] (verify, evidence) and fixtures.knownGaps[0] rewritten — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at drift-gate acceptance[0] and [3], round 15 of wave anchor #24579. acceptance[0]–[3] score the boot log, and the item told the runner to score them from the plugin pins when the live arm is blocked ('Unit fallback', 'the unit pins are the fallback evidence'). The three pins drive ExternalValidationPlugin over stubbed services. By RUNNER rule 6 (a pin scores only the layer it drives) they score the test-oracle clauses acceptance[4]–[6] and negative[2] and [4], and are cited as plugin-layer evidence beside a blocked(fixture) verdict for the rest. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/platform-core.json b/docs/qa/platform-checklist/areas/platform-core.json index 4aef7f0bbb..2965218612 100644 --- a/docs/qa/platform-checklist/areas/platform-core.json +++ b/docs/qa/platform-checklist/areas/platform-core.json @@ -496,7 +496,7 @@ { "revision": 3, "date": "2026-10-16", "change": "steps[1], [3] and [7] and acceptance[0].verify and [5].verify re-pointed (checklist-accuracy findings of QA run #23939, wave #23878). A plain PUT /meta/view/NAME saves state=active, so the draft never appeared in /meta/_drafts — ?mode=draft is required — and a view's name must be qualified OBJECT.view (#23939 used showcase_task.os_qa_view_RUNID). The Studio half now names the create form (/_console/apps/showcase_app/metadata/page/new) and its 'list' default, which has to be set to 'record' for a record page. Not loosened: every clause still scores the same server reads", "ref": "#24036" }, { "revision": 4, "date": "2026-10-20", "change": "automated.stale.observedIn, partial, why and ownedBy refreshed — the carried note from round 6 of wave anchor #24579 (its report, comment 6280484387), the refresh round 6 made on studio-authoring.record-page-roundtrip in PR #24697. Tier 1 run #24598 re-measured studio-record-page.spec.ts red at the sweep's console pin 4016370af3c9 (0 passed / 1 failed), and the spec is byte-identical at the shipped pin 12dc7b47c340, so the marker first written from #7695 still holds; ownedBy names the family card objectstack-ai/objectui#13104. The why also said studio-object-designer.spec.ts was unaffected; #24598 ran its F1 and F4 red (F2 and F3 passed), and partial now says so. No clause changed", "ref": "#24717" }, { "revision": 5, "date": "2026-10-20", "change": "automated.stale removed, automated.ref scoped per objectui spec, and acceptance[2].verify's stale note rewritten — round 14 of wave anchor #24579 (18.0 sweep), on the objectui pointer 6284006658. objectui#13132 (objectui 0b36ffa) repaired both specs the stale record named, and the console pin moved to 616df6598b42 (PR #24829), which contains 0b36ffa. studio-record-page.spec.ts: blob 181b2562c18d at the old pin 12dc7b47c340, 3844a60ccd79 at both 0b36ffa and 616df6598b42; it now sets Page type to record and picks the object from the Object combobox. studio-object-designer.spec.ts: blob 5967807df335, then 58a7f1cbf024 at both; F1 and F4 repaired. objectui measured its whole live suite at 0b36ffa as 61 passed (11.0m) on the shipped showcase; no live run exists at 616df6598b42, so the verdict there is NOT MEASURED. Re-read at the new pin, the ref keeps only what this record claimed: studio-record-page.spec.ts asserts acceptance[2], the clause the stale record sent to hand-driving, and studio-object-designer.spec.ts, on which no clause here rests, now says so. The dogfood pin is unchanged. No clause changed", "ref": "#24857" }, - { "revision": 6, "date": "2026-10-20", "change": "automated.ref and acceptance[3].verify narrowed — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: acceptance[3]'s verify names the dogfood pin as covering the PUT door), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 14's ref edit: package-first-authoring.dogfood.test.ts calls protocol.saveMetaItem in-process on a booted CRM stack and never PUT /api/v1/meta. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause of this item, and the ref and acceptance[3].verify now say so. The objectui studio-record-page.spec.ts keeps acceptance[2]. No clause changed", "ref": "#PENDING-24579R15" } + { "revision": 6, "date": "2026-10-20", "change": "automated.ref and acceptance[3].verify narrowed — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: acceptance[3]'s verify names the dogfood pin as covering the PUT door), round 15 of wave anchor #24579. Re-read at base b1676441ba, after round 14's ref edit: package-first-authoring.dogfood.test.ts calls protocol.saveMetaItem in-process on a booted CRM stack and never PUT /api/v1/meta. By RUNNER rule 6 (a pin scores only the layer it drives) it scores no clause of this item, and the ref and acceptance[3].verify now say so. The objectui studio-record-page.spec.ts keeps acceptance[2]. No clause changed", "ref": "#24891" } ] }, { @@ -2694,7 +2694,7 @@ "revision": 5, "date": "2026-10-20", "change": "automated.ref says which clause each part scores, and acceptance[5].verify names its check — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: 'acceptance[5] is checked by the item's enumSource gate, not a pin'), round 15 of wave anchor #24579. The pin drives LifecycleService.sweep over a fake engine: acceptance[2]'s pinned half, [3], and negative[0]–[1]. acceptance[5]'s count is held by the enumSource variants-freshness ratchet of check:platform-checklist (LifecycleClassSchema, expect 5), and its engine-set half by reading packages/objectql/src/engine.ts. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -3068,7 +3068,7 @@ "revision": 4, "date": "2026-10-20", "change": "automated.ref narrowed per clause — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at settings-audit acceptance[0]–[4], round 15 of wave anchor #24579. The ref listed seven cases whose names read as acceptance[0], [2], [3] and [4]. The pin drives SettingsService over stub audit sinks and never reads sys_platform_setting_audit or sys_audit_log over the API, which is what those clauses score. By RUNNER rule 6 (a pin scores only the layer it drives) it scores acceptance[5] and its negatives [2] and [3] only (QA run #24838 scored acceptance[0]–[4] live). Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -3245,7 +3245,7 @@ "revision": 2, "date": "2026-10-20", "change": "automated.ref narrowed per clause — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: a bare ref over-implies coverage), round 15 of wave anchor #24579. The five pins pass as acceptance[0] asks, but none drives the install door, the GET/PUT door or an os start boot. By RUNNER rule 6 (a pin scores only the layer it drives) they score acceptance[0] only, and the ref says which lower layer each reaches for acceptance[1]–[3]. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, diff --git a/docs/qa/platform-checklist/areas/records-forms.json b/docs/qa/platform-checklist/areas/records-forms.json index 16bbe19f59..4e9977e87f 100644 --- a/docs/qa/platform-checklist/areas/records-forms.json +++ b/docs/qa/platform-checklist/areas/records-forms.json @@ -3973,7 +3973,7 @@ "revision": 6, "date": "2026-10-20", "change": "automated.ref and acceptance[1].verify re-scored per RUNNER rule 6 — the sweep-wide rule-6 contradiction QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4) names at cascading-multilevel acceptance[1], round 15 of wave anchor #24579. acceptance[1] scores the direct POST's answer. Its verify and the ref (round 14 moved the objectql pin's scope into it) scored it, and acceptance[2], from rule-validator.option-visibility.test.ts, which calls evaluateValidationRules directly. By the rule (a pin scores only the layer it drives) that pin scores neither clause. Re-read at the console pin 616df6598b42 (blob 5f65228948b7 of cascading-options.spec.ts, unchanged since round 14), the ref's own objectui spec carries a second case, 'the server rejects an out-of-set option value, and accepts an in-set one', which POSTs to the live backend and asserts the out-of-set 400 VALIDATION_FAILED with invalid_option on province and the in-set 201. That is acceptance[1] at the HTTP door, so the ref and the verify cite it; no live run exists at that pin, so its verdict there is NOT MEASURED. acceptance[2] needs the run. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -4081,7 +4081,7 @@ "revision": 3, "date": "2026-10-20", "change": "automated.ref narrowed per clause; acceptance[3].verify and fixtures.knownGaps[1] rewritten — checklist-accuracy finding of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4: the acceptance[2] and [4] guards are pinned at the engine only), round 15 of wave anchor #24579. secret-fields.test.ts drives a real ObjectQL engine over a stub driver, with no HTTP. By RUNNER rule 6 (a pin scores only the layer it drives) it scores none of this item's api clauses, so the ref names it engine-layer evidence for acceptance[2]–[4] and negative[1]–[3], and names the dogfood pin's own clauses (acceptance[0]'s API oracle, acceptance[1], negative[0]). The same rule makes acceptance[3]'s 'unit-pin verdict' (the #22017 row 22 disposition) a rule-6 contradiction, located here beyond #24840's list: no stock composition runs without a provider, so the clause is now recorded blocked(fixture) with the pin beside it. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] }, @@ -4313,7 +4313,7 @@ "revision": 5, "date": "2026-10-20", "change": "automated.ref narrowed per clause; acceptance[1].verify, acceptance[2].verify, acceptance[3].verify and steps[4] rewritten — checklist-accuracy findings of QA run #24840 (18.0 sweep round 4, Tier 1 at 307ce4c4), round 15 of wave anchor #24579. (1) The ref said the two SQL pins assert '409 UNIQUE_VIOLATION over real REST'. They drive SqlDriver over better-sqlite3 and map its refusal to that envelope through a test-local createAsApi helper, so no REST door is driven. (2) The sweep-wide rule-6 contradiction #24840 names at acceptance[1]–[3]: those clauses score the REST answer, and their verifies and steps[4] told the runner to score them from the memory-driver pin ('score from the pin'). By RUNNER rule 6 (a pin scores only the layer it drives) the pins are now cited as driver-level evidence only, and an updateMany door the boot does not expose is recorded blocked(fixture). The ref names the clauses each pin scores: acceptance[4] (test oracle) and [5], and negative[2] and [3]. Read at base b1676441ba. No clause changed", - "ref": "#PENDING-24579R15" + "ref": "#24891" } ] },